C++ 11的可变模版参数是其新增的最强大的特性之一。通过对参数进行了泛化,可以表示从0到任意个数、任意类型的参数。我们知道对于一个模板类来说,通常只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。下面来具体说一下:
可变参数模板与通常的类模板写法相似。声明可变参数模板时需要在typename后面带上省略号 ... 。写法如下:
template<typename T, typename ...Types>
T mul(T head, Types ...rest){
return head * mul<T>(rest...);
}
省略号当放在声明Types左边,表示一个参数包声明,这个参数包声明可以包括0以上任意多个参数;放在右边,如rest...则表示实际参数包,可以将其展开成一个一个独立的参数。我们无法直接获取参数包types中的每个具体参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
首先来看一个最简单的可变模板参数:
#include <iostream>
#include <string>
#include <queue>
#include <vector>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <functional>
using namespace std;
template <class... Types>
void f(Types... args)
{
cout << sizeof...(args) << endl; //输出可变参数的总数量
}
int main()
{
f();
f("aaa");
f(1, 2.0);
system("pause");
return 0;
}
显然输出为0,1,2. 因为主函数中,f()没有传入参数,所以参数包为空,输出的size为0,后面两次调用分别传入1个和2个参数,故输出的size分别为1和2。由于可变模版参数的类型和个数是不固定的,所以我们可以传任意类型和个数的参数给函数f。这个例子只是简单的将可变模版参数的个数打印出来,如果我们需要将参数包中的每个参数打印出来的话就需要通过一些方法了。展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包。下面来看看如何用这两种方法来展开参数包。
第一. 首先是通过递归函数的方法。
这种方法要求必须有明确的递归结束函数。比如下面的函数:
void print(){//递归结束函数
cout << "this is the end!" << endl;
}
template<typename T, typename ...args>
void print(T t, args ... arg){//每次递归,都调用头部参数进行操作
cout << "this is arg: " << t << endl;
print(arg...);
}
int main()
{
print(1, 2, 3, 4, 5);
system("pause");
return 0;
}
/*
输出是:
this is arg: 1
this is arg: 2
this is arg: 3
this is arg: 4
this is arg: 5
this is the end!
*/
我们观察上述的过程,每次调用模板类中的print(args...)函数,都会取其中头部的参数,剩下的参数则进行下一次的递归。也就是首先print(1,2,3,4,5)这个函数会对头部参数1进行操作,之后递归调用print(2,3,4,5)...最后调用到print();这个函数是递归结束函数。也就是展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包arg...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。
所以可变参数模板还可以模拟一个递归的过程,我们看下面的函数:
template <typename T>
T mul(T t){//递归终止函数,当递归的参数只有1个的时候返回结果并终止
return t;
}
//展开函数
template<typename T, typename ...Types>
T mul(T head, Types ...rest){
return head * mul<T>(rest...);
}
int main()
{
cout << mul<int>(1, 2, 3, 4, 5) << endl;
system("pause");
return 0;
}
/*输出结果是120*/
展开相乘函数mul的时候,使用递归的形式,分别用1*mul(2,3,4,5) -> 1 * 2 * mul(3,4,5)->...->1*2*3*4*mul(5) = 120;
第二种方式是逗号表达式:
用递归的办法很好理解,但是他要求必须有明确的递归结束函数。那么有没有办法不用写递归结束的函数呢?这就用到逗号表达式的解决办法了。我们看下面的一个例子:
template<typename T>
void printarg(T t)
{
//do nothing
}
template <class ...Args>
void expand(Args... args)
{
int arr[] = { (printarg(args), 0)... };
for (int i = 0; i < sizeof(arr)/4; i++){
cout << arr[i] << " ";
}
cout << endl;
}
int main()
{
expand(1, 2, 3, 4, 5);
system("pause");
return 0;
}
上面这个函数中的:
int arr[] = { (printarg(args), 0)... };
这句话实际上起到的是初始化一个长度为args参数数量的int形数组。对于(printarg(args), 0)... 作为可变参数模板,可以展开写成:
(printarg(args[0]), 0),(printarg(args[1]), 0),(printarg(args[2]), 0) ... (printarg(args[n]), 0).而对于逗号表达式,c = (a,b) 就是按照顺序执行c = b,所以最终上面式子的结果就是0,0,0,0...0。再加上外层的大括号作为初始化,也就实际上展开为:
int arr[] = {0,0,...0}一共n个0了。在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数(虽然这个函数什么也没做),也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展示参数包的过程。