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

本文详细探讨了C++模板类型推导的过程,通过四个不同情形展示了如何根据函数参数类型推导T和ParamType。情形1中,ParamType为非万能引用的引用或指针,情形2添加了const限定,情形3和4涉及指针。通过删除函数模板和编译器输出,分析了不同类型参数如何影响模板实例化。
摘要由CSDN通过智能技术生成

函数模板伪代码如下:

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

一次调用的形如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);    
}

编译输出如下:

<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 ;
      |      ^

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

方式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&
}

生成的汇编代码如下:

...
        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
 ...

输出结果为

f    22
f    22
f    22
f    27
f    0x7ffdfd4ae16c
f    0x7ffdfd4ae16c
f    22
f    22

结论如下:

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);   
}

生成汇编代码如下:

...
        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&)

结论

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&)'  
}

生成的汇编代码如下:

   		 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*)
		...

结论

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&
}

编译结果如下

<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&]'
...

详见右值引用的相关博客

ParamType 既非指针也非引用

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

这种情况就是按值进行传递,无论下述代码中,传入什么,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 * 
}
<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*]'
...

注: 在推导过程中,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);
}

生成的汇编代码如下:

...
        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. 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);//推导为函数引用

}

汇编代码如下:

        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
Coming to grips with C++11 and C++14 is more than a matter of familiarizing yourself with the features they introduce (e.g., auto type declarations, move semantics, lambda expressions, and concurrency support). The challenge is learning to use those features effectively—so that your software is correct, efficient, maintainable, and portable. That’s where this practical book comes in. It describes how to write truly great software using C++11 and C++14—i.e. using modern C++. Topics include: The pros and cons of braced initialization, noexcept specifications, perfect forwarding, and smart pointer make functions The relationships among std::move, std::forward, rvalue references, and universal references Techniques for writing clear, correct, effective lambda expressions How std::atomic differs from volatile, how each should be used, and how they relate to C++'s concurrency API How best practices in "old" C++ programming (i.e., C++98) require revision for software development in modern C++ Effective Modern C++ follows the proven guideline-based, example-driven format of Scott Meyers' earlier books, but covers entirely new material. "After I learned the C++ basics, I then learned how to use C++ in production code from Meyer's series of Effective C++ books. Effective Modern C++ is the most important how-to book for advice on key guidelines, styles, and idioms to use modern C++ effectively and well. Don't own it yet? Buy this one. Now". -- Herb Sutter, Chair of ISO C++ Standards Committee and C++ Software Architect at Microsoft
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-西门吹雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值