以下面伪代码为例:
template<class T>
void TemplateFun(ParamType param);
调用:
TemplateFun(expr);
在编译的时候,编译器通过expr来进行推导出两个类型:一个是T的,另一个是ParamType。通常来说这些类型是不同的,因为 ParamType 通常包含一些类型的装饰,比如const或引用特性,T的类型不仅和expr的类型独立,而且还和ParamType的形式独立
template<class T>
void TemplateFun(const T& param)
{
}
第一种调用方式:
{
int nTmp = 0;
TemplateFun(nTmp);
}
T 被推导成int,ParamType被推导成const int&
当 ParamType 是一个引用类型或者是一个指针,但并非是通用的引用。在这种情况下,类型推导的过程如下:
有如下模板函数:
template<class T>
void TemplateFun(T& param)
{
}
调用:
{
int nTmp = 0;
const int nConstTmp = 10;
const int& nConstRefTmp = nTmp;
TemplateFun(nTmp);
TemplateFun(nConstTmp);
TemplateFun(nConstRefTmp);
}
这种调用方式TemplateFun(nTmp);
T是int,param的类型时int&
这种调用方式TemplateFun(nConstTmp);
T是const int,param的类型时const int&
这种调用方式TemplateFun(nConstRefTmp);
T是const int,param的类型时const int&,按照推导过程尽管 nConstRefTmp 的类型是一个引用, T 仍然被推导成了一个非引用的。这是因为 nConstRefTmp 的引用特性会被类型推导所忽略。
当传递一个 const 对象给一个引用参数,我们期望对象会保留常量特性,也就是说,参数变成了const的引用。这也就是为什么给一个以 T& 为参数的模板传递一个 const 对象是安全的:对象的 const 特性是 T 类型推导的一部分。
上述示例展示了左值引用参数的处理方式,但是类型推导在右值引用上也是如此。当然,右值参数只可能传递给右值引用参数,但是这个限制和类型推导没有关系
我们对上面的模板函数修改一下在看看推导出的类型:
template<class T>
void TemplateFun(const T& param)
{
}
调用依然一样
这种调用方式TemplateFun(nTmp);
T是int,param的类型是const int&
这种调用方式TemplateFun(nConstTmp);
T是int,param的类型是const int&
这种调用方式TemplateFun(nConstRefTmp);
T是int,param的类型是const int&
对于指针也一样
template<class T>
void TemplateFun(T* param)
{
}
{
int nTmp = 0;
const int* nConstTmp = &nTmp;
TemplateFun(&nTmp);
TemplateFun(nConstTmp);
}
这种调用方式TemplateFun(&nTmp);
T是int,param的类型是int*
这种调用方式TemplateFun(nConstTmp);
T是const int,param的类型是const int*
对于通用的引用参数,这些参数被声明成右值引用(也就是函数模板使用一个类型参数 T ,一个通用的引用参数的声明类型是 T&& ),但是当传递进去右 值参数情况变得不一样
如果expr是一个左值,T和ParamType都会被推导成左值引用。这有些不同寻常。第一,这是模板类型 T 被推导成一个引用的唯一情况。第二,尽管 ParamType利用右值引用的语法来进行推导,但是他最终推导出来的类型是左值引用
如果 expr是一个右值,那么就执行“普通”的法则(第一种情况ParamType是个非通用的引用或者是一个指针)
例如:
template<class T>
void TemplateFun(T&& param)
{
}
{
int nTmp = 0;
const int nConstTmp = 10;
const int& nConstRefTmp = nTmp;
TemplateFun(nTmp);
TemplateFun(nConstTmp);
TemplateFun(nConstRefTmp);
TemplateFun(100);
}
这种调用方式TemplateFun(nTmp);
nTmp是个左值所以T是int&,param的类型是int&
这种调用方式TemplateFun(nConstTmp);
nConstTmp是个左值所以T是const int&,param的类型是const int&
这种调用方式TemplateFun(nConstRefTmp);
nConstRefTmp是个左值所以T是const int&,param的类型是const int&
而这种调用方式TemplateFun(100);
100是右值所以T是int,param的类型是int&&
以后我们讨论这个例子推导的原因。关键的地方在于通用引用的类型推导法则和左值引用或 者右值引用的法则大不相同。特殊的情况下,当使用了通用的引用,左值参数和右值参数的 类型推导大不相同。这在非通用的类型推到上面绝对不会发生
当 ParamType 既不是指针也不是引用,我们把它处理成by-value,这就意味着param就是完全传给他的参数的一份拷贝——一个完全新的对象。基于这个事实 可以从 expr 给出推导的法则:
例如:
template<class T>
void TemplateFun(T param);
{
int nTmp = 0;
const int nConstTmp = 10;
const int& nConstRefTmp = nTmp;
TemplateFun(nTmp);
TemplateFun(nConstTmp);
TemplateFun(nConstRefTmp);
}
这三种调用方式TemplateFun(nTmp);
、TemplateFun(nConstTmp);
、TemplateFun(nConstRefTmp);
T和param的类型都是int
正如我们刚刚提到到by-value按值传递参数这种形式param 是一个独立的对象,expr不能被修改并不意味着它的一份拷贝不能被修改,所以nConstTmp和nConstRefTmp虽然是const类型,但是param却不是const的
下面这种调用:
const char* const ptr = "pointer";
TemplateFun(ptr);
我们知道位于星号右边的const是表明指针是常量const的:ptr不能被修改指向另外一个不同的地址,并且也不能置成null;星号左边的const表明ptr 指向的——字符串——是const的,也就是说字符串不能被修改。因为是by-value按值传递的,所以ptr的const特性会被忽略,这样param 的推导出来的类型就是const char* ,也就是一个可以被修改的指针,指向一个const的字符串。 ptr指向的东西的 const 特性被 加以保留,但是ptr自己本身的 const 特性会被忽略,因为它要被重新复制一份而创建了一个新的指针param
数组类型和指针类型是不一样的,尽管它们通常看起来是可以替换的。一个最基本的幻觉就是在很多的情况下,一个数组会被退化成一个指向其第一个元素的指针。例如:
const char name[] = "zhangsan";
const char * ptrToName = name;
在这里, const char* 指针ptrToName使用name初始化,实际的name的类型是const char[8] 。这些类型( const char* 和 const char[8] )是不一样的,但是因为数组到指针的退化规则,代码会被正常编译
当以by-value按值传递的方式传递数组参数会被退化成指针类型,例如:
TemplateFun(name);
那么T被推导成const char*
我们修改一下模板函数的定义:
template<class T>
void TemplateFun(T& param);
那么此时的调用:
TemplateFun(name);
此时T最后推导出来的实际的类型就是数组!类型推导包括了数组的长度,所以在这个例子里面,T被推导成了const char [8] ,param的类型被推导成了const char(&)[8]
一个推导出一个数组包含元素长度的模板:
// 在编译的时候返回数组的长度(数组参数没有名字,因为只关心数组包含的元素的个数)
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
这里关于constexpr的使用可以参考这篇文章
关于noexcept的使用可以参考这篇文章
函数类型可以被退化成函数指针,它的退化和数组参数类似:
void ProcessFun(int nParam);
template<class T>
void TemplateFun(T param);
当以by-value按值传递的方式,此时调用TemplateFun(ProcessFun)
param被推导成函数指针void(*)(int)
当模板函数的定义:
template<class T>
void TemplateFun(T& param);
此时调用TemplateFun(ProcessFun)
param被推导成函数指针void(&)(int)
在模板类型推导的时候,有引用特性的参数的引用特性会被忽略
在推导通用引用参数的时候,左值会被特殊处理
在推导按值传递的参数时候, const和volatile参数会被视为非const和非volatile
在模板类型推导的时候,参数如果是数组或者函数名称,他们会被退化成指针,除非是用在初始化引用类型