C++理解模板类型推导
模板类型推导时auto类型推导的基础:应用于auto类型推导时,不像在模板类型推导中那么直观
函数模板我们一般这么使用(伪代码):
template<typename T>
void f(ParamType param);
一般这么调用:
f(expr);
在编译的时候,编译器通过expr来进行推导出T和ParamType
需要注意,T和ParamType是不同的,因为ParamType通常包含一定类型的装饰,例如const
template<typename T>
void f(const T& param);
int x =0;
f(x);
此时,T被推到成int,ParamType被推到成const int &
编译器在对T的类型进行推导时,不仅仅只取决于‘expr’的类型,还取决于‘ParamType’的形式。
主要有三种情况:
①‘ParamType’是指针或者引用,但不是通用引用
什么是通用引用?
在C++11及之后的标准版本里,“右值引用”(如
T&&
)和“左值引用”(如const T&
或T&
),以及普通类型的规则变得更加复杂。当模板形参声明为T&&
形式时,并不是简单地表示这个参数只能接收临时对象(即所谓的“右值”,例如函数返回的新创建的对象、字面常量等)。实际上,在某些条件下它可以绑定到任何表达式——无论是左右值都行;这种情形下的T&&
被称为通用引用
如果expr的类型是一个引用,忽略引用的部分,然后利用expr的类型和ParamType对比去判断T的类型
template<typename T>
void f(T& param);//param是一个引用
int x = 27;
const int cx =x;
const int& rx = x;
f(x);//T是int,param此时推到类型为int&
f(cx);//T是const int,param此时推到类型为const int&
f(rx);//T是const int,param此时推到类型为const int&
上面展示的是左值引用参数的处理方式,右值引用也是如此
如果param是一个指针而不是引用,情况也是类似的
template<typename T>
void f(T*param);//param是一个指针
int x = 27;
const int *px = &x;
f(&x);//T是int,param的类型是int*
f(px);//T是const int,param的类型是const int *
②‘ParamType’是通用引用
通过引用参数的类型声明是T&&,如果expr是一个左值,T和ParamType都会被推到成左值引用(T&&->T&);如果expr是一个右值,那么同第一种情况
template<typename T>
void f(T&& param);//param现在是一个通用的引用
int x =27;
const int cx = x;
const int& rx =x;
f(x);//x是左值,所以T是int&,param的类型也是int&
f(cx);//cx是左值,所以T是const int&,param的类型也是const int&
f(rx);//rx是左值,所以T是const int&,param的类型也是const int&
f(27);//27是右值,所以T是int,param的类型也是int&&
③‘ParamType’既不是指针也不是引用
按pass-by-value处理:
如果expr的类型是个引用,将会忽略引用部分;如果expr是const的,也要忽略掉const.如果是volatile的,也要忽略掉
int x = 27;
const int cx =x ;
const int& rx =x ;
f(x);//T和param的类型都是int
f(cx);//T和param的类型都是int
f(rx);//T和param的类型都是int
如果指针(ptr)自己本身是被按值传递的,则忽略指针本身的const
template<typename T>
void f(T param);//param仍然是按值传递的(pass by value)
const char* const ptr ="WHAT CAN I SAY";//ptr是一个const指针,指向一个const对象
f(ptr);//给参数传递的是一个const char * const类型
param的推导出来的类型就是const char*
数组参数
虽然通常情况下,一个数组会退化成一个指向其第一个元素的指针,但是数组类型的指针类型是不一样的
const char name[]="ASDFGHJKL";//name类型是const char[]
const char* ptrToName = name;//数组被退化成指针
const char* 和const char[]是不一样的类型,但是基于数组到指针的退化原则,代码会被正常编译
但是如果将一个数组传递给一个按值传递的函数模板参数,会发生什么?
template<typename T>
void f(T param);
f(name);
显然不存在这种情况,不能将数组作为函数参数。
合法的写法如下
void myFunc(int param[]);
void myFunc(int* param);
f(name);//name是数组,但是T被推到成const char*
如上所示,数组声明被当作指针声明来处理
可以声明参数是数组的引用
template<typename T>
void f(T& param); //引用参数的模板
f(name); //传递数组给f
T被推到成了const char[],函数f的参数(数组的引用)被推到成const char (&)[]
通过声明数组的引用,可以创造出推导数组长度的模板:
//在编译的时候返回数组的长度(数组参数没有名字,因为只关心数组包含的元素的个数)
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N])noexcept
{
return N;//constexpr和noexcept在随后的条款中介绍
}
int keyVals[]= {1,3,7,9,11,22,35};//keyVals有七个元素
int mappedVals[arraySize(keyVals)];//mappedVals长度也是七
数组替代方案:
std::array<int,arraySize(keyVals)> mappedVals;//mappedVals长度是七
函数参数
函数类型可以被退化为函数指针
void someFunc(int ,double);//someFunc是一个函数,类型是void(int,double)
template<typename T>
void f1(T param);//按值传递
template<typename T>
void f2(T& param);
f1(someFunc);//param被推导成函数指针,类型是void(*)(int,double)
f2(someFunc);//param被推导成函数引用,类型是void(&)(int,double)
要点:
①在模板类型推导期间,引用参数被视为非引用,忽略其引用性质
②在推到通用引用形参的类型时,左值实参会得到特殊处理
③在推导值传递的形参类型时,const和volatile参数被视为非const和非volatile的
④在模板类型推导期间,数组或者函数实参会退化为相应的指针,除非他们用于初始化引用