折叠表达式是 C++17 对可变参数模板的一项重要改进,它让我们能够用简洁的语法对参数包执行二元运算,从而避免了手写递归结构。下面我们通过几个典型示例,展示折叠表达式的用法、实现原理以及在实际项目中的应用场景。
1. 折叠表达式的基本语法
折叠表达式的基本形式是:
(expr op ... op expr)
或者
(... op expr)
这里 op 可以是任何二元运算符(如 +, *, &&, || 等)。语法会把参数包 expr 依次与 op 结合,产生一个单一的表达式。若参数包为空,折叠表达式会根据运算符的类型选择一个默认值(比如 对于 +、1 对于 * 等),这一点在使用时需要留意。
示例 1:求和
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 等价于 args1 + args2 + ... + argsN
}
示例 2:逻辑与
template<typename... Args>
bool all_true(Args... args) {
return (... && args); // 等价于 args1 && args2 && ... && argsN
}
2. 逆向折叠
C++17 还支持逆向折叠(reverse fold),语法为:
(... op expr)
它从右向左结合参数包。典型用例是乘法累乘:
template<typename... Args>
auto product(Args... args) {
return (... * args); // 等价于 argsN * ... * args2 * args1
}
虽然在乘法场景下与正向折叠没有区别,但在不满足结合律的运算符(如除法、取模)时,顺序会影响结果。
3. 带默认值的折叠
对于空参数包,折叠表达式会使用某个默认值。例如:
auto total = (0 + ...); // 结果为 0
auto flag = (true && ...); // 结果为 true
这在实现可变参数函数时非常有用,避免了空参数包导致的编译错误。
4. 自定义运算符的折叠
我们也可以使用自定义运算符来折叠参数包,前提是该运算符已在相应类型上重载。例如,下面演示一个 Vector3 的求和:
struct Vector3 { double x, y, z; };
Vector3 operator+(const Vector3& a, const Vector3& b) {
return {a.x + b.x, a.y + b.y, a.z + b.z};
}
template<typename... Vec>
Vector3 sum_vec(Vec... v) {
return (v + ...);
}
此时调用 sum_vec(v1, v2, v3) 就会自动展开为 v1 + v2 + v3。
5. 实际项目中的应用
5.1 变参日志系统
在日志框架中,经常需要将任意数量的参数拼接成字符串。使用折叠表达式可以大幅简化代码:
template<typename... Args>
void log(const std::string& fmt, Args... args) {
std::ostringstream oss;
oss << fmt << ": " << (... << args << " ");
std::cout << oss.str() << std::endl;
}
5.2 性能计时器
如果想用递归方式测量多个函数执行时间,折叠表达式可以一次性完成:
template<typename Func, typename... Args>
auto timed(Func f, Args... args) {
auto start = std::chrono::high_resolution_clock::now();
auto res = (f(args)...); // 逐个执行
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Elapsed: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " ms\n";
return res;
}
6. 与 C++11/14 的比较
在 C++11/14 中实现类似功能通常需要手写递归模板或使用 initializer_list 技巧,代码冗长且不易维护。折叠表达式让这些模式变得简洁可读,也减少了模板错误导致的编译错误。
7. 小结
折叠表达式是 C++17 引入的一项强大语法,能让可变参数模板的写法更接近普通代码。掌握它后,许多常见的变参模式(求和、逻辑与、字符串拼接等)都能用极少的代码完成。建议在新的 C++ 项目中优先考虑使用折叠表达式,既能提升代码可读性,也能降低维护成本。