zoukankan      html  css  js  c++  java
  • 第1课 理解函数模板的类型推导

    第1课 理解模板类型推导

    一、函数模板

    (一)  函数模板及调用形式

    template<typename T>
    
    void f(ParamType param); //注意这里是ParamType而不是T。这两者可能不一样!
    
    f(expr); //调用形式,以实参expr调用f

    (二)讨论:

        ①T和ParamType的类型往往不一样。因为ParamType常包含一些修饰词,如const或引用符号等限定词。

        ②T的类型,不仅仅依赖于实参expr的类型,还依赖于ParamType的类型

        ③ParamType的形式可分为三种情况:A. ParamType是个指针或引用类型(非万能引用)。B. ParamType是一个万能引用。C. ParamType既非指针也非引用。

    二、函数模板的推导规则

    (一)规则1:ParamType是个指针或引用(但非万能引用),即T*或T&等

     1、两条规则:

        ①若expr是个引用类型,先将其引用忽略

        ②然后对expr的类型和ParamType的类型进行模式匹配,来决定T的类型。

       2、注意事项:

        ①ParamType是个引用类型。由于引用的特点,即形参代表实参本身所以实参expr的CV属性会被T保留下来。(注意,如果传进来的实参是个指针,则param会将实参(指针)的顶层和底层const的都保留下来)。

        ②当ParamType为T*指针,传参时param指针是实参的副本,因此实参和形参是两个不同的指针。T在推导中,会保留实参中的底层const,而舍弃顶层const。因为底层const修饰的是指针所向对象,表示该对象不可更改,所以const应保留,而顶层const表示指针本身,从实参到形参传递时复制的是指针,因此形参的指针是个副本,无须保留const属性。(如,const char* const ptr中,顶层const指修饰ptr的const,即*号右侧const,而底层const指*号左侧的const)

    (二)规则2:ParamType为万能引用,即T&&

    1、两条规则:

        ①如果实参expr是个左值,T和ParamType会被推导为左值引用。(注意,这是模板类型推导中,T唯一被推导为引用(T&)的情形

        ②如果要实参expr是个右值,则应用“规则1”来推导。 此时的T被推导为T

    2、注意事项:

        ①当实参为左值时T被推导为T&(不是T类型),表示形参是实参的引用,即代表实参本身。因此指针的顶层和底层const属性均会被保留

        ②形如const T&&或vector<T>&&)均属于右值引用,因为const的引用会剥夺引用成为万能引用的资格,因为由其定义的变量再也不能成为非const类型,所以不是“万能”的类型,而后者己经确定是个vector<T>类型,不可能成为“万能”的类型,如不会再是int型,因此也不是万能引用。(详见《万能引用》一节)

        ③在推导过程中会发生引用折叠(详见《引用折叠》一节)。

    (三)规则3:ParamType是个非指针也非引用类型(即按值传递

    1、推导规则:

        ①若实参是个引用类型,则忽略其引用部分,同时cv属性被忽略。(因为按值传递,采用复制手段,形参是个副本,与实参是两个不同的参数)。

        ②如果实参是指针类型,从实参到形参传递时,传用的是按比特位复制,因此形参也是个副本,只保留指针的底层const,而舍弃顶层const)

        ③其他情况也是按值传递,形参同样也是一个副本

    【编程实验】函数模板的推导规则

    #include <iostream>
    #include <boost/type_index.hpp>
    
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    
    //辅助类模板,用于打印T和param的类型
    template <typename T, typename Param>
    void printType()  
    {
        cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", ";
        cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl;
    }
    
    /***************************规则1:当ParamType引用或指针时*****************************/  
    //(1)当ParamType为T&时
    template <typename T>
    void func_r(T& param)  //由于T是个引用,操作的是变量/对象本身,当如果实参带有const或volatile属性,会被T保留下来
    {                      //。如果实参是个指针,param代表实参本身,会将顶层和底层const全部保存一下。
        printType<T, decltype(param)>();
    }
    
    //(2)当ParamType为const T&时
    template <typename T>
    void func_cr(const T& param)  //由于假定param是个const引用,为避免重复,T的推导结果就没必要包含const了。
    {
        printType<T, decltype(param)>();
    }
    
    //(3)当ParamType为右值引用时
    template <typename T>
    void func_rr(const T&& param)  //注意由于T&&被const修饰,为右值引用(而非万能引用)
    {
        printType<T, decltype(param)>();
    }
    
    //(4)当ParamType为T*指针时(注意const char* const ptr)
    //顶层const:指ptr指针变量本身的const属性(T推导时被舍弃)
    //底层const:指ptr指针所指向对象/变量的const属性(被T保留)
    template <typename T>
    void func_p(T* param)  //当param是个指针时,如果实参带const,则表示所指对象具有const属性,因此其底层const会被T保留
    {
        printType<T,decltype(param)>();
    }
    
    //(3)当ParamType为const T*指针时
    template <typename T>
    void func_cp(const T* param)  //如果实参带有const,为避免重复,T的推导就没有必要包含const
    {
        printType<T, decltype(param)>();
    }
    
    /***************************规则2:当ParamType为万能引用时,即T&& *****************************/
    template<typename T>
    void func_ur(T&& param)
    {
        printType<T, decltype(std::forward<T>(param))>();
    }
    
    template<typename T>
    void func_cr2(const T&& param) //注意这是右值引用
    {
        printType<T, decltype(param)>();
    }
    
    /***************************规则3:当ParamType为非指针和引用类型时 *****************************/
    template<typename T>
    void func_v(T param)
    {
        printType<T, decltype(param)>();
    }
    
    int main()
    {
        int x = 27;
    
        const int cx = x;
        const int& rx = x;
        const int* const ptr = &x;
    
        cout << "****************************规则1*****************************" << endl;
        //ParamType为引用: void func_r(T& param)
        func_r(x);   //T = int, param = int&
        func_r(cx);  //T = const int, param = const int&
        func_r(rx);  //T = const int, param = const int&
        func_r(ptr); //T = const int*, param = const int* const; //这里表示实参(ptr)的引用, 即代表实参本身,因此ptr的顶、底层const均保留
    
    //Param为const T&: void func_cr(const T& param) func_cr(x); //T = int, param = const int& func_cr(cx); //T = int, param = const int& func_cr(rx); //T = int, param = const int& //Param为指针: void func_p(T* param) func_p(&x); //T = int, param = int* func_p(&cx); //T = const int, param = const int* func_p(ptr); //T = const int, param = const int* //只保留底层const。(注意形参param是实参的副本,即将ptr按比特位复制给param, //因为是副本,所以只保留底层const) //Param为const T* : void func_cp(const T* param) func_cp(&x); //T = int, param = const int* func_cp(&cx); //T = int, param = const int* func_cp(ptr); //T = int, param = const int* //只保留底层const //ParamType为右值引用: void func_rr(T&& param) func_rr(10); //T = int, param = int&& func_rr(std::move(cx)); //T = int, param = const int&& func_rr(std::move(rx)); //T = int, param = const int&& cout << "****************************规则2*****************************" << endl; //void func_ur(T&& param); 注意,当实参为左值时,T一定是一个引用类型(左值引用),这是T被推导为引用的唯一情形 func_ur(x); // T = int&, param = int& func_ur(cx); // T = const int&, param = const int& //T为引用,会保留cv属性 func_ur(rx); // T = const int&, param = const int& func_ur(ptr); // T = const int* const&, param = const int* const& //顶层和底层const均保留 func_ur(10); // T = int, param = int&& 实参为右值,T被推导为int,注意不是int&& func_ur((int&&)10); // T = int, param = int&& 实参为右值,T被推导为int,注意不是int&& //func_cr2(x); // 编译不通过,因为const T&&代表右值引用(而非万能引用),必须传入右值。 func_cr2(10); // T = int, param = const int&& 实参为右值,T被推导为int cout << "****************************规则3*****************************" << endl;
        //按值形参: void func_v(T param);  
    func_v(ptr); //T = const int*, param = const int* (注意只保留底层const,即实参本身的const舍弃)

    return 0;
    }

    三、数组和函数实参的推导规则

    (一)推导规则:

    (1)当函数模板为按值形参时(如T param):数组和函数类型均退化成指针类型。(如char*、void(*)(int, double)。

        //由于数组到指针的退化规则,以下两个函数等价的,所以不能同时声明这两个同名函数。

        void myFunc(int param[]);

        void myFunc(int* param);

    (2)当函数模板为引用类型形参时(如T&  param):则数组和函数分别被推导为数组引用和函数引用类型。特别值得注意,数组引用类型会包含数组元素类型及大小信息,如const char(&)[13]。而函数引用类型如void(&)(int, double)

    (二)数组引用的妙用:用于推导数组元素个数

    【编程实验】数组实参与函数实参的推导

    #include <iostream>
    #include <boost/type_index.hpp>
    
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    
    //辅助类模板,用于打印T和param的类型
    template <typename T, typename Param>
    void printType()
    {
        cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", ";
        cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl;
    }
    
    //数组和函数实参的推导
    template<typename T>
    void func_v(T param)  //按值形参
    {
        printType<T, decltype(param)>();
    }
    
    template<typename T>
    void func_r(T& param)  //按引用形参
    {
        printType<T, decltype(param)>();
    }
    
    void test(int x, double y)
    {
    }
    
    //数组引用的妙用:获得数组元素的个数
    template<typename T, std::size_t N>
    constexpr std::size_t arraySize(T (&)[N]) noexcept //注意形参是个数组引用
    {
        return N;
    }
    
    int main()
    {
        const char name[] = "SantaClaus"; //name的类型为const char[11];(含)
        const char* pName = name;         //数组退化为指针
    
        /********************************数组和函数实参的推导****************************************/
        func_v(name);  //T = const char*, param = const  char*(数组退化为指针,因此T是指针类型)
        func_r(name);  //T = const char[11], param = const char(&)[11]
    
        func_v(test);  //T = void(*)(int, double), param = void(*)(int, double) (函数名退化为指针,因此T是指针类型)
        func_r(test);  //T = void(int, double), param = void(&)(int, double) (注意T的类型)
    
        /***********************************数组引用的妙用******************************************/
        int keyValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        int mapVals[arraySize(keyValues)]; //arraySize返回值是编译期常量,可用于定义数组大小
    
        cout << arraySize(keyValues) << endl; //9
        cout << arraySize(mapVals) << endl;   //9
        cout << arraySize(name) << endl;      //11
    }
  • 相关阅读:
    JQuery 日历控件
    恢复xp_cmdshell SQL Server阻止了对组件 'xp_cmdshell' 的过程'sys.xp_cmdshell' 启用
    XML基础总结
    09.09.16总结
    草根
    设计模式学习总结
    检索 COM 类工厂中 CLSID 为{0002450000000000C000000000000046} 的组件时失败,原因是出现以下错误: 80070005。
    09.09.22总结
    Singleton 单态模式
    【Tomcat源码学习】5.请求处理
  • 原文地址:https://www.cnblogs.com/5iedu/p/11183878.html
Copyright © 2011-2022 走看看