C++17 详解 12
本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。
4.2 折叠表达式
C++11 引入了可变参模版,这是一个非常强力的特性,特别是你想实现一个有可变数量的模版参数的函数时。比如,(C++11)之前你不得不实现多个不同版本的模版函数(一个带一个参数、另一个带两个参数、另一个带三个参数……)。
当你想实现类似 sum 这样的递归函数时,可变参模版仍然需要一些额外的代码。你必须指明递归规则。
比如:
1 | auto SumCpp11(){ |
通过 C++17 我们可以把代码写得更简单:
1 | template<typename ...Args> auto sum(Args ...args) |
有如下几种使用二元操作符的折叠表达式的变种:
表达式 | 名称 | 解释 |
---|---|---|
(... op e) |
一元左折叠 | ((e1 op e2) op ...) op eN |
(init op ... op e) |
二元左折叠 | (((init op e1) op e2) op ...) op eN |
(e op ...) |
一元右折叠 | e1 op (... op (eN-1 op eN)) |
(e op ... op init) |
二元右折叠 | e1 op (... op (eN-1 op (eN op init))) |
译注:注意,折叠表达式外层必须有一个括号。
op
可以是下边 32 种二元操作符中的任意一个:+ - * / % ^ & | = < > << >> += -= *= /= %=
^= &= |= <<= >>= == != <= >= && || , .* ->*
。在一个二元折叠里,两个 op
必须一样。
比如,当你写下:
1 | template<typename ...Args> auto sum2(Args ...args) |
模版函数被展开为:
1 | auto value = 1 + (2 + (3 + 4)); |
对空参数包,默认得到下列值:
操作符 | 默认值 |
---|---|
&& | true |
|| | false |
, | void() |
其它 | 错误 |
这也是为什么你不能不带任何参数调用 sum2
,因为 +
操作符上的一元折叠对空参数列表没有任何默认值。
更多示例
这里有一个使用折叠的非常好的 printf
实现 P0036R0:
1 | template<typename ...Args> |
但是上边的 FoldPrint
一个接一个地打印参数,没有任何分隔符。所以你会看到 “hello102030” 这样的输出。
如果想要分隔符和其它更多格式化选项,你必须调整打印技巧,在逗号操作符上使用折叠:
1 | template<typename ...Args> |
这种通过逗号操作符使用折叠的技巧很方便。另一个例子是一个 push_back
的特殊版本:
1 | template<typename T, typename... Args> |
一般来讲,折叠表达式能让你写出更清晰、更短小、也可能读起来更令人愉悦的代码。