模板-可变参数模板展开
一、可变参数模板
C++11增强了模板功能,在C++11之前,类模板和函数模板只能含有固定数量的模板参数,现在C++11中的新特性可变参数模板允许模板定义中包含0到任意个模板参数。可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“…”。
省略号的作用有两个:
- 声明一个参数包,这个参数包中可以包含0到任意个模板参数。
- 在模板定义的右边,可以将参数包展开成一个一个独立的参数。
二、可变参数模板函数
1、定义
template <class... T>
void f(T... args){
cout << sizeof...(args) << endl; //打印变参的个数
}
f(); // 0
f(1, 2); // 2
f(1, 2.5, ""); // 3
2、模板参数包展开方式
1)递归函数方式展开参数包
通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的,来看下面的例子。
#include <iostream>
using namespace std;
// 递归终止函数
void print(){
cout << "empty" << endl;
}
/*
或者
template <class T>
void print(T t){
cout << t << endl;
}
或者
template<typename T,typename T1, typename T2>
void print(T t, T1 t1){
cout<<t<<""<<t1 <<endl;
}
或者
void print(T t, T1 t1, T2 t2){
cout<<t<<""<<t1<<""<<t2<<endl;
}
*/
// 展开函数
template <class T, class ...Args>
void print(T head, Args... rest){
cout << "parameter " << head << endl;
print(rest...);
}
int main(void){
print(1,2,3,4);
return 0;
}
上例会输出每一个参数,直到为空时输出empty。有两个函数,一个是递归函数,另一个是递归终止函数,参数包Args…在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数fun终止递归过程。
递归调用的过程如下:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
print();
/*
或者
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
*/
还可以通过std::tuple和std::enable_if方式:
template<std::size_t I = 0, typename Tuple>
typename std::enable_if<I == std::tuple_size<Tuple>::value>::type printtp(Tuplet){
}
template<std::size_t I = 0, typename Tuple>
typename std::enable_if<I < std::tuple_size<Tuple>::value>::type printtp(Tuplet){
std::cout << std::get<I>(t) << std::endl;
printtp<I + 1>(t);
}
template<typename... Args>
void print(Args... args){
printtp(std::make_tuple(args...));
}
在上面的代码中,通过std::enable_if来选择合适的重载函数打印可变模版参数,基本思路是先将可变模版参数转换为tuple,然后通过递增参数的索引来选择print函数,当参数的索引小于总的参数个数时,会不断取出当前索引位置的参数并输出,当参数索引等于总的参数个数时终止递归。
2)逗号表达式和初始化列表方式展开参数包
递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须有一个重载的递归终止函数,即必须有一个同名的终止函数来终止递归,这样会感觉稍有不便。有没有一种更简单的方式,直接展开参数包呢?其实还有一种方法可以不通过递归方式来展开参数包,这种方式需要借助逗号表达式和初始化列表。
template <class ...Args>
void expand(Args... args){
std::initializer_list<int>{(printarg(args), 0)...};
}
或者
template<typename... Args>
void expand (Args... args){
std::initializer_list<int>{([&]{cout << args << endl; }(), 0)...};
}
{(printarg(args),0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0),(printarg(arg3),0),etc…)
三、可变参数模板类
2、参数包展开方式
1)模板递归和特化方式展开参数包
可变参数模板类的展开一般需要定义2~3个类,包括类声明和特化的模板类。如下方式定义了一个基本的可变参数模板类,这个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) };
};
/*
或者 最后2个参数结束
template<typename First, typename Last>
struct sum<First, Last>{
enum{ value = sizeof(First) +sizeof(Last) };
};
或者 最后0个参数结束
template<>
struct sum<> {
enum{ value = 0 };
};
*/
- 也可改成两段式
//定义
template<typename First, typename... Rest>
struct sum{
enum { value = Sum<First>::value+Sum< Rest...>::value };
};
//特化
template<typename Last>
struct sum<Last>{
enum{ value = sizeof(Last) };
};
还可以通过std::integral_constant来消除枚举定义value,利用std::integral_constant可以获得编译期常量的特性,可以将前面的sum例子改为这样:
// 前向声明
template<typename... Args>
struct sum;
// 基本定义
template<typename First, typename... Rest>
struct sum<First, Rest...> : std::integral_constant<int, sum<First>::value + sum<Rest...>::value>
{
};
// 递归终止
template<typename Last>
struct sum<Last> : std::integral_constant<int, sizeof(Last)>{
};
sum<int,double,short>::value;// 值为14