C++程序员肯定接触过可变参数,毕竟我们都用过printf,但是直到C++11时C++才推出真正意义上的可变参数。
可变参数通过可变参数模板实现,在C++11中通过递归调用,借助编译器生成多个递归的特化函数,调用时依次展开。C++17中引入折叠表达式,简化了可变参数的实现方式,但仍经由编译器生成了对应的特化函数。
基本概念
//类型形参包
template<typename... Args> // Args 是一个类型形参包
class Tuple {
// ...
};
template<typename... Args> // Args 是一个类型形参包
auto sum(Args... args) {
// ...
};
//非类型形参包示例
template<int... Values> // Values 是一个非类型形参包
struct Sum {
//....
};
由上文知道,可变参数存在两种实现方式,递归展开和折叠表达式。接下来将分别说明如下:
递归展开
可变参数在C++17前仅支持递归展开,通过逐步处理形参包直到其为空。示例见如下的print函数,
// 特殊化处理0参数情况
void print() {
// 可选:处理无参数时的逻辑,比如打印结束符
std::cout << "\n" ;
}
//终止条件
template<typename T>
auto print(T a)
{
std::cout << a << "\n";
}
//变参模板
template<typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << ", ";
print(args...); // 递归调用,形参包展开
}
// 使用示例
print(1, 2, 3, 4, 5);
在上面的代码中,
折叠表达式
C++17引入了更简洁的形参包展开语法,折叠表达式(Fold Expressions):
template<typename... Args>
auto sum(Args... args)
{
if constexpr (sizeof...(Args) )//形参包非空
{
return (args + ...); // 折叠表达式,等价于 args[0] + args[1] + ... + args[N]
}
else
{
// 没有参数,处理空情况
std::cout << "No arguments provided." << std::endl;
}
}
如上代码借助sizeof...方法获得形参包中形参数量,区别处理形参包为空——0个参数的场景。
注意事项
可变参数由于其可输入任意长度参数,方便了用户,但其也存在自身的劣势,所以在使用时需要注意:
结论
可变参数模板是C++现代编程不可或缺的一部分,本文结合代码分别介绍了递归调用和折叠表达式两种实现方式。由于多参数时折叠表达式生成的模板特化函数的数量远少于递归生成的特化函数数量(5个参数的递归展开将产生5个模板特化,而折叠表达式只有1个特化)同时编译器也基本都支持C++17了,建议使用折叠表达式的实现方式.