• Effective Modern C++[实践]->理解模板类别的推导


    函数模板伪代码如下:

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

    一次调用的形如f(expr);,编译器用 expr 来推导两个类型: TParamType。 这些类型经常是不一样的,因为 ParamType 一般带有限定符,如 const 或引用。
    T 的类型推导不仅仅依赖于expr的类型, 还依赖于 ParamType的格式。具体需要从三个方面讨论:
    如何查看模板类型参数导出的类型的方法
    方式1:
    见如下连接
    How can I see the type deduced for a template type parameter?
    在这里插入图片描述方式2:
    点击网址打开Compiler查看生成的代码,如下
    在这里插入图片描述

    ParamType 是个指针或引用,但不是万能引用

    类型推导步骤为:

    1. expr具有引用类型,先将引用部分忽略
    2. 之后,对expr的类别和ParamType的类别进行匹配来决定T的类别。

    情形1 f(T& a)

    方式1

    使用删除的模板函数

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    //情形1
    template<typename T>
    void f(T& a)=delete ;
    int main() {
        int x = 22;
        const int lx = x;
        const int &rx = x;
        int&& rx1 = 27;
        const int*px = &x;
        int*px1 = &x;
    
        f(x); 		
        f(lx);	
        f(rx);		
        f(rx1);       
        f(px);    
        f(px1);   
        f(*px);     
        f(*px1);    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    编译输出如下:

    <source>: In function 'int main()':
    <source>:17:6: error: use of deleted function 'void f(T&) [with T = int]'
       17 |     f(x);
          |     ~^~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    <source>:18:6: error: use of deleted function 'void f(T&) [with T = const int]'
       18 |     f(lx);
          |     ~^~~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    <source>:19:6: error: use of deleted function 'void f(T&) [with T = const int]'
       19 |     f(rx);
          |     ~^~~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    <source>:20:6: error: use of deleted function 'void f(T&) [with T = int]'
       20 |     f(rx1);
          |     ~^~~~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    <source>:21:6: error: use of deleted function 'void f(T&) [with T = const int*]'
       21 |     f(px);
          |     ~^~~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    <source>:22:6: error: use of deleted function 'void f(T&) [with T = int*]'
       22 |     f(px1);
          |     ~^~~~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    <source>:23:6: error: use of deleted function 'void f(T&) [with T = const int]'
       23 |     f(*px);
          |     ~^~~~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    <source>:24:6: error: use of deleted function 'void f(T&) [with T = int]'
       24 |     f(*px1);
          |     ~^~~~~~
    <source>:8:6: note: declared here
        8 | void f(T& a)=delete ;
          |      ^
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    从输出的结果可看出推导的类型

    方式2

    // Compile with /W4
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    void f(T& a){
        cout<<__func__<<"    "<<a<<endl;
    } ;
    int main() {
        int x = 22;
        const int lx = x;
        const int &rx = x;
        int&& rx1 = 27;
        const int*px = &x;
        int*px1 = &x;
    
        //void f<int>(int&)
        f(x); 		//T  int;		ParamType  int&
        //void f<int const>(int const&)
        f(lx);		//T const int; ParamType  const int&
        //void f<int const>(int const&)
        f(rx);		//T const int; ParamType  const int&
        // void f<int>(int&)
        f(rx1);             //T  int;		ParamType  int&
        //void f<int const*>(int const*&)
        f(px);      //T  const int*;	ParamType const int *&
        //void f<int*>(int*&)
        f(px1);     //T   int*;		ParamType  int*&
        //void f<int const>(int const&)
        f(*px);     //T const int;     ParamType  const int&
        //void f<int>(int&)
        f(*px1);    //T  int;		    ParamType  int&
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    生成的汇编代码如下:

    ...
            mov     rdi, rax
            call    void f<int>(int&)
            lea     rax, [rbp-24]
            mov     rdi, rax
            call    void f<int const>(int const&)
            mov     rax, QWORD PTR [rbp-8]
            mov     rdi, rax
            call    void f<int const>(int const&)
            mov     rax, QWORD PTR [rbp-16]
            mov     rdi, rax
            call    void f<int>(int&)
            lea     rax, [rbp-40]
            mov     rdi, rax
            call    void f<int const*>(int const*&)
            lea     rax, [rbp-48]
            mov     rdi, rax
            call    void f<int*>(int*&)
            mov     rax, QWORD PTR [rbp-40]
            mov     rdi, rax
            call    void f<int const>(int const&)
            mov     rax, QWORD PTR [rbp-48]
            mov     rdi, rax
            call    void f<int>(int&)
            mov     eax, 0
            leave
            ret
     ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    输出结果为

    f    22
    f    22
    f    22
    f    27
    f    0x7ffdfd4ae16c
    f    0x7ffdfd4ae16c
    f    22
    f    22
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结论如下:

    expr f(expr)TParamTypeinstance
    int xf(x)intint&void f<int>(int&)
    const int lx f(lx)const intconst int&void f<int const>(int const&)
    const int &rxf(rx)const intconst int&void f<int const>(int const&)
    int&& rx1f(rx1)intint&void f<int>(int&)
    const int*pxf(px)const int*const int* &void f<int const*>(int const*&)
    int*px1f(px1)int *int* &void f<int*>(int*&)
    const int*pxf(*px)const intconst int&void f<int const>(int const&)
    int*px1f(*px1)intint&void f<int>(int&)

    情形2 f(const T& a)

    代码

    // Compile with /W4
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    void f(const T& a){
        cout<<__func__<<"    "<<a<<endl;
    } ;
    int main() {
        int x = 22;
        const int lx = x;
        const int &rx = x;
        int&& rx1 = 27;
        const int*px = &x;
        int*px1 = &x;
    
        f(x); 		
        f(lx);	
        f(rx);	
        f(rx1);       
        f(px);   
        f(px1);   
        f(*px);    
        f(*px1);   
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    生成汇编代码如下:

    ...
            call    void f<int>(int const&)
    ...
            call    void f<int>(int const&)
    ...
            call    void f<int>(int const&)
    ...
            call    void f<int>(int const&)
    ...
            call    void f<int const*>(int const* const&)
    ...
            call    void f<int*>(int* const&)
    ...
            call    void f<int>(int const&)
    ..
            call    void f<int>(int const&)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    结论

    expr f(expr)TParamTypeinstance
    int xf(x)intconst int&void f<int>(int const&)
    const int lx f(lx)intconst int&void f<int>(int const&)
    const int &rxf(rx)intconst int&void f<int>(int const&)
    int&& rx1f(rx1)intconst int&void f<int>(int const &)
    const int*pxf(px)const int*const int* &void f<int const*>(int const* const&)
    int*px1f(px1)int *int* &void f<int*>(int* const &)
    const int*pxf(*px)intconst int&void f<int>(int const&)
    int*px1f(*px1)intconst int&void f<int>(int const&)

    情形3 f(T* a)

    代码

    // Compile with /W4
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    void f(T* a){
        cout<<__func__<<"    "<<*a<<endl;
    } ;
    
    // template<typename T>
    // void f(T*& a)=delete;
    
    int main() {
        int x = 22;
        const int lx = x;
        const int &rx = x;
        int&& rx1 = 27;
        const int*px = &x;
        int*px1 = &x;
    
        f(&x); 		
        f(&lx);	
        f(&rx);	
        f(&rx1);       
        f(px);   
        f(px1);   
        // f(*px);  //  no matching function for call to 'f(const int&)'
        // f(*px1); //error: no matching function for call to 'f(int&)'  
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    生成的汇编代码如下:

       		 call    void f<int>(int*)
    		 ...
            call    void f<int const>(int const*)
    		...
            call    void f<int const>(int const*)
    		...
            call    void f<int>(int*)
    		...
            call    void f<int const>(int const*)
    		...
            call    void f<int>(int*)
    		...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结论

    expr f(expr)TParamTypeinstance
    int xf(x)intint&*void f<int>(int *)
    const int lx f(lx)const intconst int*void f<int>(int const*)
    const int &rxf(rx)int constconst int*void f<int>(int const*)
    int&& rx1f(rx1)intint*void f<int>(int*)
    const int*pxf(px)const intconst int*void f<int const>(int const*)
    int*px1f(px1)int *int*void f<int*>(int*)

    情形4 f(const T* a)

    与情形2类似不做讨论

    ParamType 是个万能引用

    推导规则会区分左值和右值

    1. expr是左值,TParamType都会被推导为左值
    2. expr是右值,则应用《 ParamType 是个指针或引用,但不是万能引用》规则。
    // Compile with /W4
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    void f4(T&& a)=delete ;
    
    int main() {
        int x = 22;
        const int lx = x;
        const int &rx = x;
        const int && yy = 27;
    
        f4(x); // 左值 :T   int&;	         ParamType int &
        f4(lx);// 左值 :T   const int&;	 ParamType const int&
        f4(rx);// 左值 :T   const int&;	 ParamType const int*
        f4(26);// 右值 :T   int;	 	     ParamType int &&
        f4(yy);// 左值 :T   const int&;	 ParamType const int&
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    编译结果如下

    <source>: In function 'int main()':
    <source>:18:7: error: use of deleted function 'void f4(T&&) [with T = int&]'
    ...
    <source>:19:7: error: use of deleted function 'void f4(T&&) [with T = const int&]'
    ...
    <source>:20:7: error: use of deleted function 'void f4(T&&) [with T = const int&]'
    ...
    <source>:21:7: error: use of deleted function 'void f4(T&&) [with T = int]'
    ...
    <source>:22:7: error: use of deleted function 'void f4(T&&) [with T = const int&]'
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    详见右值引用的相关博客

    ParamType 既非指针也非引用

    template<typename T>
    void f(T a)=delete ;
    
    • 1
    • 2

    这种情况就是按值进行传递,无论下述代码中,传入什么,a都会是它的一个副本,也就是说是一个全新的对象。

    推到规则如下:

    1. expr是引用类别,那么忽略掉
    2. 忽略掉expr的引用特性后,若expr是const对象也忽略,若expr是个volatile对象同样忽略。
    // Compile with /W4
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    void f(T a)=delete ;
    
    
    int main() {
        int x = 22;
        const int lx = x;
        const int &rx = x;
        const int && yy = 27;
        const char*const ptr = "xi_men_chui_xue";
    
        f(x);
        f(lx);
        f(rx);
        f(26);
        f(yy);
        f(ptr); //T 为const char * 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    <source>:20:6: error: use of deleted function 'void f(T) [with T = int]'
    ...
    <source>:21:6: error: use of deleted function 'void f(T) [with T = int]'
    ...
    <source>:22:6: error: use of deleted function 'void f(T) [with T = int]'
    ...
    <source>:23:6: error: use of deleted function 'void f(T) [with T = int]'
    ...
    <source>:24:6: error: use of deleted function 'void f(T) [with T = int]'
    ...
    <source>:25:6: error: use of deleted function 'void f(T) [with T = const char*]'
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注: 在推导过程中,ptr所指内容的常量性会得到保留,但是其自身的常量性被忽略

    数组实参

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    void f(T a){} ;
    
    template<typename T>
    void f1(T& a){} ;
    
    template<typename T,std::size_t N>
    constexpr std::size_t arraySize(T (&)[N])noexcept{
        return N;
    }
    
    int main() {
        const char name[]= "xi men chui da xue";
    
        f(name);
        f1(name);
        arraySize(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    生成的汇编代码如下:

    ...
            call    void f<char const*>(char const*)
    ...
            call    void f1<char const [19]>(char const (&) [19])
    ...
            call    unsigned long arraySize<char const, 19ul>(char const (&) [19ul])
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结论:

    1. ParamType非指针非引用 即按值传递,按值传递给函数模板的数组类别会被推导程指针类别。
    2. 按引用传递给函数模板的会被推导程数组类别,这个类别会包含数组的尺寸

    函数实参

    同数组实参类似。

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    void f(T a){} ;
    
    template<typename T>
    void f1(T& a){} ;
    
    void function(){
    }
    
    int main() {
        const char name[]= "xi men chui da xue";
        f(function);//退化为指针
        f1(function);//推导为函数引用
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    汇编代码如下:

            mov     QWORD PTR [rbp-32], rax
            mov     QWORD PTR [rbp-24], rdx
            mov     DWORD PTR [rbp-17], 6649208
            mov     edi, OFFSET FLAT:function()
            call    void f<void (*)()>(void (*)())
            mov     edi, OFFSET FLAT:function()
            call    void f1<void ()>(void (&)())
            mov     eax, 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    【WinRAR】去除请购买WinRAR许可
    Linux下的文件操作和文件管理
    Java实现FTP下载文件到客户端(浏览器)
    13个 Python 必备的知识,建议收藏
    计算机毕业设计Java我图你秀图片素材交易平台(源码+系统+mysql数据库+Lw文档)
    go慢速入门——函数
    希望所有计算机学生都知道这些宝藏课程
    docker部署最新版nacos(2.2.3)设置登录密码
    Redis实现分布式锁-原理-问题详解
    小程序图形:echarts-weixin 入门使用
  • 原文地址:https://blog.csdn.net/MMTS_yang/article/details/125480367