深入探索 – Fold 表达式





0/5 (0投票)
极端情况下的 Fold 表达式
自 C++17 以来,折叠表达式就已存在,并且对我们处理可变模板的方式产生了重大影响。 很久以前,我写过关于 折叠表达式 作为元编程系列的一部分,但今天,我们将探索折叠表达式用法的极端情况。
在开始之前,一个重要的免责声明:在本文中,代码示例展示了使用值传递参数的可变模板用法,而没有转发它们。这样做是为了简化它们,并专注于示例背后的思想。
空的可变参数
在单折叠(没有初始化的折叠表达式)的情况下,此情况对于 3 种类型的运算符是合法的:&&
、||
和 ,
。
运算符 &&
template <typename ...Args>
auto and_cond(Args... args) {
return (args && ...);
}
在参数为空的情况下(对于调用 and_cond()
),函数将返回 true
。对这个决定的一个合理的解释可能是 &&
运算符要求不会有任何部分评估为 false
。在这种情况下,根本没有任何部分,因此没有任何部分评估为 false
,因此结果应该是 true
。
运算符 ||
template <typename ...Args>
auto or_cond(Args... args) {
return (args || ...);
}
在参数为空的情况下,函数将返回 false
。对这个决定的一个合理的解释可能是 ||
运算符要求至少有一个部分将评估为 true
。因为根本没有任何部分,所以没有任何部分评估为 true
,因此结果是 false
。
运算符 ,
template <typename ...Args>
auto comma_op(Args... args) {
return (args , ...);
}
在这种情况下,当没有传递参数时,结果将是 void()
。
其他运算符
对于其他运算符,任何不带参数的调用都不会编译。
带单个参数的可变包
在这里,事情变得有点出乎意料。当只传递一个参数给可变包时,结果将忽略运算符并返回与发送给函数的类型和参数相同的类型(在 gcc 13.1.0 和 clang 16.0.0 上进行了测试)。例如
template <typename ...Args>
auto func(Args ...args)
{
return (args || ...);
}
int main() {
using namespace std::string_literals;
std::cout << func("I am a string"s); // Will print "I am a string"
return EXIT_SUCCESS;
}
根据 C++ Insights 生成的代码是
template<>
std::basic_string<char> func<std::basic_string<char>>(std::basic_string<char> __args0)
{
return std::basic_string<char>(static_cast<std::basic_string<char>&&>(__args0));
}
对于任何其他运算符,包括 +=
、.*
和 ->
,也会是相同的。
无参数的二元折叠表达式
与具有单个参数的单折叠表达式应用相同的规则,也适用于无参数的二元折叠表达式
template <typename ...Args>
auto func(Args ...args)
{
return (args ->* ... ->* "I am a string"s);
}
int main() {
std::cout << func(); // Will print "I am a string"
return EXIT_SUCCESS;
}
C++ Insights 生成的代码
template<>
std::basic_string<char> func<>()
{
return std::operator""s("I am a string", 13UL);
}
结论
我们必须密切关注极端情况,并且永远不能指望基本逻辑站在我们这边(谁会相信我们可以将一个 std::string
发送到使用 ||
或 &&
运算符的折叠表达式中?)。
本文的灵感来自于 Daisy Hollman 和我之间的一次对话。 谢谢 Daisy!
一个不相关的注释:我的文章发布频率在过去几个月里有所下降。原因是为在 Core C++ 中的 Core C++(猜猜哪次对话促成了这篇文章)上 Daisy 和我的演讲做准备。