C++98仅有一套型别推导,用于函数模板。C++11对这套规则进行了一些改动并增加了两套规则,一套用于auto,一套用于decltype。后来C++14又扩展了能够运用auto和decltype的语境。
下面先重点讲一下auto关键字的使用语境:
请允许我使用一小段伪代码来说明。函数模板的大致形如:
template<Class T>
void f(ParamType param);
而一次调用如下:
f(expr);
在编译期,编译器将会通过expr推导推导T及ParamType的型别,这两个往往不一样。因为ParamType常会包含一些修饰词,例如const或者引用之类的。
下面将分三种情况进行分析:
情况一:ParamType是一个指针或者引用,但不是万能指针或引用(两个引用符号)
此时,型别推导会进行如下操作:
- 若expr是具有引用型别,则先将引用部分忽略
- 然后对expr的型别和ParamType进行型别匹配,进而决定T的型别。
结合以上两句并参考以下代码便可以理解:
template<class 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& 类型
在各个调用时,param及T的推导如下:
f(x); //T是int,param 为 int&
f(cx); //T是const int,param为 const int&
f(rx); //T是const int,param为 const int&
情况二:ParamType是一个万能指针
万能引用的声明型别写作T&&,其会根据实参类型型别,进行不同的处理:
- 如果expr是左值,T和ParamType都会被推导为左值引用,这是T被推导为引用的唯一情况
- 如果expr是右值,则按照常规情况进行处理(情况一)
结合以上两句并参考以下代码便可以理解:
template<class 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& 类型
在各个调用时,param及T的推导如下:
f(x); //x是左值,所以T是int&,param 也为 int&
f(cx); //cx是左值,所以T是const int&,param也为 const int&
f(rx); //x是左值,所以T是const int&,param为 const int&
f(27); //x是右值,所以T是int,param为int&&
情况三:Paramtype即非指针也非引用,按值传递
如果是按值传递,则param相当于一个副本。
- 若expr具有引用型别,则先忽略其引用部分
- 若expr是个const、volatile对象,则也忽略其const、volatile关键字
结合以上两句并参考以下代码便可以理解:
template<class 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& 类型
在各个调用时,param及T的推导如下:
f(x); //T是int,param 也为 int
f(cx); //T是int,param 也为 int
f(rx); //T是int,param 也为 int
这里需要说明一下,虽然cx或者rx是const类型,即不可修改的,虽然此时在f函数中被去掉了const修饰词,但仍然是正确的,因为此时f()的实参是形参的一份拷贝,修改形参并不会改变实参,满足const属性要求
注意以下是难点,初学者可以不必了解:
除了以上三种情况外,还有一种情况,就是指针退化的情况,包括数组退化为数组指针,函数退化为函数指针。
当我们把数组传入到某个数组指针形参时,是可以可以编译通过的,因为数组在传递时,会被退化为数组指针。这符合按值传递的过程。但是如果此时我们把ParamType是一个引用,再向其传入一个数组,此时数组将不会发生退化,及形参就是一个数组的引用。
例如:
template <class T>
std::size_t arraySize(T& param){
return N;
}
int main()
{
int key[] = { 1,2,3,4,5,6,7,8,9 };
arraySize(key); //此时key被推导为 const int(&)[9]
return 0;
}
我们也可以借助这个特性来计算该数组的大小,代码如下:
template <class T,std::size_t N>
std::size_t arraySize(T(&)[N]){ //此时入参是const int(&)[9],按照模式匹配规则,此时N为9
cout << N << endl;
return N;
}
int main()
{
int key[] = { 1,2,3,4,5,6,7,8,9 };
arraySize(key); //此时key被推导为 const int(&)[9]
return 0;
}
同理,函数也是一样,具体逻辑见代码:
int someFunc(int,double);//someFunc是一个函数,类型是:void(int,double)
template <class T>
void f1(T param); //按值传递
template <class T>
void f2(T& param); //按引用传递
f1(someFunc); //此时param被推导为函数指针:void(*)(int,double)
f2(someFunc); //此时param被推导为函数引用:void(&)(int,double)
要点速记:
- 在模板型别推导推导过程中,具有引用型别的实参会被当成非引用型别来处理,换言之,其引用型别将被忽略
- 对万能引用形参进行推导时,左值实参需要特殊处理
- 对按值传递的形参进行推导时,若实参中带有const、volatile修饰词将被忽略
- 在模板型别推导过程中,数组或者函数类型的实参会退化为对应的指针,除非他们形参ParamType是一个引用。