模板参数包
我们先看看变长模板的语法,以tuple为例:
template<typename... Elements> class tuple;
可以看到,我们在标识符Elements之前使用了省略号(三个点)来表示该参数是变长的。在C++11中,Elements被称作是一个“模板参数包”,这是一种新的模板参数类型。有了这样的参数包,类模板就可以接受任意多个参数作为模板参数。比如:
tuple <int, char,double>
与普通的模板参数类似,模板参数包也可以是非类型的,比如:
template<int... A> class NonType {};
NonType<1, 2, 2>nValues;
该声明方式相当于:
template<int, int, int> class NonType {};
NonType<1, 2, 2>nValues;
一个模板参数包在模板推导时会被认为是模板的单个参数,为了使用模板参数包,我们总是需要将其解包,在C++11中,这通常是通过一个名为包扩展的表达式来完成,比如:
template<template T1, template T2>class TClassB{};
template<typename ... A> class TClassA : private TClassB<A...> {};
TClassA<int, double> xy;
这里我们为类模板声明了一个参数包A,而使用参数包A...则是在TClassA的私有基类TClass<A...>中,最后一个表达式声明了一个基类为B<X,Y>的模板类TClassA的对象。其中X、Y两个模板参数先是被打包为参数包A,而后又在包扩展表达式A...中被还原。
不过上面的例子,只能接受两个参数类型,那如何才能利用模板参数包及包扩展,使得模板能够接受任意多的模板参数,且均能实例化出有效的对象呢?
在C++11中,这一答案是使用数学的归纳法,转换为计算机能够实现的手段就是递归。通过定义递归的模板偏特化定义,可以使得模板参数包在实例化时能够层层展开,直到参数包中的参数逐渐耗尽到达某个数量的边界为之。下例是简化的tuple实现:
template<typename...Element>class tuple; //变长模板的声明;
template<typename Head, typename... Tail> //递归的偏特化定义
class tuple<Head, Tail...> :private tuple<Tail...>
{
Head head;
};
template<> class tuple<>{}; //边界条件
我们首先声明了变长模板类tuple,其只包含一个模板参数,即Elements模板参数包。此外,我们又偏特化地定义了一个双参数的tuple的版本。我们将Head型的数据作为tuple<Head,Tail...>的第一个成员,而将使用了包扩展表达式的模板类tuple<Tail...>做为私有基类。这样当程序员实例化一个形如tuple<double,int, char, float>的类型时,则会引起基类的递归构造,这样的递归在tuple的参数为0个的时候结束。因此我们又定义了边界条件。
tuple<double,int, char,float>实例化后的继承结构如下图,我们用方框表示类型,而方框内的方框表示类型由其内部的方框所代表的类型私有派生而来。
函数参数包
在C++11中,还可以声明变长模板的函数,对于变长模板的函数而言,除了声明可以容纳变长个模板参数的模板参数包之外,相应地,变长的函数参数也可以声明成函数参数包。比如:
template<typename...T>void foo(T... args) {};
注意:在C++11中,标准要求函数参数包必须唯一,且是函数的最后一个参数,而模板参数包没有这样的要求。
有了函数参数包和模板参数包就可以实现C中变长函数的功能了。一起看一下Printf的例子:
#include <iostream>
#include <stdexcept>
using namespace std;
void Printf(const char *s)
{
while (*s)
{
if (*s == '%' && *++s != '%')
{
throw runtime_error("invalid format string");
}
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("extra args provided to Printf");
}
int main()
{
Printf("hello %d %s \n", 5, "world");
}
Printf是一个变长函数模板,功能等同于printf。