美文网首页
C++ 模板与泛型

C++ 模板与泛型

作者: 从不中二的忧伤 | 来源:发表于2020-06-14 16:47 被阅读0次
    一、概述
    二、模板定义与使用

     1. 函数模板的定义与使用
     2. 类模板的定义与使用

    三、typename 的特殊用法

     1. 声明类型模板参数
     2. 标识类的类型成员

    四、默认模板参数

     1. 类模板的缺省参数
     2. 函数模板的缺省参数

    五、函数指针、对象作为函数模板参数

     1. 函数指针作为函数模板参数
     2. 对象作为函数模板参数

    六、类的成员函数模板
    七、using 定义模板别名
    八、模板的全特化与偏特化

     1. 类模板特化
     2. 函数模板特化

    九、可变参模板

     1. 可变参函数模板
     2. 可变参类模板

    十、模板模板参数

    一、概述
    1. 泛型编程是以独立于任何特定类型的方式编写代码。 使用泛型编程时,需要提供具体程序实例所操作的类习惯或者值。
    2. 模板是泛型编程的基础,模板是创建类或者函数的蓝图或者公式,通过给定蓝图或者公式足够信息,将其真正的转变成具体的类或者函数。这种转变通常发生在编译时。
    3. 模板支持将类型作为参数的程序设计方式,从而实现对泛型程序设计的直接支持。(在定义类、函数时,将类型作为参数)

    二、模板定义与使用
    1. 函数模板

    (1) 函数模板的定义

    template<typename T>
    T Add(T val1, T val2)
    {
        T res = val1 + val2;
        return res;
    }
    

    <>里面是 模板参数列表(模板实参),<>里面至少需要一个模板参数。

    (2) 函数模板的使用
    调用函数模板时,编译器会根据调用函数模板时的实参 去 推断模板参数列表内的参数(形参)类型。当编译器光凭借函数实参推断不出模板参数时,需要用<>主动提供模板参数。

    // 编译器通过调用的实参,推断模板形参类型为 int
    int res = Add(1, 2);
    
    // 编译器在推断出模板的形参类型后,实例化了一个特定版本的函数,相当于:
    int Add(int val1, int val2)
    {
        int res = val1 + val2;
        return res;
    }
    

    (3) 非类型模板参数
    用typename/class 声明的参数,为类型参数。在模板参数列表内,还可以定义非类型参数。非类型参数代表一个数值。非类型参数需要用具体的类型名来声明非类型参数。

    // 非类型模板参数定义
    template<int S1, int S2>
    int Add()
    {
        int res = S1 + S2;
        return res;
    }
    
    // 非类型模板参数的调用
    // 显式指定模板参数
    int res = Add<1, 2>();
    
    template<int L1, int L2>
    int charscmp(const char(&p1)[L1], const char(&p2)[L2])
    {
        return strcmp(p1, p2);
    }
    
    // 编译器推断非类型模板参数的值为传入的字符串长度
    int res = charscmp("test", "test1");
    

    当模板被实例化时,非类型模板参数的值,可能是调用时指定的,也可能是编译器推断的。
    非类型模板参数的值,必须是常量表达式。 因为模板实例是在编译器编译时实例化的。
    模板的定义并不会使编译器生成代码,只有在具体调用时,编译器实例化一个特定版本的函数,才会生成代码。

    2. 类模板

    (1) 概述
    可以用类模板实例化一个特定的类。编译器不能为类模板推断模板参数类型,使用类模板必须显式指定模板参数。(比如:vector<int> )
    实例化类模板时,必须要有类的全部信息,包括类模板中的成员函数的函数体。 所以一般来说,所以类模板信息会放在.h头文件中。

    (2) 类模板的定义与调用

    template<typename T>
    class myVector
    {
    public:
        typedef T* myiterator;  //迭代器
    
    public:
        myVector();
        myVector& operator=(const myVector&);
    
    public:
        myiterator mybegin();
        myiterator myend();
    
    public:
        void myfunc1() // 成员函数体在类模板定义中,被隐式声明为内联函数
        {
            // todo
            return;
        };  
        void myfunc2();
    };
    
    // 成员函数体,在类模板定义外
    template<typename T>
    void myVector<T>::myfunc2()
    {
        // todo
        return;
    }
    
    template<typename T>
    myVector<T>& myVector<T>::operator=(const myVector&) // <T> 表示返回的是一个实例化了的myVector
    {
         return *this;
    }
    
    int main()
    {
      myVector<int> vec;  //调用时,编译器生成具体的类
      return 0;
    }
    

    myVector是类模板名,不是一个类名, 类模板是用于实例化类用的。
    myVector<int> 才是类型名。

    (3) 类模板的成员函数

    • 类模板成员函数体,写在类模板定义中时,会被隐式声明为inline函数。
    • 类模板的成员函数,具有和这个类模板相同的模板参数。类模板实例化后,模板的不同实例,都有自己版本的成员函数。
    • 类模板实例化时,其成员函数只有在被调用到时,才会被实例化。

    (4) 非类型模板参数

    template<typename T, int size = 10>
    class myarray
    {
    private:
        T arr[size];
    public:
        void myfunc();
    };
    
    template<typename T, int size>
    void myarray<T, size>::myfunc()
    {
        // todo
        return;
    }
    
    int main()
    {
        myarray<int, 100> tmparray1;
        myarray<int> tmparray2;
    
        return 0
    }
    
    • 非类型模板参数的限制:
      (1) 浮点型不能作为非类型模板参数 (double, float)
      (2) 类类型不能作为非类型模板参数(非类型一般是基础类型)

    三、typename 的特殊用法
    1. 声明类型模板参数

    typename 和 class 在模板定义中,声明模板参数为类型参数时,可以互换。

    template<typename T>
    T Add(T val1, T val2)
    
    template<class T>
    T Add(T val1, T val2)
    
    2. 标识类的类型成员

    使用类的类型成员时,用 typename 标识其后跟的是个类型。
    例一

    template<typename T>
    class myvector
    {
    public:
        typedef T* myiterator;  // 用 typedef 定义的为类型成员
    public:
        myiterator mybegin();  // 返回容器中的第一个元素
    };
    
    template<typename T>
    // 1. 需要用 myvector<T>:: 访问类型成员 myiterator
    // 2. 编译器只有在实例化时才能确定 myvector<T>:: 后面跟着的是类型还是静态成员变量,所以需要用 typename标识其后跟着的是类型。
    typename myvector<T>::myiterator myvector<T>::mybegin()
    {
        //todo 
    }
    

    例二
    size_type 为 string / STL 的类型成员, typedef usigned int size_type
    typename 表示 T::size_type 返回的也是一个类型

    template<typename T>
    typename T::size_type getlength(const T& c)
    {
        return c.size();
    }
    
    string str = "hello world";
    cout << getlength(str) << endl;
    

    四、默认模板参数
    1. 类模板的缺省参数

    类模板的实例化必须显示声明模板参数(<>不可省略)
    类模板的缺省参数

    template<typename T=string, int size=10>
    class myarray
    {
    private:
        T arr[size];
    };
    
    int main()
    {
        // 完全使用模板参数缺省值
        myarray<> arr1;
    
        // 使用部分模板参数缺省值
        myarray<int> arr2;
    
        return 0;
    }
    
    2. 函数模板的缺省参数

    C++11 开始,支持函数模板默认参数
    例一: 对象作为函数模板默认参数

    class Test
    {
    public:
        Test() { cout << "construct" << endl; }
        Test(const Test& t) { cout << "copy construct" << endl; }
        
        int operator()(int v1, int v2) const
        {
            return v1 + v2;
        }
    };
    
    // 为 F 提供默认参数 Test 类
    template<typename T, typename F=Test>
    // 为 pf 提供临时对象 F(),即为Test()
    void tfunc(const T &i, const T &j, F pf=F())
    {
        cout << pf(i, j) << endl;
    }
    
    int main()
    {
        tfunc(2, 5);
        return 0;
    }
    

    例二:函数指针作为函数模板默认参数

    typedef int (*FunType)(int, int);
    
    int myfunc(int val1, int val2)
    {
        return val1 + val2;
    }
    // 为 F 提供默认参数 FunType 类型
    template<typename T, typename F=FunType>
    // pf 默认为 myfunc
    void tfunc(const T &i, const T &j, F pf=myfunc)
    {
        cout << pf(i, j) << endl;
    }
    
    int main()
    {
        tfunc(2, 5);
        return 0;
    }
    

    例三:函数模板的默认非类型参数

    template<int T=10>
    void tfunc()
    {
        cout << T << endl;
    }
    
    int main()
    {
        tfunc();     // 输出10
        tfunc<>();  // 输出 10
        tfunc<12>();    // 输出12
        return 0;
    }
    

    五、函数指针、对象作为函数模板参数
    1. 函数指针作为函数模板参数

    (1) 函数指针作其他函数的参数

    // 定义函数指针类型
    typedef int (*FunType)(int, int);
    
    int myfunc(int val1, int val2)
    {
         return val1 + val2;
    }
    
    void recvfunc(int i, int j, FunType pf)
    {
        cout << pf(i, j) << endl;
    }
    
    int main()
    {
        recvfunc(1, 2, myfunc);
        return 0;
    }
    

    (2) 函数指针作为函数模板参数
    下面的例子中,编译器将模板参数 F 解释成函数指针类型 FunType

    typedef int (*FunType)(int, int);
    
    int myfunc(int val1, int val2)
    {
         return val1 + val2;
    }
    
    template<typename T, typename F>
    void tfunc(const T &i, const T &j, F pf)
    {
        cout << pf(i, j) << endl;
    }
    
    int main()
    {
        tfunc(1, 2, myfunc);
        return 0;
    }
    
    2. 对象作为函数模板参数
    template<typename T, typename F>
    void tfunc(const T &i, const T &j, F pf)
    {
        cout << pf(i, j) << endl;
    }
    
    class Test
    {
    public:
        Test() { cout << "construct" << endl; }
        Test(const Test& t) { cout << "copy construct" << endl; }
        
        int operator()(int v1, int v2) const
        {
            return v1 + v2;
        }
    };
    
    int main()
    {
        Test t;    // 调用构造函数
        tfunc(1, 2, t);    // 调用拷贝构造函数
    }
    

    执行结果:


    image.png

    上面的程序中,编译器将模板参数 F 解释为 Test 类型对象(因为在 Test 类中重载了括号Test(int, int),所以Test类对象可以当作函数调用的格式使用),并将 t 传递给了 pf (pf相当于临时对象),调用了拷贝构造函数。

    使用临时对象:

    int main()
    {
        tfunc(2, 3, Test());
    }
    

    执行结果:


    image.png

    使用临时对象,则模板函数直接承接此临时对象,省去了拷贝构造过程(也省去一次析构过程),更加节省时空。


    六、类的成员函数模板
    1. 普通类和模板类,其成员函数都可以是模板函数,即 “成员函数模板”
    2. 成员函数模板不允许是虚函数

    (1) 普通类的成员函数模板

    class Test
    {
    public:
        template<typename T>
        void func(T t)
        {
            cout << t << endl;
        }
    };
    
    int main()
    {
        Test test;
        test.func("hello world");
    
        return 0;
    }
    

    (2) 模板类的成员函数模板

    • 模板类的模板,与模板类中的成员函数模板互不打扰
    • 模板类的成员函数模板,需要带上模板类的模板,和模板函数的模板
    • 模板类的成员函数(普通成员函数/成员函数模板),只有在调用到时才进行实例化(即函数在目标文件 .o 中存在)
    template<typename C>
    class Test
    {
    public:
        template<typename T>
        Test(T v);
    
    private:
        C m_tc;
    };
    
    template<typename C>  // 模板类的模板
    template<typename T>  // 成员函数模板的模板
    Test<C>::Test(T v)
    {
        cout << "construct v: " << v << endl;
    }
    
    int main()
    {
        // int 类型对应的是 模板类的 typename C
        // 2 对应的是构造模板函数中的typename T
        Test<int> test(2); 
        return 0;
    }
    

    七、using 定义模板别名

    typedef 用于定义类型的别名:

    typedef std::map<int, int> map_i_i;
    typedef std::map<int, string> map_i_s;
    

    假设希望定义 std::map<int, 自主指定类型> :


    方法一: 定义模板类,在模板类中定义带模板参数的类型成员

    template<typename T>
    class map_i_t
    {
    public:
        typedef std::map<int, T> type;     
    };
    
    int main()
    {
        map_i_t<int>::type map_i_i;
        map_i_t<string>::type map_i_s;
        return 0;
    }
    



    方法二:C++11,可以用using定义模板别名

    typelate<typename T>
    using map_i_t = std::map<std::string, T>
    
    int main()
    {
        map_i_t<int> map_i_i;
        map_i_t<string> map_i_s;
        return 0;
    }
    
    • using 在用于定义类型,或者定义类型模板时,包含了 typedef 的所有功能
    typedef unsigned int uint_t;
    // 相当于使用 using 定义:
    using uint_t = unsigned int;
    
    // 定义函数指针:
    typedef int(*FuncType)(int, int);
    using FuncType = int(*)(int, int);
    
    • 使用using 定义函数指针模板
    template<typename T>
    using myfunc = int(*)(T, T);
    
    int Add(int a, int b)
    {
        return a + b;
    }
    
    int main()
    {
        // myfunc<int> 为函数指针类型,是类型名
        // pFunc 为函数指针
        myfunc<int> pFunc;    
    
        pFunc = Add;    // 将函数地址赋值给函数指针变量
        cout << pFunc(1, 2) << endl;
        return 0;
    }
    

    八、模板的全特化与偏特化

    特化: 模板是泛化的表现,可以指定不同类型做相同的表现。而特化是指对于某些特殊的类型(类型模板参数),进行特殊的处理。需要注意的是,必须先有了泛化版本,才会有对应的特化版本。

    1. 类模板特化

    (1) 类模板全特化

    a) 常规类模板全特化

    • 全特化版本,即所有【类型模板参数】都被指定
    • 全特化版本可以有多个【类型模板参数】不同的版本
    • 编译器会优先选择满足条件的特化版本
    #include <iostream>
    using namespace std;
    
    // 泛化版本 
    template<typename T, typename U>
    class Test{
    public:
        Test()
        {
            cout << "Test<T, U>()" << endl; 
        }   
    };
    
    // 全特化版本,即所有类型模板参数都被指定 
    template<>
    class Test<int, int>{
    public:
        Test()
        {
            cout << "Test<int, int>()" << endl;
        }
    }; 
    
    // 全特化版本可以有多个【类型模板参数】不同的版本 
    template<>
    class Test<double, double>{
    public:
        Test()
        {
            cout << "Test<double, double>()" << endl;
        }
    };
    
    int main()
    {
        // 编译器会优先选择满足条件的特化版本 
        Test<char, char> t1;
        Test<int, int> t2;
        Test<double, double> t3;
        return 0;   
    } 
    
    • 全特化类模板的成员函数,必须放在类内定义,不然会报错(关于这一点,我也觉得很蛋疼,但是暂时没有找到很好的支持这一现象的原因说明)`(>﹏<)′

    正确写法:

    template<>
    class Test<int, int>{
    public:
        Test()
        {
            cout << "Test<int, int>::Test()" << endl;
        }
        // 特化版本成员函数需要在类内实现 
        void Func()
        {
            cout << "Test<int, int>::Func()" << endl;
        }
    };
    

    错误写法:

    template<>
    class Test<int, int>{
    public:
        Test()
        {
            cout << "Test<int, int>::Test()" << endl;
        }
        void Func();
    };
    
    template<>
    void Test<int, int>::Func()
    {
        cout << "Test<int, int>::Func()" << endl;
    }
    

    报错信息:
    [Error] template-id 'Func<>' for 'void Test<int, int>::Func()' does not match any template declaration



    b) 特化成员函数

    • 只对类模板中的【某些成员函数】进行特化
    • 特化的成员函数,其被构造的对象依然是【泛化版本】的,但是会调用到【特化的成员函数】。
    #include <iostream>
    using namespace std;
    
    // 泛化版本 
    template<typename T, typename U>
    class Test{
    public:
        Test()
        {
            cout << "Test<T, U>()" << endl; 
        }
        
        void Func();
    };
    
    // 泛化版本成员函数
    template<typename T, typename U>
    void Test<T, U>::Func()
    {
        cout << "Test<T, U>::Func()" << endl;   
    } 
    
    // 特化成员函数 
    template<>
    void Test<int, int>::Func()
    {
        cout << "Test<int, int>::Func()" << endl;
    }
     
    
    int main()
    {
        Test<char, char> t1;
        t1.Func();
        
        Test<int, int> t2;
        t2.Func();
    
        return 0;   
    } 
    



    (2) 类模板偏特化

    a) 模板参数数量 —— 偏特化

    • 偏特化的特化参数类型可以跳着来,不一定需要从最右边开始(但是从代码风格来说,不建议这样做)
    • 偏特化类模板,可以在类外进行成员函数的定义。(所以当一定需要将特化的类模板的成员函数【声明】和【定义】分开时,可以加一个无用的模板参数类型,凑成偏特化。但是我觉得这样做依然很蛋疼(っ °Д °;)っ)
    // 模板参数数量 —— 偏特化 
    template<typename U>
    class Test<int, U, int>{
    public:
        Test()
        {
            cout << "Test<int, U, int>::Test()" << endl;
        }
        
        void Func();
    };
    
    template<typename U>
    void Test<int, U, int>::Func()
    {
        cout << "Test<int, U, int>::Func()" << endl;
    }
    

    b) 模板参数范围 —— 偏特化

    • 范围的概念是说,从任意类型T,缩小为某种更小的范围T。
      比如:从 int 变成 const int
      从 T 变成 T*,或者变成T&(左值引用),或者变成T&&(右值引用)都是范围缩小。
    • 编译器在选择特化版本时,会根据<>内的类型寻找最合适的版本
      如下的 const T* 实际上是指向 const T 类型的指针,特化时会使用 <T* > 版本
      而 T* const 实际上只读的指针,不可修改指向,更符合<const T>特化版本
    // 模板参数范围 —— 偏特化
    
    template<typename T>
    class Test{
    public:
        Test()
        {
            cout << "Test<T>::Test()" << endl;
        }
        
        void Func();
    }; 
    
    template<typename T>
    void Test<T>::Func()
    {
        cout << "Test<T>::Func()" << endl;
    }
    
    // const 特化版本 
    template<typename T>
    class Test<const T>{
    public:
        Test()
        {
            cout << "Test<const T>::Test()" << endl;
        }
        void Func();
    };
    
    template<typename T>
    void Test<const T>::Func()
    {
        cout << "Test<const T>::Func()" << endl;
    }
    
    // 指针* 特化版本 
    template<typename T>
    class Test<T*>{
    public:
        Test()
        {
            cout << "Test<T*>::Test()" << endl;
        }
        void Func();
    };
    
    template<typename T>
    void Test<T*>::Func()
    {
        cout << "Test<T*>::Func()" << endl;
    }
    
    int main()
    {
        Test<char> t1;
        t1.Func();
        
        Test<const char> t2;
        t2.Func();
        
        Test<char*> t3;
        t3.Func(); 
    
        Test<const char*> t4;   // <T*> 特化版本 
        t4.Func();
        
        Test<char* const> t5;   // <const T> 特化版本 
        t5.Func(); 
    
        return 0;   
    } 
    
    2. 函数模板特化

    (1) 函数模板全特化

    • 全特化函数模板,实际上等价于实例化一个函数模板,并不能看作函数重载。
    • 即存在重载函数,又存在特化函数模板时,编译器会优先顺序为:
      普通函数(重载函数)> 特化版本函数模板 > 泛化版本函数模板
    // 函数模板泛化版本
    template<typename T, typename U>
    void Func(T val1, U val2)
    {
        cout << "Func<T, U>" << endl;
    }
    
    // 函数模板全特化版本
    template<>
    void Func<double, double>(double val1, double val2)
    {
        cout << "Func<double, double>" << endl;
    } 
    
    
    int main()
    {
        Func('a', 'b');
        Func(1.0, 2.0);
        
        return 0;
    }
    

    (2) 函数模板偏特化

    • 实际上,函数模板是不允许被偏特化的(也是没有找到具体不允许偏特化函数模板的原因……○| ̄|_ =3)
      错误写法:
    // 函数模板偏特化版本(不被允许) 
    template<typename T>
    void Func<T, double>(T val1, double val2)
    {
        cout << "Func<T, double>" << endl;
    } 
    

    报错信息:
    [Error] function template partial specialization 'Func<T, double>' is not allowed


    九、可变参模板

    C++ 11 中引入了 可变参模板 (Variadic Template): 允许模板中含有 0 个到 任意个 模板参数。

    1. 可变参函数模板

    (1) 可变参函数模板的基本写法

    • T为可变参类型,是0到n个不同的类型
    • args为可变形参,是对应的不同类型的参数
    template<typename... T>
    void VarFunc(T... args)
    {
        cout << sizeof...(T) << endl;
    }
    
    int main()
    {
        VarFunc(1, 0.5, 'a', "abc");
        return 0;
    }
    

    (2) 递归函数展开参数包

    • 将可变参函数模板的入参拆成 【一个形参,一包可变形参】
    • 重载函数,作为递归终止函数,作为可变形参为空时调用的函数
    // 递归终止函数
    void VarFunc2()
    {
        cout << "end" << endl;
    } 
    
    // 可变参函数模板 
    template<typename T, typename... U>
    void VarFunc2(const T& t, const U&... args)
    {
        cout << t << endl;
        VarFunc2(args...);
    }
    
    int main()
    {
        VarFunc2(1, 0.5, 'a', "abc");
        return 0;
    }
    
    2. 可变参类模板

    (1) 递归继承方式展开参数包

    // 泛化可变参类模板 
    template<typename... args>
    class VarClass {};
    
    // 全特化可变参类模板基类 
    template<>
    class VarClass<>
    {
    public:
        VarClass()
        {
            cout << "VarClass<>(), this = " << this << endl;
        }
    };
    
    // 偏特化可变参类模板 
    template<typename val, typename... args>
    class VarClass<val, args...> : private VarClass<args...> 
    {
    public:
        VarClass(val v, args... vs) : m_i(v), VarClass<args...>(vs...)
        {
            cout << "VarClass<val, args...>, this = " << this << endl;
            cout << v << endl;
        }
        val m_i;
    };
    
    
    int main()
    {
        VarClass<int, double, string> v(1, 2.5, "abc");
            
        return 0;
    }
    

    输出结果:


    image.png
    • 从输出结果可以看出,实例化的对象的首地址是一致的,属于继承关系,继承顺序为:
      → 继承于
      VarClass<int, double, string> → VarClass<double, string> → 继承于 → VarClass<string> → VarClass<>

    在实例化 VarClass<int, double, string> v(1, 2.5, "abc") 时,编译器相当于生成了以下的类:

    // 泛化可变参类模板 
    template<typename... args>
    class VarClass {};
    
    // 全特化可变参类模板基类 
    template<>
    class VarClass<>
    {
    public:
        VarClass()
        {
            cout << "VarClass<>(), this = " << this << endl;
        }
    };
    
    template<>
    class VarClass<string> : private VarClass<>
    {
    public:
        VarClass(string val) : m_val(val), VarClass<>()
        {
            cout << "VarClass<string> : " << m_val << endl;
        }
        
        string m_val;
    };
    
    template<>
    class VarClass<double, string> : private VarClass<string>
    {
    public:
        VarClass(double val, string arg1) : m_val(val), VarClass<string>(arg1)
        {
            cout << "VarClass<double, string>() : " << m_val << endl;
        }
        
        double m_val;
    };
    
    
    template<>
    class VarClass<int, double, string> : private VarClass<double, string>
    {
    public:
        VarClass(int val, double arg1, string arg2) : m_val(val), VarClass<double, string>(arg1, arg2)
        {
            cout << "VarClass<int, double, string>() : " << m_val << endl;
        }
        
        int m_val;
    };
    



    (2) 递归组合方式展开方式展开参数包
    组合关系:类与类之间的关系,其中一个类包含另一个类的对象。

    // 组合关系
    class B
    {
        // todo...  
    };
    
    class A
    {
    public:
        B b;      // A 中包含B对象
    };
    

    通过递归组合方式展开参数包:

    template<typename... Args>
    class VarClass{};
    
    template<typename Val, typename... Args>
    class VarClass<Val, Args...>
    {
    public:
        VarClass(Val val, Args... args) : m_val(val), m_args(args...)
        {
            cout << "VarClass<Val, Args...>(), this : " << this << endl;
            cout << m_val << endl; 
        }
        
        Val m_val;
        VarClass<Args...> m_args;
    };
    
    
    int main()
    {
        VarClass<int, double, string> var(1, 2.5, "hello");
        return 0;
    }
    

    输出结果:


    image.png
    • 通过输出结果,可以看出实例化的对象的地址是不一致的,属于组合关系,组合顺序为:
      → 包含对象
      VarClass<int, double, string> → VarClass<double, string> → VarClass<string> → VarString<>

    实际上在实例化 VarClass<int, double, string> 时,编译器生成了以下几个类:

    template<typename... Args>
    class VarClass{};
    
    template<>
    class VarClass<string>
    {
    public: 
        VarClass(string val) : m_val(val)
        {
            cout << "VarClass<string>() : " << m_val << endl;
        }
        
        string m_val;
        VarClass<> m_args;
    };
    
    template<>
    class VarClass<double, string>
    {
    public:
        VarClass(double val, string arg1) : m_val(val), m_args(arg1)
        {
            cout << "VarClass<double, string>() : " << m_val << endl;
        }
        
        double m_val;
        VarClass<string> m_args;
    };
    
    
    template<>
    class VarClass<int, double, string>
    {
    public:
        VarClass(int val, double arg1, string arg2) : m_val(val) , m_args(arg1, arg2)
        {
            cout << "VarClass<int, double, string>() : " << m_val << endl;
        }
        
        int m_val;
        VarClass<double, string> m_args;
    };
    

    (3) 通过 tuple 和递归调用方式展开参数包
    tuple (元组):各种类型元素的组合

    #include <iostream>
    #include <tuple>
    
    using namespace std;
    
    int main()
    {
        tuple<int, char, string> t(1, 'a', "hello");
        cout << get<0>(t) << endl;
        cout << get<1>(t) << endl;
        cout << get<2>(t) << endl;
        
        return 0;
    }
    

    实现思路:计数器从0开始,每处理一个参数,计数器+1;直到把所有参数处理完成。最后使用一个模板偏特化,作为递归调用结束

    // count从0开始统计, maxcount 表示参数数量 
    template<int count, int maxcount, typename... T>
    class MyTuple
    {
    public:
        static void TupleCount(const tuple<T...>& t)
        {
            cout << "value = " << get<count>(t) << endl;
            MyTuple<count + 1, maxcount, T...>::TupleCount(t);
        }
    };
    
    // 特化版本,结束递归调用 
    template<int maxcount, typename... T>
    class MyTuple<maxcount, maxcount, T...>
    {
    public:
        static void TupleCount(const tuple<T...>& t){}
    };
    
    
    template<typename... T>
    void TupleFunc(const tuple<T...>& mytuple)
    {
        MyTuple<0, sizeof...(T), T...>::TupleCount(mytuple) ;
    }
    
    int main()
    {
        tuple<int, char, string> mytuple(1, 'a', "hello");
        TupleFunc(mytuple);
        
        return 0;
    }
    

    十、模板模板参数

    考虑在类模板中,有成员变量 : vector<T> myt;

    template<typename T>
    class Test
    {
    public:
        void setMyt(T t)
        {
            myt.push_back(t);
        }
        
        T getMyt(size_t i)
        {
            return myt[i];
        }
    
    public:
        vector<T> myt;
    };
    
    int main()
    {
        Test<int> t;
        t.setMyt(1);
        cout << t.getMyt(0) << endl;
        
        return 0;
    }
    

    vector 实际上是一个类模板,现在考虑,把类似vector, list 的类模板,当作模板参数,传入一个类模板中:

    • 此处的 Container 即是模板模板参数
    • vector 和 list,作为模板模板参数时,因为编译器无法确定默认的 allocator<T> 缺省参数,需要使用 using 设定包含vector 和 list 缺省参数的类型,将此类型作为模板模板参数。
    template<typename T>
    using MyVec = vector<T, allocator<T> >;
    
    template<typename T>
    using MyList = list<T, allocator<T> >;
    
    template <typename T, template<typename> class Container>
    class Test
    {
    public:
        void setMyt(T t)
        {
            myt.push_back(t);
        }
        
        T getMyt(size_t i)
        {
            return myt.back();
        }
    
    public:
        Container<T> myt;
    };
    
    int main()
    {
        Test<int, MyVec> t;
        t.setMyt(1);
        cout << t.getMyt(0) << endl;
        
        Test<char, MyList> tlist;
        tlist.setMyt('a');
        cout << tlist.getMyt(0) << endl;
        
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:C++ 模板与泛型

          本文链接:https://www.haomeiwen.com/subject/hfzxxktx.html