C++ Template

发布时间:2024年01月15日


五、模板

>> [ 1 ]. 模板参数推断

  • 在c++17之前,类模板的参数必须显式指定,而c++17中类模板和函数模板一样,可以从初始化构造函数参数中推断模板参数类型

  • 在c++17之后, 如果类模板所有参数能全部推断出来(包含有默认模板参数的情况) (形参不含引用的情况)则生成对象时不需要指定尖括号<>

  • std::vector v(8,2) //自动推断为vector<int> 类型

  • 在c++17中 允许auto或decltype(auto)用于模板的非类型参数推断

    template<auto N>
    class A {

    };
    A<42> s1; // OK: type of N in A is int
    A<‘a’> s2; // OK: type of N in A is char

  • 不能从函数形参的默认值处(包括构造函数) 推断

     #include <iostream>
     using namespace std;
     template <typename T, typename U>
     void func(T val1 = 0, U val2 = 0)
     {
         //...
     }
     int main()
     {
         func(5,6); //ok
         func();    //编译报错
         return 0;
     }
    
  • 模板参数的推断规则本质上和 auto 一样, 模板类型参数T中是不保留原类型中的引用的,除非你显式指明:func<int&,int&&>

>> [ 2 ]. 默认模板参数

  • #include <iostream>
    using namespace std;
    template <class T=short,class Z=double,class U=char,auto N=5>
    Z func(Z val)
    {
        T t{};
        Z z{};
        U u{};
        decltype(N) n {}; //用decltype()推导右值N的类型
        cout<<N<<endl;
        return val;
    }
    int main()
    {
        //c++17之后允许传递给非类型参数 非全局作用域内此被static修饰的 局部变量的地址
        static int temp{};    
        func(97);                          // T=short, Z=int, U=char, decltype(N)= int
        func<char>(97);                    // T=char, Z=int, U=char, decltype(N)= int
        func<double,long>(97);             // T=double, Z=long, U=char, decltype(N)= int
        func<double,long,short,&temp>(97); // T=double, Z=long, U=short, decltype(N)= int*
        return 0;
        //为模板参数指定默认值时可以是在参数列表的开头, 中间 或 结尾
    }
    

>> [ 3 ]. 模板的非类型参数

  • c++17对模板的非类型参数的规定有所更改(更好用了)
  • 模板的非类型参数可以是以下类型
    >.指针变量必须用constexpr修饰才能传入 要么就用&取变量的地址直接传入 用auto修饰的非类型参数可以自动推断为该变量的指针类型
    • 整型/字符型
    • 对象类型(可以是模板对象)和基本类型的引用或指针
    • 函数(可以是函数模板的实例化函数)的引用或指针
    • 类的成员指针
  • 用法示例:
    #include <iostream>
    using namespace std;
    //类模板
    template <class Z>
    class x
    {
        //....
    };
    //函数模板
    template <class Z>
    void func(Z z){
        //...
    }
    //普通函数
    void func1(char ch){ //
        //...
    }
    //测试非类型参数用 函数模板
    template <class Z, decltype(auto) N>
    Z test(Z val)
    {
        decltype(N) n = N;
        return val;
    }
    //传指针变量时必须用constexpr修饰为常量 表示后面出现pfunc时 可以自动转换为指向的函数的地址常量
    constexpr auto pfunc =func<double>;//必须显示指明,不然不会有这个版本的函数,模板只是个"图纸"
    auto& pfunc1 =func1;  //普通函数的引用 传引用类型时可不用constexpr修饰
    
    constexpr int temp = 5; //基本类型 使其成为编译时常量即可传入模板的非类型参数
    auto & temp1 = temp;    //基本类型的引用
    constexpr auto pTemp = &temp; //基本类型的指针 必须用constexpr声明为常量指针 才能传入
    int main()
    {
        static x<short> obj;   //或者在全局范围声明就不用加static
        static x<short>& obj1 = obj;
    
        decltype(auto) testType = pfunc1;
        //各个版本对非类型参数的要求略有不同 c++98要求必须将temp声明在全局,
        //即使用static修饰也无济于事  直到c++17 (至少gcc下如此)
        test<int,pTemp>(97);      //pTemp为 const int*const 类型     N 为 const int* 类型
        test<int,temp>(97);       //temp为  const int 类型           N 为 int 类型  (把temp当做编译时常量所以没有const)
        test<int,&obj>(97);       //&obj为  obj  对象地址             N 为 x<short>* 类型
        test<int,obj1>(97);       //obj1为  x<short>& 类型           N 为 x<short>& 类型
        test<int,func1>(97);      //func1为 普通函数                  N 为 void(*)(char) 类型
        test<int,func<int>>(97);  //func为  函数模板实例化后的函数       N 为 void(*)(int) 类型
        test<int,pfunc1>(97);     //pfunc1为 普通函数的引用             N 为 void(&)(char) 类型
        test<int,pfunc>(97);      //pfunc为 函数模板实列化后的地址常量 指针  N 为 void(*)(double) 类型
        return 0;
        //函数名会自动转换为地址常量 可以不用 & 取函数的地址
    }
    
  • 在模板内非类型参数是个右值不能修改

