一个可变参数模板函数的定义如下:
template <class... T>
void f(T... args)
{
cout<<sizeof...(args)<<endl; //打印变参的个数
}
f(); //0
f(1,2); //2
f(1,2.5,""); //3
通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归,来看下面的例子。
1 #include <iostream> 2 using namespace std; 3 4 void print() 5 { 6 cout << "empty" << endl; 7 } 8 9 template <class T,class ...Args> 10 void print(T head,Args... rest) 11 { 12 cout << "parameter "<<head << endl; //打印变参的个数 13 print(rest...); 14 } 15 16 int main() 17 { 18 print(1, 2, 3, 4); 19 return 0; 20 }
输出结果为:
递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须有一个重载的递归终止函数,即必须有一个同名的终止函数来终止递归,这样会感觉稍有不便。其实还有一种方法可以不通过递归方式来展开参数包。比如:
1 #include <iostream> 2 using namespace std; 3 4 template <class T> 5 void printarg(T t) 6 { 7 cout << t << endl; 8 } 9 10 template <class ...Args> 11 void expand(Args... args) 12 { 13 int arr[] = { (printarg(args),0)... }; 14 } 15 16 int main() 17 { 18 expand(1, 2, 3, 4); 19 return 0; 20 }
也可以打印1,2,3,4四个数字。
可变参数模板类是一个带可变模板参数的模板类,第1章中介绍的std::tuple就是一个可变模板类,它的定义如下:
template<class... Types>
class tuple;
这个可变参数模板类可以携带任意类型任意个数的模板参数:
std::tuple<int> tp1=std::make_tuple(1);
std::tuple<int, double> tp2=std::std::make_tuple(1, 2.5);
std::tuple<int, double,string> tp2=std::std::make_tuple(1, 2.5,"test");
可变参数模板的模板参数个数可以为0,所以下面的定义也是合法的:
std::tuple<> tp;
可变参数模板类的展开一般需要定义2~3个类,包括类声明和特化的模板类。如下方式定义了一个基本的可变参数模板类:
template<typename... Args>structSum;
template<typename First, typename... Rest>
struct Sum<First, Rest...>
{
enum { value = Sum<First>::value +Sum<Rest...>::value);
}
template<typename Last>struct Sum<Last>
{
enum { value = sizeof (Last)};
}
这个sum类的作用是在编译期计算出参数包中参数类型的size之和,通过sum<int, double, short>::value就可以获取这3个类型的size之和为14。这是一个简单的通过可变参数模板类计算的例子,可以看到一个基本的可变参数模板应用类由三部分组成,第一部分是:
template<typename... Args> struct sum
它是前向声明,声明这个类是一个可变参数模板类;第二部分是类的定义:
template<typename First, typename... Rest>
struct sum<First, Rest...>
{
enum { value = Sum<First>::value +Sum<Rest...>::value);
}
它定义了一个部分展开的可变参数模板类,告诉编译器如何递归展开参数包。第三部分是特化的递归终止类:
template<typename Last>struct Sum<Last>
{
enum { value = sizeof (Last)};
}
上面的这种3段式的定义也可以改为两段式,去掉前向声明:
template<typename First, typename... Rest>
struct sum
{
enum { value = Sum<First>::vlue+Sum< Rest...>::value);
};
template<typename Last>
{
enum { value = sizeof(Last); };
}
除了通过模板递归和模板特化的方式展开,还有另外一种方式:通过继承和特化的方式展开。下面的例子就是通过继承的方式去展开参数包
1 //整数序列的定义 2 template<int...> 3 struct IndexSeq{}; 4 5 //继承方式,开始展开参数包 6 template<int N, int... Indexes> 7 struct MakeIndexes : MakeIndexes<N-1, N-1, Indexes...> {}; 8 9 //模板特化,终止展开参数包的条件 10 template<int... Indexes> 11 struct MakeIndexes<0, Indexes...> 12 { 13 typedef IndexSeq<Indexes...> type; 14 }; 15 16 int main() 17 { 18 using T = MakeIndexes<3>::type; 19 cout<<typeid(T).name()<<endl; 20 return 0; 21 }
最终输出的类型是struct IndexSeq<0, 1, 2>