在 C++11 及之后的标准中,变参模板(Variadic Templates)让我们能够编写可以接受任意数量参数的函数或类。下面给出一个最常见的用例:实现一个函数 sum,可以对任意数量的整数(或更通用的数值类型)求和。我们将逐步解释实现思路、关键代码以及使用示例。
1. 基础概念
1.1 变参模板(Variadic Templates)
变参模板使用 ...(ellipsis)表示可以接受任意数量的模板参数。形式上有两种:
- 参数包(Parameter Pack):
typename... Args,表示类型参数包。 - 非类型参数包:
int... Ns,表示整数参数包。
使用时可以展开(展开为列表):
template <typename... Args>
void foo(Args... args) {
// 这里 args 是一个参数包,可以展开
}
展开可以用递归方式实现,也可以用现代 C++11/14/17 的折叠表达式(fold expression)简化。
1.2 折叠表达式(Fold Expressions)
折叠表达式是 C++17 新增的特性,用于在一个表达式中对参数包执行递归操作。格式如下:
- 左折叠:
( expr op ... )例如(args + ...)计算 args 的总和。 - 右折叠:
(... op expr)例如(... + args)。
折叠表达式可以极大简化变参模板的实现。
2. 用折叠表达式实现 sum
2.1 基本实现
#include <iostream>
#include <type_traits>
// 检查所有参数是否都为数值类型
template <typename... Args>
constexpr bool all_arithmetic_v = (std::is_arithmetic_v <Args> && ...);
// sum 函数
template <typename T, typename... Args>
auto sum(T first, Args... args) {
static_assert(all_arithmetic_v<T, Args...>,
"所有参数必须是数值类型");
if constexpr (sizeof...(args) == 0) {
return first;
} else {
return first + sum(args...); // 递归方式
}
}
// 版本 2:使用折叠表达式
template <typename T, typename... Args>
auto sum_fold(T first, Args... args) {
static_assert(all_arithmetic_v<T, Args...>,
"所有参数必须是数值类型");
return first + (... + args); // 右折叠
}
2.2 说明
all_arithmetic_v使用折叠表达式检查所有参数是否都是算术类型(int、float 等),防止误用。sum采用递归实现,兼容 C++11/14。递归终止于无可展开参数时直接返回first。sum_fold采用折叠表达式实现,语义更简洁、性能更好。
2.3 使用示例
int main() {
std::cout << sum(1, 2, 3, 4) << std::endl; // 输出 10
std::cout << sum_fold(1.5, 2.5, 3.0) << std::endl; // 输出 7.0
std::cout << sum(5) << std::endl; // 输出 5
// std::cout << sum("a", "b"); // 编译错误,类型不匹配
}
3. 进阶:支持任意数值类型的混合求和
如果想让 sum 既支持整数、浮点数,又能自动做类型提升(例如 int + double 结果为 double),可以让函数返回 decltype(auto) 并使用 std::common_type_t:
template <typename... Args>
auto sum_mixed(Args... args) {
static_assert(all_arithmetic_v<Args...>,
"所有参数必须是数值类型");
using Common = std::common_type_t<Args...>;
return (Common{0} + (... + Common{args})); // 强制转换为公共类型后相加
}
使用示例:
std::cout << sum_mixed(1, 2.5, 3) << std::endl; // 输出 6.5
4. 性能与可读性
- 折叠表达式 生成的代码在编译阶段展开,产生与手写循环相同的机器码。相比递归实现,它更简洁,且避免了多次函数调用。
- 对于极大量参数(> 10k),递归实现可能导致编译时间过长。折叠表达式几乎不受参数数量影响。
5. 小结
通过变参模板和折叠表达式,我们可以轻松实现一个可以接受任意数量整数(或更广泛数值类型)并返回其和的函数。核心思路是:
- 用
typename... Args接受参数包。 - 用
static_assert检查参数合法性。 - 用折叠表达式
(first + (... + args))或(... + args)完成求和。
这段代码既简洁又兼容 C++17 及以上标准,展示了现代 C++ 的强大表达能力。祝你编码愉快!