可变参数模版是C++11标准引入的一个新特性,顾名思义就是类模板或函数模版的形参个数是可变的。
基本语法:可变参数模版的语法和可变参数函数有一点相似之处就是都使用了省略号
template<class ...Args> //class ...Args是类型模版形参包
void foo(Args ...args) {} //Args ...args是函数形参包
template<class ...Args>
class bar {
public:
bar(Args ...args) { //Args ...args是形参包展开
foo(args...);
}
}
并不是所有的情况都能进行包展开的,包展开需要特定的场景。
1、表达式列表
2、初始化列表
3、基类描述
4、成员初始化列表
5、函数参数列表
6、模版参数列表
7、动态异常列表(C++17已不再使用)
8、lambda表达式捕获列表
9、sizeof运算符
10、对其运算符
11、属性列表
举例:
//普通函数模版
template<class T, class U>
T baz(T t, U u) {
cout<<t<<:<<u<<endl;
return t;
}
//可变模板参数的函数模版
template<class ...Args>
void foo(Args ...args) {}
//可变参数的类模板
template<class ...Args>
class bar {
public:
bar(Args ...args) {
foo(baz(&args, args)...);
}
}
int main() {
bar<int, double, unsigned int> b(1, 5.0, 8);
}
// 模版实例化后相当于
class bar {
public:
bar(int a1, double a2, unsigned int a3) {
foo(baz(&a1, a1), baz(&a2, a2), baz(&a3, a3));
}
}
除了直接进行包展开,C++11还提供了获取包展开后参数个数的方法。那就是sizeof...运算符,用于获取形参包中形参的个数,返回的类型是size_t。
#include <iostream>
using namespace std;
template<class ...Args>
void foo(Args ...args) {
cout<<"foo sizeof...(args) = "<<sizeof...(args)<<endl;
}
template<class ...Args>
class bar {
public:
bar() {
cout<<"bar sizeof...(Args) = "<<sizeof...(Args)<<endl;
}
};
int main()
{
foo();
foo(1, 2, 3, 4, 5);
bar<> b1;
bar<int, int, double> b2;
return 0;
}
运行结果:
可变参数模版的一个很常见的用法就是递归计算
#include <iostream>
using namespace std;
template<class T>
T sum(T args) {
return args;
}
template<class T1, class ...Args>
auto sum(T1 arg1, Args ...args) {
return arg1 + sum(args...);
}
int main()
{
cout<<sum(1, 3.2, 5)<<endl;
return 0;
}
从C++17开始,标准引入了更加友好的方法完成上面的工作。这对于只有常规需求的程序员来说绝对是一个好消息。这个新方法叫折叠表达式。
#include <iostream>
using namespace std;
template<class ...Args>
auto sum(Args ...args) {
return (args + ...); //编译器会根据这句代码进行包展开,每个参数直接使用+连接起来,最终结果就是计算这些参数的和
}
int main()
{
cout<<sum(1, 5.2, 5)<<endl;
return 0;
}
折叠表达式展开的4条规则:
一元向左折叠、二元向左折叠、一元向右折叠、二元向右折叠
对于一元和二元向左折叠编译器总是优先结合左边的展开参数;
对于一元和二元向右折叠编译器总是优先结合右边的展开参数;
而一元和二元的区别只是有没有初始化参数而已。
#include <iostream>
using namespace std;
template<class ...Args>
auto sum(Args ...args) {
return (args + ...);
}
int main()
{
cout<<sum(string("a"), "bb", "cc")<<endl; //编译失败
// 这段代码是向右折叠,先计算("bb" + "cc"),字符串常量是无法直接相加的,所以编译失败
// 相当于代码 string("a") + ("bb" + "cc")
return 0;
}
如果将这段代码中向右折叠改为向左折叠,这段代码就可以编译通过了。
另外在使用一元折叠时还需要主要空参数的情况,还是以sum函数为例,如果在调用函数的时候没有任何参数,那么就无法确定结果的返回类型,所以一定会编译失败。
总结:可变参数模版这个特性可以说是新标准中最重要的模版相关的特性,丰富的包展开和折叠表达式功能也让模板元编程代码变得更加容易理解