现代C++中被广泛应用的auto
是建立在模板类型推导的基础上的。而当模板类型推导规则应用于auto
环境时,有时不如应用于模板中那么直观。由于这个原因,真正理解auto
基于的模板类型推导的方方面面非常重要。
在c++中声明一个模板函数的伪代码基本如下:
template<typename T>
void f(ParamType param);
它的调用看起来像这样
f(expr); //使用表达式调用f
在编译期间,两个类型T和ParamType
通常是不同的。因为ParamType
包含一些修饰,比如const
和引用修饰符。例如:
template<typename T>
void f(const T& param); //ParamType是const T&
//调用情况
int x = 0;
f(x); //ParamType被推导为const int&,T被推导为int。
所以,T
的类型推导不仅取决于expr
的类型,也取决于ParamType
的类型。
这里有三种情况:
ParamType
是一个指针或引用,但不是通用引用ParamType
是一个通用引用ParamType
既不是指针也不是引用(传值方式(pass-by-value))
测试数据:
用变量、引用、常量、右值引用、对常量的引用、指针、指针常量、常量指针、指向常量的指针常量 、数组来进行测试
int i = 0;
int& Ref = i;
int&& rRef=2;
const int Const = 2;
const int& ConstRef = i;
int* Pointer = &i;
int* const PointerConst = &i;
const int* ConstPointer=&i;
const int* const ConstPointerConst = &i;
char CharArr[]="wosh";
const char ConstCharArr[]="wosh";
推荐一个测试网站,可以查看推导出来的类型。https://cppinsights.io/
1.ParamType
是一个指针或引用,但不是通用引用
1.1ParamType
是一个引用
T&和const T&。
推导规则(得到T的类型):
忽略引用部分,保留cv限定符。
那么,ParmType就一定是左值引用类型,ParmType去掉引用符号后,剩下的就是T类型。
注意的事项:
由于引用的特性,形参就代表实参本身,所以实参expr的cv限定符会被T保留下来的。若传进来的实参是个指针,那param会将实参(即指针)的top-level和low-level const都保存下来的。
(不明白top-level含义的请看常量指针和指针常量, top-level const和low-level const)
1.1.1 ParmType是 T&
测试的模板函数:
template<typename T>
void FunRef(T& param) {}
FunRef(i); // ParamType:int& , T:int
FunRef(Ref); //ParamType:int& T:int,
FunRef(rRef); // ParamType:int& T:int,
FunRef(Const); //ParamType:cosnt int& T:const int,
FunRef(ConstRef); //ParamType:cosnt int& T:cosnt int,
FunRef(Pointer); // ParamType:int* &, T:int*,
FunRef(PointerConst); //ParamType:int* cosnt& T:int* const,
FunRef(ConstPointer); //ParamType:const int* & T:const int*,
FunRef(ConstPointerConst); //ParamType:cosnt int* const &, T:cosnt int* const,
//数组类型对于T&有点特别,不会退化成指针的
FunRef(CharArr); //ParamType:char(&)[5] T:char[5]
FunRef(ConstCharArr); //ParamType:const char(&)[5] T:const char[5]
1.1.2 ParmType是const T&
与T&的区别是ParmType是top-level(顶层)const类型。
那么ParmType去掉顶层const和引用符号就是T类型。(即T一定不是顶层const类型)
测试的模板函数:
template<typename T>
void FunConstRef(const T& param) {}
//以下情况ParmType都是const int& ,T都是int
FunConstRef(i);
FunConstRef(Ref);
FunConstRef(rRef);
FunConstRef(Const);
FunConstRef(ConstRef);
FunConstRef(Pointer); //ParmType: int* const & T:int*,
FunConstRef(PointerConst); // ParmType: int* cosnt& T:int*,
FunConstRef(ConstPointer); // ParmType:const int* const& T:const int*,
FunConstRef(ConstPointerConst); //ParmType:cosnt int* const & T:cosnt int*,
//数组类型和T&类似的
FunConstRef(CharArr); //ParamType:const char(&)[5] T:const char[5]
FunConstRef(ConstCharArr); //ParamType:const char(&)[5] T:const char[5]
1.2 ParamType
是一个指针
指针有4种情况:T*,const T*,T* const,const T* const。
注意事项:
传参时param指针是实参的副本,所以实参和形参是不同的两个指针。T在推导的时候,会保留实参中的low-level的const,舍弃top-level的const。
low-level const修饰的是指针指向的对象,表示该对象不可改变,所以const 应该保留。而top-level const修饰的是指针本身,从实参到形参传递时复制的是指针,因此形参的指针只是个副本,所以不保留其const属性。
1.2.1 ParamType 是 T*
推导规则:
和引用的类似,ParamType一定是non-const类型(传参时忽略top-level const)。
那么,ParamType去掉指针符号就是T类型。
测试的模板函数:
template<typename T>
void FunPtr(T* param) {}
测试结果:
//void FunPtr(T* param)
FunPtr(&i); //ParamType:int* T:int
FunPtr(&Ref); //ParamType:int* T:int
FunPtr(&rRef); //ParamType:int* T:int
FunPtr(&Const); //ParamType:const int* T:cosnt int
FunPtr(Pointer); //ParamType:int* T: int
FunPtr(PointerConst); //ParamType:int* T:int
FunPtr(ConstPointer); //ParamType:const int* T: const int
FunPtr(ConstPointerConst); //ParamType:const int* T: const int
//数组会退化成指针
FunPtr(CharArr); //ParamType:char* T:char
FunPtr(ConstCharArr); //ParamType:const char* T:const char
1.2.2 ParamType 是 T* const
推导规则:
和T*实际是同一个模板的,ParamType只是多了top-level const。T是不变的。
template<typename T>
void FunPtrConst(T* const param) {}
//测试结果
FunPtrConst(&i); //ParamType:int* const T:int
FunPtrConst(&Ref); //ParamType:int* const T:int
FunPtrConst(&rRef); //ParamType:int* const T:int
FunPtrConst(&Const); //ParamType:const int* const T:cosnt int
FunPtrConst(Pointer); //ParamType:int* const T: int
FunPtrConst(PointerConst); //ParamType:int* const T:int
FunPtrConst(ConstPointer); //ParamType:const int* const T: const int
FunPtrConst(ConstPointerConst); //ParamType:const int* const T: const int
//数组会退化成指针
FunPtrConst(CharArr); //ParamType:char* const T:char
FunPtrConst(ConstCharArr); //ParamType:const char* const T:const char
1.2.3 ParamType 是 const T* const 或const T*
推导规则:
- ParamType 是 pointer to const,则T 一定是不带 const 的非指针类型
templete<typename T>
void FunConstPtrConst(const T* const param);
//以下,ParamType都是const int* const T都是int
FunConstPtrConst(&i);
FunConstPtrConst(&Ref);
FunConstPtrConst(&rRef);
FunConstPtrConst(&Const);
FunConstPtrConst(Pointer);
FunConstPtrConst(PointerConst);
FunConstPtrConst(ConstPointer);
FunConstPtrConst(ConstPointerConst);
//数组会退化成指针 ParamType都是const char* const, T都是char
FunConstPtrConst(CharArr);
FunConstPtrConst(ConstCharArr);
templete<typename T>
void FunConstPtr(const T* param);
//以下,ParamType都是const int* T都是int
FunConstPtr(&i);
FunConstPtr(&Ref);
FunConstPtr(&rRef);
FunConstPtr(&Const);
FunConstPtr(Pointer);
FunConstPtr(PointerConst);
FunConstPtr(ConstPointer);
FunConstPtr(ConstPointerConst);
//数组会退化成指针 ParamType都是const char* , T都是char
FunConstPtr(CharArr);
FunConstPtr(ConstCharArr);
2.ParamType是通用引用
- 如果
expr
是左值,T
和ParamType
都会被推导为左值引用。这非常不寻常,第一,这是模板类型推导中唯一一种T
被推导为引用的情况。第二,虽然ParamType
被声明为右值引用类型,但是最后推导的结果是左值引用。 - 如果 expr 是右值,则 ParamType 推断为右值引用类型,去掉 && 就是 T 的类型,即 T 一定不为引用类型
template <typename T>
void f(T&& x);
f(i); //i是左值, ParamType:int& T:int&
f(rRef); //rRef是右值引用,也就是左值 ParamType:int& T:int&
f(Const); //Const是左值 ParamType:const int& T:const int&
f(ConstRef); //ConstRef是左值, ParamType:const int& T:const int&
f(2); //2是右值, ParamType:int&& T:int
3.ParamType
既不是指针也不是引用
通过传值的方式处理,其模板如下:
template<typename T>
void f(T param); //以传值的方式处理param
推导规则:
- 如果
expr
是一个引用,忽略引用部分。 - 如果
expr
带有const
或volatile
,忽略const
、volatile
。
即是丢弃 expr 的 top-level cv 限定符和引用限定符
测试结果:
//void Fun(T param)
//以下 ParamType都是int, T都是int
Fun(i);
Fun(Ref);
Fun(rRef);
Fun(Const);
Fun(ConstRef);
Fun(Pointer); //ParamType:int* T:int*,
Fun(PointerConst); //ParamType:int* T:int*,
Fun(ConstPointer); //ParamType:const int* T:const int*,
Fun(ConstPointerConst); //ParamType:cosnt int* , T:const int*
//数组退化成指针
Fun(CharArr); //ParamType:char* , T:char*
Fun(ConstCharArr); //ParamType: coonst char* , T: const char*
4.特别情况:expr是函数名
数组可以退化为指针,函数也可以退化为指针
void someFunc(int); //someFunc是一个函数,类型是void(int)
template<typename T>
void f1(T param); //传值给f1
template<typename T>
void f2(T & param); //传引用给f2
template<typename T>
void f3(T && param); //传通用引用给f3
template<typename T>
void f4(T* param); //传指针给f4
f1(someFunc); //param被推导为指向函数的指针,类型是void(*)(int),T也是param类型
f2(someFunc); //param被推导为指向函数的引用,类型是void(&)(int),T是void()(int)
f3(someFunc); //someFunc是左值,被推导为左值引用,parm类型是void(&)(int),T和param类型一致
f3(someFunc); //param被推导为指向函数的指针,类型是void(*)(int),T是void()(int)