参考自:深入理解C++11
变长模版:
变长函数和变长的模版参数
变长函数:
double
sum(int n, ...)//求n个double数据之和
{
double
sum = 0;
va_list
args;//接受输入数据的数据结构,需声明stdarg.h,
va_start(args,
n); //初始化数据
while (n>0)
{
sum += va_arg(args,
double); //将args中的数据一一取出,每隔sizeof(double)取一次数,再求和
--n;
}
va_end(args);
return
sum;
}
变长的模版参数:
pair<int,
double> PairNum
std::tuple<>double,
char, int, std::string> collections
变长模版:模版参数包和函数参数包
模版参数包:
变长类模版:
template<typename...Elements>class
tuple;
在标识符Elements之前使用了省略号…来表示该参数是变长的。Elements被称作三一个“模版参数包”。这样tuple 就可以接受任意多个参数作为模版参数。其实例化的tuple模版类:
tuple<int ,char,double>
编译器则可以将多个模版参数打包成大哥的模版参数包Elements,即Elements在进行模版推导的时候,就是一个包含int,char,double三种类型类型集合。
也可以声明非类型的模版参数包,如:
template<int …A>classNonTypeVariadicTemplate{}
NonTypeVariadicTemplate<1,2,3>ntvt;
就是定义了个:
template<int,int,int>class NonTypeVariadicTemplate{}
NonTypeVariadicTemplate<1,2,3>ntvt;
一个模版参数包在模版推导的时候会被认为是模版的单个参数。为了使用模版参数包,我们总是需要将其解包(Unpack)。在C++11中,这通常需要通过一个名为包扩展的表达式来完成:
template <typename …A>classTemplate:private B<A…>{};
式中的表达式A…就是一个包扩展。这样参数包会在包扩展的位置展开为多个参数。如:
template<typename
T1, typename
T2>class
B{};
template<typename ...A>class
Template :private
B<A...>{};
Template<X,
Y> xy;
这样,我们为类模版声明了一个参数包A,而使用参数包A…则是在Template的私有基类B<A…>中,那么最后一个表达式就声明了一个基类为B<X,Y>的模版类Template<X,Y>的对象xy。基类B总是接受两个参数,如果参数包的参数个数大于两个,我们在进行模版推导的时候就会发生错误。
事实上,C++11就给出了解决的办法。实现tuple模版的方式给出了一种使用模版参数包的答案。
template<typename...Elements>class
tuple ;//变长模版声明
//递归的偏特化定义
template<typename
Head, typename ...
Tail>class
tuple<Head,Tail...> :private
tuple < Tail... > {
Head head; };
template <> class
tuple<> {};//边界条件
如我们实例化tuple<double,int,char,float>,则我们需要构造tuple<int,char,float>,然后将设置head为double型。依次构造tuple<char,float>和设置int head,tuple<float>,char head,tuple<> float head;这样就完成了实例化。
template<long...nums>
struct Multiply;
template<long
first,long ... last>
struct
Multiply<first,last...>
{
static
const long val =
first*Multiply<last...>::val;
};
template<> struct
Multiply < > {
static const long
val = 1; };
函数参数包:
template<typename...T>
void f(T...args);
T为变长模板参数,args则是对应于这些变长类型的数据,即函数参数包。C++11中,要去函数参数包必须唯一而且是函数的最后一个参数(模板参数包没有这样的要求)。
void
Printf(const char *s)
{
while (*s)
{
if (*s ==
‘%‘&&*++s !=
‘%‘)
{
throw runtime_error("invalid formatstring: missing arguments");
}
cout << *s++;
}
}
template<typename
T,typename...Args>
void
Printf(const char *
s,
T value,
Args... args)
{
while (*s)
{
if (*s==‘%‘&&*++s!=‘%‘)
{
cout <<
value;
return
Printf(++s, args...);
}
cout << *s++;
}
throw runtime_error("extraarguments provided to Printf");
}
int main()
{
Printf("hello %s\n",
std::string("world"));
return 0;
}
相比于变长函数,变长函数模板不会丢弃参数的类型信息。因此重载的cout总是可以将具有类型的变量正确的打印出来。这就是变长模板函数强于变长函数的地方。
C++11定义了七种参数包可以展开的位置:
1. 表达式
2. 初始化列表
3. 基类描述列表
4. 类成员初始化列表
5. 模板参数列表
6. 通用属性列表
7. Lambda函数的捕捉列表
如果我们生命Arg为参数包,那么可以使用Arg&&…这样的包扩展表达式,其解包后等价于Arg1&&,…,Argn&&(Arg1为包中的第一个参数,Argn为包中的第n个参数)。
1. template<typename…A>classT:private B<A>…{};
2. template<typename…A>classT:private B<A…>{};
同样实例化T<X,Y>;
1解包后为classT<X,Y>:private B<X>,private B<Y>{};
2解包后为classT<X,Y>:private B<X, Y>{};
操作符sizeof…,其作用是计算参数包中的参数个数。