C++11新特性之可变参数模板

在c++11之前,类模板和函数模板只能含有固定数量的模板参数,c++11增加了可变模板参数特性,即允许模板定义中包含0到任意个模板参数。
声明可变参数模板时,要在typename或class后面加上省略号"…"。
省略号的作用:
(1)声明一个参数包,这个参数包中可以包含0到任意个模板参数;
(2)在模板定义的右边,可以将参数包展开成一个一个独立的参数。
可变参数模板包括以下两种情况:

  1. 可变参数模板函数
    定义如下:
template<class... T>
void f(T... args)
{
    cout << sizeof...(args) << endl;   //sizeof...(args) 取得可变参数的个数
}
f();
f(1, 2);
f(1, 2.3, "hello");

注意:参数包可以包含0个或者多个参数,如果需要用参数包中的参数,则一定要将参数包展开。有两种展开参数包的方法:(1)通过递归的模板函数来展开参数包;(2)通过逗号表达式和初始化列表方式展开参数包。

展开参数包的方式:
(1)递归函数方式
需要提供一个参数包展开的函数和一个递归终止函数,二者同名。递归终止函数的参数可以为0,1,2或者多个(一般用到0个或1个),当参数包中剩余的参数个数等于递归终止函数的参数个数时,就调用递归终止函数,则函数终止。

#include<iostream>
using namespace std;
void print()		// 递归终止函数
{
   cout << "empty" << endl;
}
template<class T, class... Args>
void print(T head, Args... rest)		// 展开函数
{
   cout << "parameter = " << head << endl;
   print(rest...);
}
int main(){
   print(1,2,3,4);		// 当调用print(1,2,3,4)时,先后调用print(2,3,4), 								print(3,4),print(4),最终调用print()终止
   return 0;
}

如果递归终止函数为:

template<typename T>
void print(T a)
{
    cout << a << endl;
}

则函数调用到 print(4)就终止。

还可通过type_traits方式来定义同名但不同参数的函数,分别实现递归终止和展开函数,从而展开参数包。

template<typename I = 0, typename Tuple>		// 相当于递归终止函数
typename std::enable_if<I==std::tuple_size<Tuple>::value>::type printtp(Tuple t)
{    
}
template<typename I = 0, typename Tuple>		// 相当于展开函数
typename std::enable_if<I < std::tuple_size<Tuple>::value>::type printtp(Tuple t)
{
    std::cout << std::get<I>(t) << std::endl;   //打印出元组中的第i个
    printtp<I+1>(t);
}
template<typename... Args>
void print(Args... args)
{
    printtp(std::make_tuple(args...);
}

(2)初始化列表方式
递归函数展开参数包,需要有一个同名的终止函数来终止递归,可以使用初始化列表的方式避免多定义一个同名的终止函数。

template<typename T>
void printarg(T a)
{
    cout << a << endl;
}

template<class...Args>
void expand(Args... args)
{
int arr[] = {(printarg(args), 0)...};	
//或者改进为 std::initializer_list<int>{(printarg(args), 0)...};
}
expand(1,2,3,"hello");

上式中,{(printarg(args), 0)…}会被展开为{(printarg(arg1), 0), (printarg(arg2), 0), (printarg(arg3), 0)}
这种展开参数包的方式,不需要通过递归终止函数,而是直接在expand函数体内展开,printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种处理方式的关键是逗号表达式。
逗号表达式会按顺序执行前面的表达式,例如:在d = (a = b, c)中,b先赋值给a,接着括号中的逗号表达式返回c的值,因此d被赋值为c。
c++11中使用列表初始化方法来初始化一个边长的数组,可以使用int arr[] = {args…};其中args为一个变长的参数集合。

template<typename... Args>
void print(Args... args)
{
   int arr[] = { (args, 0)... }; 
}

上式中,args…代表所有的可变参数集合,可以将args和…分开,此时args表示…中每一个参数。

还可以通过lambda表达式来改进上述的列表初始化方式:

template<typename... Args>
void expand(Args... args)
{
    std::initializer_list<int>{([&]{cout << args << endl;}(), 0)...};
}
  1. 可变参数模板类
    定义一个可变参数模板类tuple,这个可变参数模板类可以携带任意类型任意个数的模板参数。
template<class... Types>
class tuple;
std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1,2.4);
std::tuple<> tp;

可变参数模板类的参数展开:
(1)模板递归和特化方式展开参数包
可变参数模板类的展开一般需要2~3个类,包括类声明和特化的模板类。如下方式定义了一个基本的可变参数模板类:

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>		 //递归终止类,模板参数不一定为1个,可能为0							个或者2个
struct Sum<Last>  //Sum类的作用是在编译期计算参数包中参数类型的size之和
{
    enum{value = sizeof(Last)};
};

或者可通过std::integral_constant来修改一下:

template<typename... Args>      //前置声明
struct Sum;
 
template<typename First, typename... Rest>  //递归定义
struct Sum<First, Rest...>: std::integral_constant<int, std::integral_constant<int, sizeof(First)>::value + Sum<Rest...>::value
{
};
 
template<typename Last>		 //递归终止
struct Sum<Last>: std::integral_constant<int, sizeof(Last)>
{
};

(2)继承方式展开参数包

template<int...>		//整型序列的定义
struct IndexSeq{};

template<int N, int ... Indexes>		//继承方式,开始展开参数包
struct MakeIndexes: MakeIndexes<N -1, N -1, Indexes...>{};
 
template<int... Indexes>		//模板特化,终止展开参数包的条件
struct MakeIndexes<0, Indexes...>
{
    typedef IndexSeq<Indexes...> type;
};
int main()
{
    using T = MakeIndexes<3>::type;  //输出为 struct IndexSeq<0,1,2>
    cout << typeid(T).name() << endl;
    return 0;
};		//MakeIndexes如果不通过继承递归方式生成,可以通过using来实现

template<int N, int ...Indexes>
struct MakeIndexes{
    using type = MakeIndexes<N-1,N-1,Indexes>::type;
};
 
template<int...Indexes>
struct MakeIndexes<0, ...Indexes>{
    using type =  IndexSeq<Indexes...>;
};

可以使用上述的IndexSeq来展开并打印可变模板参数,例如:
template<int ...Indexes, typename ...Args>
void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup)
{
    print(std::get<Indexes>(tup)...);
}
 
template<typename ...Args>
void print(Args... args)
{
print_helper(typename MakeIndexes<sizeof...(args)>::type(),std::make_tuple (args...));
}

原文转自:https://www.cnblogs.com/gtarcoder/p/4810614.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值