深入理解C++11(十九)(摘录)

深入理解C++11(十九)

可变参数模板类
可变参数模板类是一个带可变模板参数的模板类,第1章中介绍的std::tuple就是一个可变模板类,它的定义如下:

template< class... Types >
class tuple;

这个可变参数模板类可以携带任意类型任意个数的模板参数:

std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, "");

可变参数模板的模板参数个数可以为0,所以下面的定义也是也是合法的:
std::tuple<> tp;

可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包需要通过模板特化或继承方式去展开,展开方式比可变参数模板函数要复杂。

模板递归和特化方式展开参数包
可变参数模板类的展开一般需要定义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>struct Sum<Last>
{
enum { value = sizeof (Last) };
};

这个sum类的作用是在编译期计算出参数包中参数类型的size之和,通过
sum<intdoubleshort>::value就可以获取这3个类型的size之和为14

这是一个简单的通过可变参数模板类计算的例子,可以看到一个基本的可变参数模板应用类由三部分组成,第一部分是:

template<typename... Args> struct sum

它是前向声明,声明这个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 (First) };
}

通过这个特化的类来终止递归:

template<typename First, typename... Args>struct sum;

这个前向声明要求sum的模板参数至少有一个,因为可变参数模板中的模板参数可以有0个,有时0个模板参数没有意义,就可以通过上面的声明方式来限定模板参数不能为0个。

上面这种3段式的定义也可以改为两段式的,可以将前向声明去掉,这样定义:

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) };
};

上面的方式只要一个基本的模板类定义和一个特化的终止函数就行了,
而且限定了模板参数至少有一个。

递归终止模板类可以有多种写法,比如上例的递归终止模板类还可以这样写:

template<typename First, typename Last>
struct sum<First, Last>
{
enum{ value = sizeof(First) +sizeof(Last) };
};

在展开到最后两个参数时终止。
还可以在展开到0个参数时终止:
template<>struct sum<> { enum{ value = 0 }; };

还可以使用std::integral_constant来消除枚举定义value(关于std::integral_constant内容可以参考3.1.1节)。利用std::integral_constant可以获得编译期常量的特性,可以将前面的sum例子改为这样:

// 前向声明
template<typename... Args>structsum;
// 基本定义
template<typename First, typename... Rest>
struct sum<First, Rest...> : std::integral_constant<int, sum<First>::value
+sum<Rest...>::value>
{
};
// 递归终止
template<typenameLast>
struct sum<Last> : std::integral_constant<int, sizeof(Last)>
{
};
sum<int,double,short>::value;// 值为14

继承方式展开参数包
上一节介绍了可变参数模板类参数包展开的一种方式:通过模板递归和模板特化的方式展开。还有另外一种方式:通过继承和特化的方式展开。下面的例子就是通过继承的方式去展开参数包。

// 整型序列的定义
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;
cout <<typeid(T).name() << endl;
return 0;
}

其中,MakeIndexes的作用是为了生成一个可变参数模板类的整数序列,
最终输出的类型是struct IndexSeq<012>

MakeIndexes继承于自身的一个特化的模板类,这个特化的模板类同时也在展开参数包,这个展开过程是通过继承发起的,直到遇到特化的终止条件展开过程才结束。MakeIndexes<1,2,3>::type的展开过程如下:

MakeIndexes<3, IndexSeq<>> : MakeIndexes<2, IndexSeq<2>>{}
MakeIndexes<2, IndexSeq<2>> : MakeIndexes<1, IndexSeq<1, 2>>{}
MakeIndexes<1, IndexSeq<1, 2>> : MakeIndexes<0, IndexSeq<0, 1, 2>>
{
typedef IndexSeq<0, 1, 2> type;
}

通过不断继承递归调用,最终得到整型序列IndexSeq<012>

上面代码生成的IndexSeq<0,1,2>序列是升序的序列,如果需要得到降序的序列,只需要修改Indexes…的生成顺序,将MakeIndexes修改成如下代码:

template<int N, int... Indexes>
struct MakeIndexes : MakeIndexes<N - 1, Indexes..., N - 1> {};

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...>
struct IndexSeq{};
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...>;
};
template<int ... Indexes, typename ... Args>
void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup){
print(std::get<Indexes>(tup)...); // 将tuple转换为函数参数,再调用方法1
}
template<typename ... Args>
void print(Args... args){
print_helper(typename MakeIndexes<sizeof... (Args)>::type(),
std::make_tuple(args...));
}

上面代码在将可变模版参数转化为tuple的同时,生成了可变tuple中元素对应的索引位置的整型序列,在print_helper函数中展开这个整型序列时可以获取当前的索引,然后就可以通过这个索引来获取tuple中的元素了,当整型序列展开完毕时,可变模版参数就生成了,它就是函数的入参,这时再调用print函数就可以打印参数了。这里用到的技巧是将可变模版参数转换为tuple,然后又将tuple转换为可变模版参数,将tuple转换为可变模版参数需要借助整型序列IndexSeq。

可变参数模板类比可变参数模板函数要复杂一些,功能也更强大一些,因为可变参数模板类可以带有状态,可以通过一些type_traits在编译期对类型做一些判断、选择和转换等操作(读者3.3节中看到可变参数模板和type_traits结合起来的一些实例)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值