模板-可变参数模板展开

模板-可变参数模板展开

一、可变参数模板

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
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值