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);
    return 0;
}
//当调用print(1,2,3,4)时,先后调用print(2,3,4), print(3,4),print(4),最终调用print()终止。
如果递归终止函数为
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)...};
}
//{(printarg(args), 0)...}会被展开为{(printarg(arg1), 0), (printarg(arg2), 0), (printarg(arg3), 0)}
expand(1,2,3,"hello");

这种展开参数包的方式,不需要通过递归终止函数,而是直接在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)...};
}

2. 可变参数模板类

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>{
    enum{value = sizeof(Last)};
};
这个Sum类的作用是在编译期计算出参数包中参数类型的size之和。

或者可通过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...));
}
时间: 2024-08-03 01:02:35

c++11——可变参数模板的相关文章

C++11 可变参数模板

在C++11之前, 有两个典型的受制于模板功能不强而导致代码重复难看的问题, 那就 function object 和 tuple. 拿 function objects 来说, 需要一个返回类型参数及N个参数类型参数. 但因为变长参数模板不受支持,导致不得不重复书写7.8个模板类,但最终也只能支持7.8个参数的 function object.C++11中最终为我们带来了强大的变长 参数模板功能,这些问题也随之迎刃而解了. 可变参数模板(Variadic Template)故名思义,即可以接受

C++ 11可变参数接口设计在模板编程中应用的一点点总结

概述 本人对模板编程的应用并非很深,若要用一句话总结我个人对模板编程的理解,我想说的是:模板编程是对类定义的弱化. 如何理解“类定义的弱化”? 一个完整的类有如下几部分组成: 类的名称: 类的成员变量(或属性,C#中属性和成员变量还是有区别的): 类的成员方法: 从编译器的角度看,我们必须明确指定以上3部分,才算完整地定义了一个类并且编译通过. 所谓的“类弱化”,是指类的设计者在定义类的时候,并没有完整定义一个类,而是把类的其中一部分的定义留给类的使用者. 从传统才c++98看,通过模板类,使用

函数模板,函数模板重载,可变参数模板,函数模板覆盖,通过引用交换数据

 1.函数模板初级,如果想使用模板,需要实例化,实例化的方式是加上<数据类型> #include <iostream> //函数模板可以对类型进行优化重载,根据类型会覆盖 //如果仍然要使用模板函数,需要实例化 template<class T> T add(T a, T b) { std::cout << "T add " << std::endl; return a + b; } int add(int a, int

c++ --可变参数模板

一个可变参数模板就是一个可接受可变数目参数的模板函数或模板类. 可变数目的参数被称为参数包. 1 //可变参数模板;sizeof ...()运算符 2 template <typename ... Args> 3 void g(Args ... args) 4 { 5 cout<<sizeof ...(Args)<<endl; //类型参数的数目 6 cout<<sizeof ...(args)<<endl; //函数参数的数目 7 } 8 9

C++中的可变参数模板

作者:Eli Bendersky http://eli.thegreenplace.net/2014/variadic-templates-in-c/ 回到C++11前夜,编写带有任意参数函数的唯一办法是使用可变参数函数,像printf,使用省略号语法(-)以及伴随的va_族的宏.如果你曾经使用这个方法编写代码,你会知道这有多累赘.除了变成类型不安全外(所有的类型解析必须在运行时在va_arg里通过显式转换来完成),要做对并不容易.Va_宏执行低级的内存操作,我看见过许多因为没有小心使用它们导致

求变量的数据类型,typeid,bool,C和C++的不同,new和delete,C++中的枚举,inline和可变参数模板,auto和函数模板,宽字符

求变量的数据类型,通过函数typeid(变量名).name();获得变量的数据类型. 案例如下: #include <iostream> #include <stdlib.h> void main() { double db = 10.9; double *pdb = &db; auto num = pdb; //通过typeid的方式获得数据类型 std::cout << typeid(db).name() << std::endl; std::c

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

有些时候,我们定义一个函数,可能这个函数需要支持可变长参数,也就是说调用者可以传入任意个数的参数.比如C函数printf(). 我们可以这么调用. printf("name: %s, number: %d", "Obama", 1); 那么这个函数是怎么实现的呢?其实C语言支持可变长参数的. 我们举个例子, double Sum(int count, ...) { va_list ap; double sum = 0; va_start(ap, count); fo

可变参数模板

一.基本语法 声明一个带有可变参数个数的模板的语法如下所示: template<typename ...Element> class tuple; tuple<int, string> a;  // use it like this 在模板参数 Element 左边出现省略号 ... ,就是表示 Element 是一个模板参数包(template type parameter pack).parameter pack(参数包)是新引入 C++ 中的概念,比如在这个例子中,Eleme

C++11中的Tuple和可变参数模版

C++11中的tuple是一个n元的可变元组,它相当于有n个元素的结构体,只不过这个结构体的成员都是匿名的,tuple中提供了一个get()方法来获取某个下标对应的元素的值.另外可以通过make_tuple()方法来构造一个tuple对象.具体用法如下 我们知道tuple中的元素个数是不确定的,而每个元素的类型通过模板参数指定,那么tuple是如何做到这些的呢?答案就是使用可变参数模板.在C++中,我们使用过printf函数,它的参数就是可变的,在C++11中也允许模板的参数也是可变的.举个例子