引言:变参模板最烦人的不是 SFINAE
C++ 模板元编程里最让人头疼的事情是什么?不是 SFINAE,也不是 std::enable_if,而是写变参模板时那一大堆递归终止函数。C++17 引入的折叠表达式正是为了解决这个痛点,它让以前需要二十行代码的事情,现在只需一行。
从一个痛点开始
你一定写过这种代码:
template<typename T>
T sum(T v) { return v; }
template<typename T, typename... Args>
T sum(T first, Args... rest) {
return first + sum(rest...);
}
为了实现一个可变参数的 sum,需要两个模板函数:一个处理单参数的终止条件,一个进行递归展开。代码冗余、难以阅读,而且任何一处写错都可能导致无限递归。
更麻烦的是,递归终止函数不能少。如果忘记写它,或者签名与展开版本略有不同,编译器就会产生一大堆难以理解的错误。当你的可变参数模板层层嵌套时,这个痛点会被放大到难以忍受的地步。
C++17 的折叠表达式彻底改变了这一现状。
四种折叠表达式
折叠表达式的核心语法只有四种形式:
- 一元左折叠:
(... op pack),展开为 ((a op b) op c) op d
- 一元右折叠:
(pack op ...),展开为 a op (b op (c op d))
- 二元左折叠:
(init op ... op pack),展开为 ((init op a) op b) op c
- 二元右折叠:
(pack op ... op init),展开为 a op (b op (c op init))
其中 op 是任意二元运算符,pack 是模板参数包,init 是初始值。
用这四种语法,之前那个递归版本的 sum 可以写成:
template<typename... Args>
auto sum(Args... args) {
return (0 + ... + args);
}
这是二元左折叠。它展开后的执行顺序是 ((0 + a) + b) + c,与递归版本完全等价,但只需要一个函数。

需要注意的是,一元折叠对 op 有限制。例如 (... - args) 是合法的,但如果参数包为空,则会导致编译中断。二元折叠则可以处理空参数包的情况,因为 init 提供了默认返回值。
实战用例
折叠表达式不仅仅用于数学计算。在实际项目中,它的场景比想象中更广:
1. 可变参数打印
template<typename... Args>
void print(Args&&... args) {
(std::cout << ... << std::forward<Args>(args)) << '\n';
}
这是一元左折叠。当调用 print(1, " ", 2, " ", 3) 时,它展开为 (((std::cout << 1) << " ") << 2) << " " << 3 并输出 1 2 3。在 C++17 之前,这种打印通常需要结合 std::initializer_list 或递归来实现。
2. 逻辑判断
template<typename... Args>
bool all_true(Args... args) {
return (... && args);
}
这是一元左折叠,相当于 a && b && c && d。它的优势是保持了 && 的短路求值特性:只要遇到第一个 false,后续参数不会被计算。
3. 比较操作
template<typename T, typename... Args>
bool within(T min, T max, Args... args) {
return ((args >= min && args <= max) && ...);
}
这是 C++17 之前难以优雅实现的功能。模板参数包与布尔表达式的结合,让代码意图一目了然。

性能与可靠性
有开发者担心这种语法糖会引入额外开销。实际上,折叠表达式在编译阶段就完成展开,编译器生成的机器码与手写展开版本完全相同。反倒是因为代码更简洁,编译器优化的空间可能更大。
另一个隐形好处是可靠性。递归模板实现中,如果忘记写终止函数,或者终止条件与展开函数的签名不匹配,就会出现编译错误或无限递归。折叠表达式没有这个风险,一行代码就是全部。
目前主流编译器均已支持该特性。GCC 6、Clang 3.6、MSVC 2017 以及更新版本都已完全支持,无需担心兼容性问题。

总结
折叠表达式是 C++17 中被严重低估的特性。它不像 std::optional 那样被频繁提起,但在变参模板的场景中,它可以用一行代码替代以前的整套递归框架。不仅代码量大幅减少,编译错误信息也更容易理解。开始使用后,你会发现它已经成为不可或缺的工具。
如果你正在系统学习现代 C++,不妨在云栈社区找一些深度内容搭配练习,从折叠表达式这类语法糖到并发编程的底层机制,逐步把 C++17 的核心特性吃透。