是否可以使用C ++ 17折叠表达式折叠部分包装?

时间:2019-09-24 20:22:58

标签: c++ c++17 variadic-templates fold-expression

我试图弄清楚如何仅使用C ++ 17折叠表达式折叠可变参数模板包的一部分。 假设我想创建一个像"str1_str2_str3_....."

这样的编译时“分隔字符串”

使用这样的代码很容易做到(仅举一个例子):

std::string result;

template<class... Strings>
ConcatString(Strings&... strs)
{
    (ConcatString(strs), ...);
}

template <class String>
void ConcatString(String& str)
{
     result += str + "_"
}

然后我们可以像这样执行它

std::string s1 = "s1", s2 = "s2", s3 = "s3";
ConcatString(s1, s2, s3);
// result == "s1_s2_s3_"

如您所见,最后一个定界符存在问题。没有运行时检查,有什么办法可以避免此问题?我可以想象的一种解决方案是仅折叠(N-1)个arg,然后“手动”连接最后一个。

3 个答案:

答案 0 :(得分:8)

如果可以避免运行时检查,则可以递归调用ConcatString并使用constexpr

template<typename String, typename... Strings>
void ConcatString(String& str, Strings&... strs) {
    if constexpr(sizeof...(strs) == 0) {
        result += str;
    } else {
        result += str + "_";
        ConcatString(strs...);
    }
}

答案 1 :(得分:7)

  

是否可以使用C ++ 17折叠表达式折叠包装的一部分?

否,折叠表达式将在整个包装中折叠。但是,我们可以采取一些技巧来实现我们所需要的。

在这里,您的Concat函数可以简化为使用单个二进制折叠。

template<class String, class... Strings>
std::string Concat(const std::string& delimiter, const String& str, const Strings&... strs)
{
     return (str + ... + (delimiter + strs));
}

用法:

int main()
{
    std::cout << Concat(",", "a") << std::endl;
    std::cout << Concat(",", "a", "b") << std::endl;
    std::cout << Concat(",", "a", "b", "c") << std::endl;
}

输出:

  

a
  a,b
  a,b,c

Live Demo

这里的窍门是将参数包分成一个单数的“ head”(str)和一个变体的“ tail”(strs)。这样,我们让函数参数列表将第一个元素拉离包装。 (C ++ 11样式模板元编程的 lot 使用了这个技巧)。

另一种方法是为参数包创建一组索引0、1,...,N,然后对于折叠逻辑,我们可以对第0,第N甚至是任意元素做一些特殊的操作。您可以在pretty print tuple问题中找到这种方法的变体。 在C ++ 20中,多亏了C ++ 20中的模板lambda,我们可以将所有逻辑移到一个方法中,如下所示:

template<class... Strings>
std::string Concat(const std::string& delimiter, const Strings&... strs)
{
    return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
    {
        return (std::string{} + ... + (I == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
    }(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
}

Demo 2

这种丑陋的语法是我创建一个lambda并在单个语句中调用它。可以说,您可以改为通过std::invoke来调用它。

请注意,我们使用索引检查是否打印定界符。

让我们使用索引检查技巧仅用定界符相互连接:

template<class... Strings>
std::string ConcatEveryOther(const std::string& delimiter, const Strings&... strs)
{
    return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
    {
        return (std::string{} + ... + (I % 2 == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
    }(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
}

现在std::cout << ConcatEveryOther(",", "a", "b", "c", "d", "e", "f", "g") << std::endl;将给我们类似

的输出
  

a,bc,de,fg

Demo 3

答案 2 :(得分:2)

怎么样:

template<typename Last>
std::string concat(Last last) {
    return last;
}

template<typename First, typename ...Rest>
std::string concat(First first, Rest ...rest) {
    return first + '_' + concat(rest...);
}

std::string s1{"foo"}, s2{"bar"}, s3{"baz"};
std::cout << concat(s1, s2, s3); // Prints "foo_bar_baz"

编辑:我使函数更加简单:

template <typename First, typename... Rest>
std::string concat(First first, Rest... rest) {
    return (first + ... + ('_' + rest));
}