一种语法糖,提供了一种方便的语法,在变参模板的参数包上实现简单的递归运算。
先看一个不使用折叠表达式的例子。
例子:计算任意多个数的和(递归解包)
可以看到,这种方式使用参数包比较繁琐,并且使用时需小心谨慎,如sum<double, double>(1, 2, 3.2, 4, 5)
所示。
引入折叠表达式
组成:一対小括号,一个涉及参数包的表达式,一个运算符,一个省略号。如(args + ...)
,其中args
是参数包的名称。
理解折叠表达式:想象一下,在参数包中每个对象之间放一个运算符,得到一个表达式,返回值是该表达式的计算结果。
例子:计算任意多个数的和
在折叠表达式(args + ...)
中,操作数(operand)只有一个,就是args
,一个参数包。而...
不是操作数,它只是语法的一部分。
折叠表达式中的操作数,可以是任意的包含参数包的表达式。这个表达式中可以只有一个参数包,也可以更复杂。
例子:计算任意多个数的平方和
折叠表达式((args * args) + ...)
中的(args * args)
是这个折叠表达式的操作数,它是一个表达式。想象一下,先计算参数包中每个值的平方,然后在得到的各个结果之间放一个加号运算符,最后返回的是加法的计算结果。
例子:把参数包中的各个值发送到函数中,再対函数返回值应用折叠表达式
这些例子只是想表达,折叠表达式中的操作数可以是任意表达式,只要包含参数包就行。
上面的例子和说明中,折叠表达式中的运算符都用的加号+
,实际上也可以用其他的二元运算符。
例子,打印所有接收到的元素。逗号运算符。
这里使用逗号运算符,
。我们并不关心折叠表达式返回什么,关心的只是副作用(side effects)。
使用lambda函数代替log()
,缩短代码:
进一步精简,使用[&args]
或[&]
让 lambda 函数直接从外部函数的作用域捕获args
。这样,在 lambda 函数中不再需要参数列表(parameter list,原来的(auto &arg)
),调用时也不需要再传参数(argument,原来的(args)
变为了()
)。(语法高亮有错误提示,但实际能成功编译)
一元折叠
折叠表达式有多种形式,上面例子所用的折叠表达式称为“一元折叠”(unary fold),即折叠表达式中只有一个操作数。除此之外有“二元折叠”(binary fold),即这样的折叠表达式中有两个操作数。
一元折叠表达式又有左折叠(left folds)、右折叠(right folds)之分。上面例子中所用的都是右折叠。
左折叠(... + args)
和右折叠(args + ...)
的区别在于,参数包中的参数的组合顺序。对于加法+
,组合顺序(或者称为 折叠方向)无关紧要,但对于减法-
,就必须要考虑了。
例子:对比(一元)左折叠和右折叠
二元折叠
在一元折叠表达式中,再添加一个操作数,就得到了二元折叠表达式(binary folds),额外添加的操作数,有时被称为初始值(initial value)。如(0 + ... + args)
,有两个操作数args
和0
。
与一元折叠一样,二元折叠也有左折叠((0 + ... + args)
)、右折叠((args + ... + 0)
)两种变体。
二元折叠中,运算符出现两次,但并不意味着在二元折叠中能进行两种运算,运算符出现两次只是语法上这样写罢了,位于不同位置的这两个运算符必须相同。
例子:理解二元折叠,(二元)左折叠,(二元)右折叠
二元折叠的两个用途
二元折叠的两个用途:处理空参数包时不会出现编译错误;相比一元折叠更为灵活的折叠。
例子:处理空参数包,一元折叠和二元折叠对比
使用二元折叠的函数在不带参数调用时返回0,使用一元折叠的函数在不带参数调用时出现编译错误。
例子:二元折叠,折叠<<
运算符
print("hello ", "world");
实际上等价于:(std::cout << "hello ") << "world";
std::cout << "hello "
返回对std::cout
对象的引用,从而可以“链式”打印更多的值。
参考