C++11 新标准(三)

来源:互联网 发布:ubuntu做什么 编辑:程序博客网 时间:2024/05/22 04:58

变长参数模板(variadic template)

1.定义

C++11的新特性--可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数

2.语法:

可变参数模板用两种方法使用省略号。 参数名称的左侧表示参数包,参数名称的右侧将参数包扩展为单独的名称
template <typename ...Arguments>class Tuple;Tuple<> t0;    // Types不含任何实参Tuple<int> t1;    // Types含有一个实参:intTuple<int, float> t2;    // Types含有两个实参:int和floatTuple<0> error;    // 错误:0不是一个类型template <typename... Arguments> void f(Arguments... args);f();    // OK:args不含有任何实参f(1);    // OK:args含有一个实参:intf(2, 1.0);    // OK:args含有两个实参int和double

还允许使用类似 const 的说明符:template <typename... Arguments> void f(const Arguments&... args);  按照可变参数模板类的定义,您可以创建需要至少一个参数的函数:template <typename First,typename... Rest> void f(const First& first,const Rest& ... rest);

3.可变模版参数函数

(1)展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包。
(2)递归函数方式展开参数包
#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(void){   print(1,2,3,4);   return 0;}/*输出结果为:parameter 1parameter 2parameter 3parameter 4empty */

展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包Args...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程


当参数包展开到最后一个参数时递归为止。再看一个通过可变模版参数求和的例子:
template<typename T>T sum(T t){    return t;}template<typename T, typename ... Types>T sum (T first, Types ... rest){    return first + sum<T>(rest...);}sum(1,2,3,4); //10
sum在展开参数包的过程中将各个参数相加求和,参数的展开方式和前面的打印参数包的方式是一样的
递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须要一个重载的递归终止函数,即必须要有一个同名的终止函数来终止递归。
(3)逗号表达式展开参数包需要借助逗号表达式和初始化列表
template <typename T>void printarg(T t){cout << t << endl;}template <typename ...Args>void expand(Args.. args){int arr[] = {(printarg(args),0)...}}int main(){expand(1,2,3,4);return 0;}/*输出结果为:1 2 3 4  */
expand函数中的逗号表达式:(printarg(args), 0),先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0),  etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包
我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个递归终止函数了,具体代码如下:
#include <iostream>using namespace std;template <typename Func,typename ...Args>void expand(const Func& f,Args&&... args){initializer_list<int>{(f(std::forward<Args>(args)),0)...};}int main(){expand([](int i){cout << i << ' ';},1,2,3,4);}/*输出结果为:1 2 3 4  */

4.可变模版参数类

可变参数模板类是一个带可变模板参数的模板类,比如C++11中的元祖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, “”);
std::tuple<> tp;
可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂
(1)模版偏特化和递归方式来展开参数包
可变参数模板类的展开一般需要定义两到三个类,包括类声明和偏特化的模板类:
#include <iostream>using namespace std;//前向声明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) };};int main(){Sum<int,double,char> i;cout << i.value << endl;}/*输出结果为:13 */
这是一个简单的通过可变参数模板类计算的例子,可以看到一个基本的可变参数模板应用类由三部分组成:
第一部分是: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个。上面的这种三段式的定义也可以改为两段式的,可以将前向声明去掉,,这样定义: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) };};//上面的方式只要一个基本的模板类定义和一个特化的终止函数就行了,而且限定了模板参数至少有一个

(2)继承方式展开参数包
#include <iostream> #include <typeinfo>using namespace std;template<typename... A> class BMW;  template<typename Head, typename... Tail>  class BMW<Head, Tail...> : public BMW<Tail...>  {  public:      BMW()      {            printf("type: %s\n", typeid(Head).name());      }        Head head;  };template<>class BMW<> {};  int main(){      BMW<int, char, float> car;      return 0;  } /*输出结果为:type: ftype: ctype: i */

声明一个可以对应任意参数的tuple类:template<typename... Elements> class tuple;//前向声明template<typename Head, typename... Tail>class tuple<Head, Tail...> : private tuple<Tail...> {    Head head;public:    /* implementation */};template<>class tuple<> {    /* zero-tuple implementation */};

5.sizeof...()

可变模板使用 sizeof...() 运算符(与更早的 sizeof() 运算符不相关):
#include <iostream> using namespace std;template<typename... Args>struct Somestruct{static const int size = sizeof...(Args);};int main(){Somestruct<int,double,char> i;cout << i.size << endl;return 0;}/*输出结果为:3 */

0 0
原创粉丝点击