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){ //param为const int(&)[9] return 0; } 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)