在 C++17 之后,折叠表达式(fold expression)让我们可以非常简洁地对可变参数模板进行聚合运算。下面以实现一个通用的“加法”函数为例,演示可变参数模板的基本用法、类型安全、以及如何结合 constexpr 让它在编译期求值。
1. 目标功能
我们想要一个叫 sum 的函数,可以接受任意数量、任意类型(只要它们之间能进行 + 运算)的参数,并返回它们的和。示例:
int r1 = sum(1, 2, 3); // 6
double r2 = sum(1.5, 2.5, 3.0); // 7.0
auto r3 = sum(1, 2.5, 3); // 6.5
注意:返回类型由 C++ 的推断机制决定,通常是所有参数中“最佳”兼容的类型。
2. 基本实现
#include <iostream>
#include <type_traits>
// 1. 基础递归实现(C++11 兼容)
template<typename T>
T sum(const T& x) { // 递归终止
return x;
}
template<typename T, typename... Args>
T sum(const T& x, const Args&... args) {
return x + sum(args...); // 递归展开
}
这种实现方式在 C++11/14 时代很常见,但存在两个问题:
- 递归深度:大量参数时会导致栈深度问题或编译器错误。
- 不够简洁:每个递归层都需要单独的实例化。
3. 现代实现(C++17+)
利用 折叠表达式,我们可以把递归压平为一行代码:
template<typename... Args>
auto sum(const Args&... args)
{
return (args + ...); // 左折叠(fold)
}
(args + ...)解释为(((args1 + args2) + args3) + ...)。- 如果参数为空,编译器会报错(可通过
requires或if constexpr处理)。
处理空参数
template<typename... Args>
auto sum(const Args&... args)
{
if constexpr (sizeof...(args) == 0) {
return 0; // 或者根据需求抛出异常
} else {
return (args + ...);
}
}
4. 类型安全与 std::common_type
在混合类型(如 int、double、std::string)时,直接使用 args + ... 可能导致隐式转换失误或错误。可以利用 std::common_type_t 预先确定返回类型:
#include <type_traits>
template<typename... Args>
auto sum(const Args&... args)
{
using result_type = std::common_type_t<Args...>;
if constexpr (sizeof...(args) == 0) {
return result_type{};
} else {
return (static_cast <result_type>(args) + ...);
}
}
这保证了所有参数在加法前都被显式转换为统一类型。
5. 编译期求值(constexpr)
若所有参数都是常量表达式,函数可以在编译期求值:
constexpr int a = sum(1, 2, 3); // a == 6
constexpr double b = sum(1.5, 2.5, 3.0); // b == 7.0
因此,sum 可以声明为 constexpr:
template<typename... Args>
constexpr auto sum(const Args&... args)
{
using result_type = std::common_type_t<Args...>;
if constexpr (sizeof...(args) == 0) {
return result_type{};
} else {
return (static_cast <result_type>(args) + ...);
}
}
6. 完整示例
#include <iostream>
#include <type_traits>
template<typename... Args>
constexpr auto sum(const Args&... args)
{
using result_type = std::common_type_t<Args...>;
if constexpr (sizeof...(args) == 0) {
return result_type{};
} else {
return (static_cast <result_type>(args) + ...);
}
}
int main()
{
std::cout << "sum(1,2,3) = " << sum(1, 2, 3) << '\n';
std::cout << "sum(1.5,2.5,3) = " << sum(1.5, 2.5, 3) << '\n';
std::cout << "sum() = " << sum() << '\n'; // 输出 0
constexpr int c = sum(4, 5, 6);
static_assert(c == 15, "Compile-time check failed");
}
7. 进阶:支持可变参数 乘法、逻辑与 等
折叠表达式同样可以轻松实现其它聚合:
// 乘法
template<typename... Args>
constexpr auto product(const Args&... args)
{
return (args * ...);
}
// 逻辑与(所有参数为 bool)
template<typename... Args>
constexpr bool all_true(const Args&... args)
{
return (args && ...);
}
8. 小结
- 可变参数模板(
template<typename... Args>)提供了对任意数量参数的抽象。 - 折叠表达式(
(expr op ...))让递归展开变成单行表达式,编译速度更快、代码更简洁。 - 使用
std::common_type_t与static_cast可确保类型安全。 - 标记为
constexpr可以让函数在编译期求值,提升性能。
通过以上技巧,你可以在自己的 C++ 项目中轻松实现各种通用聚合函数,既保持了高性能,又兼顾了代码可读性。祝编码愉快!