可变模板参数
C++11的新特性–可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。
可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“…”。比如我们常常这样声明一个可变模版参数:template<typename…>或者template<class…>,一个典型的可变模版参数的定义是这样的:
template <class... T>
void f(T... args);
上面的可变模版参数的定义当中,省略号的作用有两个:
- 声明一个参数包T… args,这个参数包中可以包含0到任意个模板参数;
- 在模板定义的右边,可以将参数包展开成一个一个独立的参数。
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
展开参数包
我们可以使用sizeof关键字来查看参数包中元素的个数
尽管如此,但是却不能使用[]
进行访问,这是因为sizeof()是在编译阶段就确定的值, 但是参数是一个范型,这个在运行时才会确定,所以不能直接进行访问。
递归函数方式展开参数包
通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的。
之所以需要递归终止函数是因为当递归出入的参数包为空的时候,print无法自己调用自己。
另外,当较泛化和较特化的模板函数同时存在的时候,最终程序会执行较特化的那一个。
基于这种情况,std::max函数只可以返回两个数的较大者,如果多个数,就可以通过不定参数的模板来实现:
#include <iostream>
template <typename T>
T my_max(T value) {
return value;
}
template <typename T, typename... Types>
T my_max(T value, Types... args) {
return std::max(value, my_max(args...));
}
int main()
{
std::cout << my_max(1, 5, 8, 4, 6) << std::endl;
return 0;
}
或者实现一个将多个数相加的函数:
#include <iostream>
template<typename T>
T sum(T t)
{
return t;
}
template<typename T, typename ... Types>
T sum(T first, Types ... rest)
{
return first + sum<T>(rest...);
}
int main()
{
std::cout<<sum(1, 2, 3, 4)<<std::endl; //10
}
逗号表达式展开参数包
递归函数展开参数包是一种标准做法,但也有一个缺点,就是必须要一个同名的重载终止函数来终止递归,这样可能会感觉稍有不便。还有一种方法可以不通过递归方式来展开参数包,这种方式需要借助逗号表达式和初始化列表。
expand函数中的逗号表达式:(printarg(args), 0)
,先执行printarg(args)
,再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组,{(printarg(args), 0)...}
将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... )
,最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]
。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个打印函数: