自C++98开始,C++就具备了类型自动推导的能力。在11版本发布之前,C++只有一种类型推导方法,即模板类型推导。在C++11中额外增加了2种类型推导方法——使用auto、decltype关键字。
“C++型别(类型)推导”系列文章会结合代码和实际编译环境的输出来分别说明以上三种情况的型别推导规则,由于内容,篇幅有些多,准备分为3篇文章分别对C++的3种型别推导进行记录,本文先完整介绍template对型别的推导规则。
书上将测试推导的工具和方法放在了最后讲,我这里提到最开始来介绍以方便大家测试:
作者首先提倡读者应该在脑海中牢记这些推导规则,其次也推荐了利用IDE编写让编译器报错的一些代码等方式来查看类型推导结果,如:
template<typename T>
class TD;//只声明一个模板类,不定义它,一旦该类被使用,编译器就会报错。
int main(){
const int theAnswer = 42;
auto x = theAnswer;
auto y = &theAnswer;
TD<decltype(x)> xType;//error C2079: “xType”使用未定义的 class“TD<int>”
TD<decltype(y)> yType;//error C2079: “yType”使用未定义的 class“TD<const int *>”
return 0;
}
从编译器的报错中,我们能清楚地看到类型推导的结果。
下面所有推导过程都以以下代码为大致模型:
template <typename T>
void f(ParamType param);
……
f(expr);
一、模板型别推导
这种方式分为三种情况:
1.ParamType是指针或者引用类型,但不是万能引用。
2.ParamType是一个万能引用。
3.ParamType既不是指针也不是引用。
(注意,ParamType是形参,而不是指的实参)
这里提到的“万能引用”书中没有立即细致说明,这里我简单介绍一下:
所谓“万能引用”,就是既不是左值引用也不是右值引用的引用,并且它最终既可以做左值引用也可以做右值引用。形如:T&&,形状看起来和右值引用没啥区别,但有一个简单的区分:长这个样子的,只要涉及型别推导,那么它就是一个万能引用,否则就是右值引用,举例:
void f(Widget&& param);//param是右值引用,因为它的类型已经确定,不涉及型别推导
Widget&& var1 = Widget();//同上
auto&& var2 = var1;//万能引用
template<typename T>
void f(std::vector<T>&& param);//右值引用
template<typename T>
void f(T&& param);//万能引用
万能引用的作用就是当你传入左值,那么它最终就是左值引用,传入右值,那么它就是右值引用。
接着第一种情况说起,如果ParamType(形参)是一个指针或者引用(非万能引用),那么模板型别推导的最终结果会忽略掉实参的引用部分(如果有)。当param是一个引用,如:
template<typename T>
void f(T& param);
int x = 10;
const int cx = x;
const int& rx = x;
f(x);//param为int&
f(cx);//param为cosnt int&
f(rx);//param为cosnt int&(实参的&被忽略了)
//以上的形参是左值引用,当改成右值引用时,情况一样
如果把param加一个const修饰: const T& 如:
template<typename T>
void f(const T& param);//加了const
int x = 10;//同上
const int cx = x;//同上
const int& rx = x;//同上
f(x);//param为const int&
f(cx);//param为cosnt int&
f(rx);//param为cosnt int&(实参的&被忽略了) 同上
//以上的形参是左值引用,当改成右值引用时,情况一样
结果平淡无奇,而当param是一个指针时,和引用的推导是一模一样的:
template<typename T>
void f(T* param);//param是一个指针了
int x = 10;
const int cx = x;
const int* px = &x;
f(x);//param为int*
f(px);//param为cosnt int*
没有什么特别的地方……
第二种情况,当ParamType是万能引用T&&
template<typename T>
void f(T&& param);//param现在是个万能引用
int x = 10; //同前
const int cx = x;//同前
const int& rx = x;//同前
f(x);//因为x是左值,所以param也为左值,int&
f(cx);//因为x是左值,所以param也为左值,cosnt int&
f(rx);/因为x是左值,所以param也为左值 ,cosnt int&(实参的&被忽略了)
f(10);//因为10是右值,所以param也为右值,int&&
第三种情况,当ParamType既不是引用也不是指针时(pass by value):
首先,模板型别推导会像之前的情况一样,忽略掉实参的引用部分。
其次,按值传递意味着param对象将是实参的一个副本,由于对副本的修改并不会影响原有对象,所以如果原有对象(实参)具有const、valatile的修饰,也会一并忽略掉。
template<typename T>
void f(T param);//param是实参的一个副本
int x = 10; //同前
const int cx = x;//同前
const int& rx = x;//同前
f(x);//param是int
f(cx);//param是int,忽略掉了const
f(rx);//param是int,忽略掉了const和&
这里有一个复杂一点,但并不会产生违背以上规则的例子:
如果实参是一个指向const的const指针,如:
template<typename T>
void f(T param);//param是实参的一个副本
int x = 10; //同前
const int* const px = &x;
f(px);//param是const int*
px的第二个const 是指 px本身不允许再发生任何更改,即不能再指向其它任何变量。它的第一个const是指,它指向的这个变量不可以再被修改。由于是按值传递,根据规则,型别的推导会忽略掉实参的const属性,而实参的类型是一个 const的指向const int的指针,所以px自己的const(右边的)会被忽略,于是param的类型变成了 const int。
这里要补充一下另外的特殊情况,那就是当实参类型为数组和函数的时候,需要注意:
C++中,数组名可以退化成指针,函数也一样,一个函数类型是可以退化成函数指针的。如下:
template<typename T>
void f1(T param);
template <typename T>
void f2(T& param);
int arr = {0,1,2,3};
void someFunc(int,double);
f1(arr);//arr会退化成指针,即param的类型为int*
f2(arr);//这里很有意思,param会被推导为 int (&)[4]
//和上面的情况相比好处是param包含了数组的元素数量信息,可以获取到数组的成员数。
f1(someFunc);//param类型为函数指针 void (*)(int,double)
f2(someFunc);param类型为函数引用 void (&)(int,double)
到这里,template的型别推导规则就说完了,下一篇文章将会介绍auto的型别推导规则。
网友评论