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


  • 相关阅读:
    react native 之页面跳转
    react native 之异步请求
    react native 之 redux
    react native 之页面布局
    ES6的相关新属性
    css中的选择器
    js中return的作用
    校园商铺-7商品类别模块-2商品类别列表从后到前
    校园商铺-6店铺编辑列表和列表功能-9店铺管理页面的前端开发
    校园商铺-6店铺编辑列表和列表功能-8店铺列表展示前端开发
  • 原文地址:https://www.cnblogs.com/gtarcoder/p/4810614.html
Copyright © 2020-2023  润新知