>> [ 4 ]. 无名模板参数

  • 如果该模板体不需要用上模板参数则可以不写名字

    template<typename,typename> //可以是一个/可以是多个
    struct test
    {
    	test(){cout<<"i do not need to use this/these type"<<endl;}
    };
    
  • 通常是用在基模板上

    template<typename,typename>
    struct test
    {
        test(){cout<<"i do not need to use this/these type"<<endl;}
    };
    // 特化版本 
    template<typename _Tp,class A>
    struct test<_Tp,A&> //只要有一个参数不一样就能形成特化
    {
        typedef _Tp   type;
        test(){cout<<"Specialization"<<endl;}
    };
    
  • 无名非类型模板参数

    template<auto,auto>
    struct test
    {
        test(){cout<<"i do not need to use this/these non-type parameters"<<endl;}
    };
    // 特化版本
    template<char a,int b>
    struct test<a,b>
    {
        test(){cout<<"Specialization"<<endl;}
    };
    

>> [ 5 ].模板的可变参数

  • 可变参数函数模板

    template<typename... T>
    void vair_fun(T...args) {
       //函数体
    } 
    
    • typename或者class后跟 就表明T是一个可变模板参数,它可以接收多种数据类型,又称模板参数包
    • args 参数的类型用T…表示,表示 args 参数可以接收任意个参数,又称函数参数包
    • vair_fun();可以这样调用,这表明 参数包可以是空包 相当于没有这个参数包
    • 使用可变参数模板的要点在于"解包", 使用包里的数据
      • 递归方式解包
        #include <iostream>
        using namespace std;
        //模板函数递归的出口
        void vir_fun() {
        }
        template <typename T, typename... args>
        void vir_fun(T argc, args... argv)//把函数参数包放后面;vir_fun(argv...);这样调用时能自动分离
        {
            cout << argc << endl;
            //开始递归,将第一个参数外的 argv 参数包重新传递给 vir_fun
            vir_fun(argv...);
        }
        int main()
        {
            vir_fun(1, "mytest.variadic_templates", 2.34);
            return 0;
        }
        
      • 借助逗号表达式和初始化列表解包
        #include <iostream>
        using namespace std;
        template <typename T>
        void dispaly(T t) {
            cout << t << endl;
        }
        template <typename... args>
        void vir_fun(args... argv)
        {
            //逗号表达式+初始化列表
            int arr[] = { (dispaly(argv),0)... };
           //{ (display(argv),0)... }会依次展开为  
           //{ (display(1),0), (display("mytest.variadic_templates"),0), (display(2.34),0) }
           //逗号表达式总是返回最后一个值,所以arr数组存储的都是0值
           //arr 数组纯粹是为了将参数包展开,没有发挥其它作用
        }
        int main()
        {
            vir_fun(1, "mytest.variadic_templates", 2.34);
            return 0;
        }
        
        
  • 可变参数类模板

    • 借助逗号表达式和初始化列表解包
      #include <iostream>
      using namespace std;
      template <typename T>
      void dispaly(T t) {
          cout << t << endl;
      }
      template<typename... Tail>
      class demo
      {
      public:
          demo(Tail... vtail) {
              int arr[] = { (dispaly(vtail),0)... };
          }
      };
      int main() {
          demo t(1, 2.34, "mytest.variadic_templates");
          return 0;
      }
      
    • 继承形式基类+构造函数递归解包
      #include <iostream>
      //形式类模板demo 只起一个形式作用 中转作用 可以直接写成声明形式
      template<typename... Values> class demo;//类型必须要用参数包申明符... 不然特化1 会 error!
      //特化1 模板特化/专门化, 因为模板参数包允许空包 继承式递归的出口
      template<> class demo<> {};
      
      //特化2 模板特化(含继承操作)  以继承的方式解包 
      template<typename Head, typename... Tail>
      class demo<Head, Tail...>
              : private demo<Tail...> //继承形式基类
      {
      public:
           //demo<Tail...>(vtail...) 初始化形式基类 , 实际会跳转选择初始化 特化2 
          demo(Head v, Tail... vtail) : m_head(v), demo<Tail...>(vtail...) {
              dis_head();
          }
          void dis_head() { std::cout << m_head << std::endl; }
      protected:
          Head m_head;
      };
      int main() {
          //必须在<>内指明类型 别问 问就是 error
          demo<int, float, std::string> t(1, 2.34, "mytest.variadic_templates"); 
          return 0;
      }
       /*输出:
       	mytest.variadic_templates
      	2.34
      	1
      */
      
  • Operator sizeof…()
    C++11 also introduced a new form of the sizeof operator for variadic templates: sizeof…It expands to the number of elements a parameter pack contains.

    template<typename T,typename… Types>
    void print(T firstArg,Types… args){
    std::cout<<sizeof…(Types)<<endl; // print number of remaining types
    std::cout<<sizeof…(args)<<endl; // print number of remaining args
    }

    所以,你可能想:

    #include <iostream>
    using namespace std;
    template <typename... args,typename T>
    void vir_fun(T argc, args... argv)
    {
        cout << argc << endl;
        if (sizeof...(args)>0)
        vir_fun(argv...);
    }
    int main()
    {
        vir_fun(1, "mytest.variadic_templates", 2.34,'a');
        return 0;
    }
    

    However, this approach doesn’t work, 因为 if 语句的两个分支在函数模板中都会被实例化
    前文C++规定说过, 模板实例化哪个版本是在编译时决定的,而 if 的分支是有运行时特性的
    用 if constexpr 代替即可

  • Fold Expressions

    Fold ExpressionEvaluation
    ( ... op pack )( ( ( pack1 op pack2 ) op pack3 ) ... op packN )
    ( pack op ... )( pack1 op ( ... ( packN-1 op packN ) ) )
    ( init op ... op pack )( ( ( init op pack1 ) op pack2 ) ... op packN )
    ( pack op ... op init )( pack1 op ( ... ( packN op init ) ) )
    用法示例:
    #include <iostream>
    using namespace std;
    class Node
    {
    public:
        int m_a;
        Node* left;
        Node* right;
        Node(int i = 0): m_a(i), left(nullptr), right(nullptr)
        {
        }
    };
    template <class T, typename... args>
    Node* traverse(T init, args... argv)
    {
        return (init ->* ... ->* argv);
    }
    auto Gleft = &Node::left;
    auto Gright = &Node::right;
    //-------------------------------------用法二--------------------------
    template<class T>
    class AddSpace
    {
        T const& ref;
    public:
        AddSpace(T const& r): ref(r) {};
        friend ostream& operator<<(ostream& os, AddSpace<T> s)
        {
            return os << ' ' << s.ref;
        }
    };
    template<typename... Types>
    void print(Types const& ... args)
    {
        (cout << ... << AddSpace(args)) << endl;
    }
    int main()
    {
        Node* root = new Node{0};
        root->left = new Node{1};
        root->right = new Node{2};
        traverse(root, Gleft, Gright);
    	
        print(2, "hello world", 2.5);
        return 0;
    }
    

