Dmi*_*try 5 c++ variadic-templates fold-expression c++17
我试图弄清楚如何使用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 + "_"
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以像这样执行它
std::string s1 = "s1", s2 = "s2", s3 = "s3";
ConcatString(s1, s2, s3);
// result == "s1_s2_s3_"
Run Code Online (Sandbox Code Playgroud)
如您所见,最后一个定界符存在问题。没有运行时检查,有什么办法可以避免此问题?我可以想象的一种解决方案是仅折叠(N-1)个arg,并“手动”连接最后一个。
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...);
}
}
Run Code Online (Sandbox Code Playgroud)
是否可以使用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));
}
Run Code Online (Sandbox Code Playgroud)
用法:
int main()
{
std::cout << Concat(",", "a") << std::endl;
std::cout << Concat(",", "a", "b") << std::endl;
std::cout << Concat(",", "a", "b", "c") << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
输出:
a
a,b,
a,b,c
这里的技巧是将参数包拆分为单数的“ head”(str)和可变的“ tail”(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)>{});
}
Run Code Online (Sandbox Code Playgroud)
这种丑陋的语法是我创建一个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)>{});
}
Run Code Online (Sandbox Code Playgroud)
现在std::cout << ConcatEveryOther(",", "a", "b", "c", "d", "e", "f", "g") << std::endl;将给我们输出
a,bc,de,fg
| 归档时间: |
|
| 查看次数: |
152 次 |
| 最近记录: |