是否可以使用 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
这里的技巧是我们将参数包拆分为一个单数“头”(str
)和一个可变参数“尾”(strs
)。通过这种方式,我们让函数参数列表从包中提取第一个元素。(很多C++11 风格的模板元编程都使用了这个技巧)。
另一种方法是为我们的参数包创建一组索引 0、1、...、N,然后对于我们的折叠逻辑,我们可以为第 0、第 N 甚至任意元素做一些特殊的事情。您可以在漂亮的打印元组问题上找到这种方法的变体。在 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)>{});
}
这种丑陋的语法是我创建了一个 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