可变参数模板的概念
可变参数模板是C++11新增的最强大的特性之一,它对参数高度泛化,能够让我们创建可以接受可变参数的函数模板和类模板。
- 在C++11之前,类模板和函数模板中只能包含固定数量的模板参数,可变模板参数无疑是一个巨大的改进,但由于可变参数模板比较抽象,因此使用起来需要一定的技巧。
- 在C++11之前其实也有可变参数的概念,比如printf函数就能够接收任意多个参数,但这是函数参数的可变参数,并不是模板的可变参数。
可变参数模板的定义方式
template<class …Args>
返回类型 函数名(Args… args)
{
//函数体
}
注意:
- 模板参数Args前面有省略号,代表它是一个可变模板参数,我们把带省略号的参数称为参数包,参数包里面可以包含0到N 个模板参数,而args则是一个函数形参参数包。
- 模板参数包Args和函数形参参数包args的名字可以任意指定,并不是说必须叫做Args和args。
现在调用Show函数时就可以传入任意多个参数了,并且这些参数可以是不同类型的。比如:
template<class ...Args>
void Show(Args... args)
{}
int main()
{
Show();
Show(1);
Show(1, 'A');
Show(1, 'A', string("hello"));
return 0;
}
我们可以在函数模板中通过sizeof计算参数包中参数的个数。比如:
template<class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl; //获取参数包中参数的个数
}
参数包的展开方式
递归展开参数包
递归展开参数包的方式如下:
- 给函数模板增加一个模板参数,这样就可以从接收到的参数包中分离出一个参数出来。
- 在函数模板中递归调用该函数模板,调用时传入剩下的参数包。
- 如此递归下去,每次分离出参数包中的一个参数,直到参数包中的所有参数都被取出来。
比如我们要打印调用函数时传入的各个参数,那么函数模板可以这样编写:
//递归终止函数
void ShowList()
{
cout << endl;
}
//展开函数
template<class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " "; //打印分离出的第一个参数
ShowList(args...); //递归调用,将参数包继续向下传
}
这样一来,当递归调用ShowList函数模板时,如果传入的参数包中参数的个数为0,那么就会匹配到这个无参的递归终止函数,这样就结束了递归。
我们也可以将展开函数和递归调用函数的函数名改为ShowListArg,然后重新编写一个ShowList函数模板,该函数模板的函数体中要做的就是调用ShowListArg函数展开参数包。比如:
//递归终止函数
void ShowListArg()
{
cout << endl;
}
//展开函数
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{
cout << value << " "; //打印传入的若干参数中的第一个参数
ShowListArg(args...); //将剩下参数继续向下传
}
//供外部调用的函数
template<class ...Args>
void ShowList(Args... args)
{
ShowListArg(args...);
}
逗号表达式展开参数包
通过逗号表达式展开参数包
- 逗号表达式会从左到右依次计算各个表达式,并且将最后一个表达式的值作为返回值进行返回。
- 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值。
- 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式。
这样一来,在执行逗号表达式时就会先调用处理函数处理对应的参数,然后再将逗号表达式中的最后一个整型值作为返回值来初始化整型数组。比如:
//处理参数包中的每个参数
template<class T>
void PrintArg(const T& t)
{
cout << t << " ";
}
//展开函数
template<class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗号表达式
cout << endl;
}
说明一下:
我们这里要做的就是打印参数包中的各个参数,因此处理函数当中要做的就是将传入的参数进行打印即可。
可变参数的省略号需要加在逗号表达式外面,表示需要将逗号表达式展开,如果将省略号加在args的后面,那么参数包将会被展开后全部传入PrintArg函数,代码中的{(PrintArg(args), 0)…}将会展开成{(PrintArg(arg1), 0), (PrintArg(arg2), 0), (PrintArg(arg3), 0), etc…}。