美文网首页
c++ 理解模板的类型推导

c++ 理解模板的类型推导

作者: zhangzhifei | 来源:发表于2020-06-13 18:20 被阅读0次

    说明

    本文来自《Effective Modern C++》,自己做了个学习笔记,一是加深印象,二是方便后期自己查阅,再来也希望帮助到有需要的同学。泛型编程是c++重中之重,模板是c++泛型编程的一大块,理解模板的类型推导才能更好的掌握c++模板。

    模板形式

    template<typename T>
    void f(ParamType param);
    

    在编译期,编译器会通过函数的实参分别推导T和ParamType的类型,ParamType相对于T,会额外加一些修饰词,如const或引用符号等限定词。

    demo

    1. 模板函数声明
    template<typename T>
    void f(const T& param);
    
    1. 调用函数
    int x = 0;
    f(x);
    

    这里T被编译器推导为int,而ParamType被推导为 const int&。如果我们没有深入的了解,可能就会认为实参类型是什么,T就会被推导为是什么类型,其实T的类型推导还要依赖ParamType

    三种情况

    接下来我们看一下类型推导的三种情况:

    情况 1:ParamType是指针或者引用,而不是万能引用

    这是最简单的情况,注意两点即可:

    1. 如果实参是引用类型,则先忽略引用部分。
    2. 之后,根据实参的类型和ParamType的修饰词来决定T的类型。

    示例 1:
    函数模板

    template<typename T>
    void f(T& param);
    

    变量

    int x = 27;             // x 的类型是 int
    const int cx = x;       // cx 的类型是const int
    const int& rx = x;      // rx 的类型是const int 的引用
    

    各次调用中,对param和T的推导结果如下:

    f(x);        // T 的类型是 int, param 的类型是 int& 
    f(cx);       // T 的类型是 const int, param 的类型是 const int&
    f(rx);       // T 的类型是 const int,param 的类型是 const int&
    

    解释:第一个是我们想当然的结果,很好理解。第二个和第三个调用中,由于函数形参加了引用修饰(T&),所以参数传递的时候,要保留实参的属性,也就是要保持实参对象的不可修改的属性。这也是为什么向持有 T& 类型的模板传入const对象是安全的:该对象的常量性(constness)会成为 T 的类型推导结果的组成部分

    接下来我们将函数形参类型加上const 关键字,看下稍微的区别:因为形参已经被const 修饰,所以推导结果 T 也就不需要const了。
    示例 2:
    函数模板

    template<typename T>
    void f(const T& param);
    

    变量

    int x = 27;             // x 的类型是 int
    const int cx = x;       // cx 的类型是const int
    const int& rx = x;      // rx 的类型是const int 的引用
    

    各次调用中,对param和T的推导结果如下:

    f(x);        // T 的类型是 int, param 的类型是 int& 
    f(cx);       // T 的类型是 int, param 的类型是 const int&
    f(rx);       // T 的类型是 int,param 的类型是 const int&
    

    指针和引用没有区别
    示例 3:
    函数模板

    template<typename T>
    void f(T* param);
    

    变量

    int x = 27;             // x 的类型是 int
    const int *px = &x;     // px 是指向x的指针,类型为 const int *
    

    调用

    f(&x);    // T 的类型是 int, param 的类型是 int *
    f(px);    // T 的类型是 const int, param 的类型是 const int *
    

    情况 2:ParamType 是万能引用

    模板函数形参声明为 T&&(也就是右值引用语法)。两种不同情况:

    1. 如果实参是做是左值,T 和 Paramtype 都会被推导为左值引用。这里与我们所想大不一样:首先,这是在模板类型推导中,T被推导为引用类型行的唯一情况。其次,尽管声明时使用的是有值引用语法,但是推导结果却是左值引用。
    2. 如果实参是右值,那和情况1规则一样

    例子
    函数模板

    template<typename T>
    void f(T&& param);      // param 现在是万能引用
    

    变量

    int x = 27;             // x 的类型是 int
    const int cx = x;       // cx 的类型是const int
    const int& rx = x;      // rx 的类型是const int 的引用
    

    调用

    f(x);    // x 是左值,所以T 的类型是 int &, param 的类型也是 int &
    f(cx);   // cx 是左值,所以T 的类型是 const int &, param 的类型也是 const  int &
    f(rx);   // rx是左值,所以T 的类型是 const int &, param 的类型也是 const  int &
    f(27);   // 27 是左值,所以T 的类型是 int, param 的类型也是 int &&(右值引用)
    

    关键在于,万能引用形参类型的推导会依据实参是右值还是左值。这里传左值时,大家可能不理解为什么T也被推导为引用类型,其实只要简单理解成T&&是一种特殊关键字就行了,此时它不是右值引用,二是万能引用。

    情况 3: ParamType 既非指针也非引用

    这时指的是值传递:

    1. 实参的引用还是忽略。
    2. const、volatile也要忽略。

    例子 1
    函数模板

    template<typename T>
    void f(T param);      // param 现在是值传递
    

    变量

    int x = 27;             // x 的类型是 int
    const int cx = x;       // cx 的类型是const int
    const int& rx = x;      // rx 的类型是const int 的引用
    

    调用

    f(x);    // T 和 param的类型都是 int
    f(cx);   // T 和 param的类型都是 int
    f(rx);   // T 和 param的类型都是 int
    

    这里比较好理解,因为值传递进行实例化新的副本。所以实参原有的属性不应该影响副本。

    这里有有个稍微不好理解的例子 const char * 类型,当然char也可以是int 或者其他。
    例子 2
    函数模板

    template<typename T>
    void f(T param);      // param 现在是值传递
    

    变量

    const char * ptr = "ABC";   // ptr 指向一个常量对象
    

    调用

    f(ptr);    // T 和 param 都是const char *
    

    解释:因为值传递针对的是实参与形参之前的关系,我们不希望改变其他变量也就是 ptr 所指向的常量。

    难点 :数组参数

    为什么说这块难一点,因为我们都只到c/c++数组在传参时都会退化为指针。但是下面的例子则不然。
    例子 1 :值传递
    函数模板

    template<typename T>
    void f(T param);      // param 现在是值传递
    

    变量

    int arr[7] = {};  // arr 长度为7的int类型数组
    

    调用

    f(arr);    // T 和 param 都是 int *
    

    这里例子好理解
    例子 2 :引用传递
    函数模板

    template<typename T>
    void f(T& param);      // param 现在是值传递
    

    变量

    int arr[5] = {};  // arr 长度为7的int类型数组
    

    调用

    f(arr);    // T 是实际数组类型 int [5],  param 被推导为长度为7的int类型数组引用(也就是 int (&)[7] )
               // sizeof(arr) = 20;
    

    是不是很有意思。

    相关文章

      网友评论

          本文标题:c++ 理解模板的类型推导

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