>> [ 6 ].模板模板参数

  • 模板也可以作为模板参数
    //template<typename T> typename Thing中 Thing 是这个模板的名字其他是固定写法,参数列表需要匹配你传入的模板
    template<template<typename T> typename Thing = std::array> //可以有默认值 
    class A{
    };
    
  • 使用示例:
    #include <iostream>
    #include <array>
    using namespace std;
    template<class T,class U=int>
    class dx{
    };
    //alias template
    template<class T>
    using my_alias= T*;
    
    template<template <typename T> typename Thing=std::array>
    class B{
    	Thing<int> m_a;
    };
    int main()
    {
       B<my_alias> a;
       B<dx> b; //因为 U 设有默认值 所以可以匹配
    }
    

>> [ 7 ].Deduction Guides

  • #include <iostream>
    using namespace std;
    template <class T>
    class test
    {
    public:
        test() = default;
        test(T a) {};
    };
    test(const char *)->test<std::string>; //Deduction Guide
    int main()
    {
        static int temp{};
        test a {"helloworld"};//const char* convert to string;
    }
    
  • Templatized Aggregates
    Aggregate classes ( classes/structs with no user-provided, explicit, or inherited constructors, no private or protected nonstatic data members, no virtual functions, and no virtual, private, or protected base classes ) can also be templates.For example:

    template <class T>
    class test
    {
    public:
    T value;
    string comment;
    };

    Since C++17, you can even define deduction guides for aggregate class templates:

    test(char const*,char const*)->test<std::string>;
    test vc={“hello”,“initial value”};

    Without the deduction guide, the initialization would not be possible, because test has no consturct to perform the deduction aganist.

文章来源:https://blog.csdn.net/Computer_Tech/article/details/135533993
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。