C++17 引入了模板折叠表达式(template fold expressions),它让我们在编写变长模板函数时,能够更简洁、高效地对参数包进行操作。下面以“求和”为例,演示如何利用折叠表达式完成此任务,并说明其工作原理和使用场景。
1. 传统实现方式(C++11/14)
在 C++11/14 中,如果想用递归方式对参数包进行求和,代码往往需要显式的递归函数和基准情形:
template<typename T>
T sum(T x) { return x; }
template<typename T, typename... Rest>
T sum(T first, Rest... rest) {
return first + sum(rest...);
}
虽然能工作,但代码相对冗长,而且每一次递归都涉及到函数调用和模板实例化,效率稍低。
2. 折叠表达式的基本语法
折叠表达式的核心形式:
- 左折叠(left fold):
(args op ...) - 右折叠(right fold):
(... op args) - 完整折叠(full fold):
(args op ... op ...)
op 可以是任何二元运算符,如 +, *, &&, || 等。
注意:折叠表达式只能用于参数包与二元运算符,不能直接与三元运算符结合。
3. 用折叠表达式实现求和
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 左折叠,等价于 ((a + b) + c) + d ...
}
- 当参数包为空时,编译器会报错(因为没有初始值)。如果想支持空调用,可以提供一个重载:
template<>
int sum<>() { return 0; } // 空参数包时返回 0
或者在函数内部使用 if constexpr 判断参数包是否为空。
4. 支持多种类型的求和
折叠表达式会根据第一个参数的类型推断返回类型,但如果想让返回类型与任意参数类型一致,可显式指定:
template<typename T, typename... Rest>
T sum(T first, Rest... rest) {
return (first + ... + rest); // 先把 first 和 rest 组合成参数包
}
此时返回类型为 T,即第一个参数的类型。
5. 性能与编译速度
折叠表达式在编译期展开为一系列简单的加法指令,编译器可以很容易地进行常量折叠、寄存器分配等优化。与递归实现相比:
- 编译时间:更少的模板实例化,编译更快。
- 运行时性能:在多数情况下相同,但折叠表达式有时能产生更紧凑的代码。
6. 常见使用场景
- 变长函数参数:如
print()、max()、min()等。 - 数学运算:多项式求值、向量加法、矩阵乘法等。
- 构造函数委托:在一个类的构造函数中转发多参数给基类或成员。
- 编译期字符串拼接:利用折叠表达式与 constexpr 字符串进行拼接。
7. 进一步扩展:可变参数的乘积
template<typename... Args>
auto product(Args... args) {
return (args * ...); // 乘积折叠
}
同样可以为空参数包提供默认值:
template<>
int product<>() { return 1; }
8. 结语
模板折叠表达式是 C++17 中非常强大的特性之一,尤其适用于需要对可变参数做重复操作的场景。通过少量代码即可实现既简洁又高效的功能,让模板编程变得更加直观。建议在新项目中优先考虑使用折叠表达式,既能减少代码冗余,又能让编译器发挥更好的优化能力。