是否可以使用 C++17 折叠表达式仅折叠部分包?
Is it possible to fold only part of the pack with C++17 fold expressions?
我正在尝试弄清楚如何使用 C++17 折叠表达式仅折叠可变参数模板包的一部分。
假设我想创建一个编译时间 "separated string" 像 "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) 个参数并连接最后一个 "manually".
您可以递归调用 ConcatString
并使用 constexpr if 以避免运行时检查
template<typename String, typename... Strings>
void ConcatString(String& str, Strings&... strs) {
if constexpr(sizeof...(strs) == 0) {
result += str;
} else {
result += str + "_";
ConcatString(strs...);
}
}
Is it possible to fold only part of the pack with C++17 fold expressions?
不,折叠表达式会折叠整个包。但是,我们可以做一些技巧来实现我们需要的。
在这里,您的 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 风格模板元编程使用了这个技巧。
另一种方法是为我们的参数包创建一组索引 0、1、...、N,然后对于我们的折叠逻辑,我们可以为第 0、第 N 甚至第 1 个做一些特殊的事情任意元素。您可以在 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
怎么样:
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));
}
我正在尝试弄清楚如何使用 C++17 折叠表达式仅折叠可变参数模板包的一部分。
假设我想创建一个编译时间 "separated string" 像 "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) 个参数并连接最后一个 "manually".
您可以递归调用 ConcatString
并使用 constexpr if 以避免运行时检查
template<typename String, typename... Strings>
void ConcatString(String& str, Strings&... strs) {
if constexpr(sizeof...(strs) == 0) {
result += str;
} else {
result += str + "_";
ConcatString(strs...);
}
}
Is it possible to fold only part of the pack with C++17 fold expressions?
不,折叠表达式会折叠整个包。但是,我们可以做一些技巧来实现我们需要的。
在这里,您的 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 风格模板元编程使用了这个技巧。
另一种方法是为我们的参数包创建一组索引 0、1、...、N,然后对于我们的折叠逻辑,我们可以为第 0、第 N 甚至第 1 个做一些特殊的事情任意元素。您可以在 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
怎么样:
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));
}