C++17 为模板元编程带来了一个非常有用的特性:折叠表达式(fold expressions)。它的出现让变参模板函数的编写变得更加简洁、清晰和直观。
1. 如何处理变长参数包
在 C++11/14 中,如果我们要处理变长参数包(parameter pack),通常需要递归展开,例如:
1
2
3
4
5
6
7
8
9
10
| template<typename T>
void print(const T& t) {
std::cout << t << '\n';
}
template<typename T, typename... Args>
void print(const T& t, const Args&... args) {
std::cout << t << ", ";
print(args...);
}
|
虽然这段代码能完成任务,但却略显冗长,并且生成的汇编代码也会很多。C++17 中引入的折叠表达式,就是为了解决这个问题。
可以在 godbolt 上看看两种方法生成的汇编代码:https://godbolt.org/z/55a7q85oE
2. 什么是折叠表达式
折叠表达式是一种用运算符对参数包进行折叠的方式。折叠表达式的实例化按以下方式展开成表达式 e:

(图片来源:https://zh.cppreference.com/w/cpp/language/fold)
基本语法形式:
| 类型 | 语法形式 | 示例 |
|---|
| 一元左折叠 | (... op pack) | (... + args) |
| 一元右折叠 | (pack op ...) | (args + ...) |
| 二元左折叠 | (init op ... op pack) | (0 + ... + args) |
| 二元右折叠 | (pack op ... op init) | (args + ... + 0) |
折叠表达式支持多种运算符,包括但不限于:
- 算术运算符:
+, -, *, / - 逻辑运算符:
&&, || - 比较运算符:
==, !=, <, > - 位运算符:
&, |, ^ - 逗号运算符:
,
需要注意的是:
- 若参数包为空,使用二元折叠可设置初始值防止编译错误。
- 初始值放在哪边,就由哪一边“开始结合”
3. 折叠表达式示例
3.1 求和函数
1
2
3
4
| template<typename... Args>
auto sum(Args... args) {
return (... + args); // 左折叠
}
|
使用:
1
| std::cout << sum(1, 2, 3, 4); // 输出 10
|
3.2 打印变参
1
2
3
4
| template<typename... Args>
void print(Args&&... args) {
(std::cout << ... << args) << '\n'; // 左折叠结合流输出
}
|
使用:
1
| print("Hello, ", "fold ", "expression!"); // 输出 Hello, fold expression!
|
3.3 所有参数是否都满足某条件
1
2
3
4
| template<typename... Args>
bool all_true(Args... args) {
return (... && args);
}
|
使用:
1
| bool result = all_true(true, true, false); // false
|
4. 与 std::cout 结合
std::cout << a << b << c 是一个左结合的表达式,其等价于:
1
| ((std::cout << a) << b) << c
|
因此,在使用折叠表达式来打印参数时,必须使用左折叠!!!。
正确示例为:
1
2
3
4
| template<typename... Args>
void print(Args&&... args) {
(std::cout << ... << args); // 左折叠
}
|
这个展开方式等价于:
1
| (((std::cout << arg1) << arg2) << arg3) ...
|
这样每次都把输出结果“继续”传给 std::cout << ...,保持流操作链的有效性。
但如果按下面的写法来:
1
| (std::cout << args << ...);
|
会导致编译错误或者语义错误,因为 std::cout 必须放在最左边,才能正确启动流操作链。
5. 与 std::cout 和逗号运算符结合
对于逗号运算符:在 (a, b) 中,逗号运算符会先执行 a,再执行 b,返回 b 的值。这允许我们:
- 在一次折叠中 执行多条语句
- 且确保顺序执行、无中间变量展开
例如下面的例子:
1
2
3
4
5
6
7
8
9
10
| #include <iostream>
template<typename... Args>
void print(Args... args) {
((std::cout << args << ", "), ...);
}
int main() {
print(1, 3.0, '*');
}
|
其在编译器处理之后会是这个样子(使用这个工具 https://cppinsights.io/):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #include <iostream>
template<typename ... Args>
void print(Args... args) {
(((std::cout << args) << ", "), ...);
}
template<>
void print<int, double, char>(int __args0, double __args1, char __args2) {
(std::operator<<(std::cout.operator<<(__args0), ", ")) ,
(
(std::operator<<(std::cout.operator<<(__args1), ", ")) ,
(std::operator<<(std::operator<<(std::cout, __args2), ", "))
);
}
int main() {
print(1, 3.0, '*');
return 0;
}
|
值得注意的是:看起来 ... 在 args 的右边,但这依然是左折叠。