条款1.类型推导
作者:互联网
理解模板类型推导
函数模板大致形如
template<typename T>
void f(ParamType param);
而一次调用形如
f(expr);
在编译期,编译器会通过expr
推导两个类型:一个是T
的类型,另一个是ParamType
的类型,这两个类型往往不一样,因为,ParamType
常会包含一些修饰词,如const
或引用符号等限定词。例如,若模板声明如下:
template<typename T>
void f(const T& param); //ParamType是const T&
而调用语句如下:
int x = 0;
f(x); //以一个int调用f
在此例中,T
被推导为int
,而ParamType
则被推导为const T&
T的类型推导结果,不仅仅依赖expr
的类型,还依赖ParamType
的形式。具体分为三种情况:
ParamType
具有指针或引用类型,但不是万能引用ParamType
是一个万能引用ParamType
既不是 指针也不是引用
情况1:ParamType是个指针或引用,但不是万能引用
在这种情况下,类型推导会这样运行。
- 若
expr
具有引用类型,先将引用部分忽略 - 对
expr
的类型和ParamType
的类型执行模式匹配,来决定T的类型
模式定义如下
template<typename T>
void f(T& param); //param是个引用
又声明了下列变量:
int x = 27; //x的类型为int
const int cx = x; //cx的类型是const int
const int& rx = x; //rx是x的类型的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&
在第二个和第三个调用中,请注意,由于cx和rx的值都被指明为const
,所以T的类型被推导为const int
,从而形参的类型就成了const int&
。这一点对于调用者来说至关重要。当人们向引用类型的形参传入const
对象时,它们期望该对象保持其不可修改的属性,也就是说,期望该形参成为const
的引用类型。向持有T&
类型的模板传入const
对象是安全的:该对象的常量性会成为T
的组成部分。
在第三个调用中,即使rx具有引用类型,T也并未被推导成一个引用。原因在于,rx的引用性会在类型推导过程中被忽略。
尽管上述调用语句演示的都是左值引用形参,但是右值引用形参的类型推导运行方式是完全相同的。当然,传给右值引用形参的,只能是右值引用实参,但这个限制和类型推导无关。
将形参类型从T&
改为const T&
,结果会有一点变化。
template<typename T>
void f(const T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); //T的类型是int,param的类型是const int&
f(cx); //T的类型是int,param的类型是const int&
f(rx); //T的类型是int,param的类型是const int&
rx的引用性在类型推导过程中被忽略了。
如果Param
是个指针(或指向const
对象的指针)而非引用,运行方式本质上并无不同。
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*
情况2:ParamType是个万能引用
此类形参的声明方式类似右值引用(即在函数模板中持有类型形参T
时,万能引用的声明类型写作T&&
)
- 如果
expr
是个左值,T
和ParamType
都会被推导为左值引用。 - 如果
expr
是个右值,则应用“常规”(情况1)规则。
template<typename T>
void f(T&& 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&&
万能引用形参的类型推导规则不同于左值引用和右值引用形参,当遇到万能引用时,类型推导规则会区分实参是左值还是右值,而非万能引用是从来不会作这样的区分。
情况3:ParamType既非指针也非引用
当ParamType
既非指针也非引用时,按照值传递。
template<typename T>
void f(T param); //param现在是按值传递
这意味着,无论传入的是什么,param
都会是它的一个副本,也就是一个全新对象,param
会是个全新对象这一事实促成了如何从expr
推导出T
的类型的规则:
- 若
expr
具有引用类型,则忽略其引用部分 - 忽略
expr
的引用性之后,若expr
是个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
即使cx和rx代表const
值,param
仍然不具有const
类型。param
是个完全独立于rx和cx存在的对象,是rx和cx的一个副本。所以,expr
的常量性和挥发性可以在推导param
的类型时加以忽略:仅仅由于expr
不可修改,并不能断定其副本也不能修改。
需要重点说明的是,const
和volatile
仅会在按值形参处被忽略。若形参是const
的引用或指针,expr的常量性会在类型推导过程中加以保留。
考虑以下这种情况,expr
是个指向const
对象的const
指针,且expr
按值传递给param
:
tempalte<typename T>
void f(T param); //param仍按值传递
const char* const ptr = "Fun with pointers"; //ptr是个指向const对象的const指针
f(ptr);
ptr被传递给f时,这个指针本身将会按照比特复制给param。换言之,ptr这个指针自己会被按值传递。按照按值传递形参的类型推导规则,ptr的常量性会被忽略,param的类型会被推导为const char*
。在类型推导的过程中,ptr指向的对象的常量性会得到保留,但其自身的常量性则会以复制方式创建新指针param的过程中被忽略。
数组实参
在很多语境下,数组会退化成指向到其首元素的指针。
const char name[] = ”J. P. Briggs“;
const char* ptrToName = name; //数组退化成指针
但当一个数组传递给持有按值形参的模板时,会怎么样?
tempalte<typename T>
void f(T param);
f(name);
首先,并没有任何的函数形参具有数组类型。
void myFunc(int param[]);
既然数组声明可以按照指针声明方式加以处理,那就意味着myFunc可以等待地声明如下:
void myFunc(int* param);
由于数组形参声明会按照它们好像是指针形参那样加以处理,按值传递给函数模板的数组类型将被推导成指针类型。
也就是说,在模板f的调用中,其类型形参T
会被推导成const char*
f(name); //name是个数组,但T的类型却被推导成const char*
尽管函数无法声明真正的数组类型的形参,它们却能够将形参声明为数组的引用!
template<typename T>
void f(T& param); //按引用方式传递形参的模板
f(name); //向f传递一个数组
在这种情况,T
的类型会被推导成实际的数组类型!这个类型中会包含数组尺寸,在这个例子中,T
的类型推导结果是const char[13]
,而f的形参类型则被推导成const char (&)[13]
。
可以利用声明数组引用这一个能力创造出一个模板,用来推导出数组含有的元素个数。
//以编译期常量形式返回数组尺寸
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
将该函数声明为constexpr
,能够使得其返回值在编译期就可用。从而就可以在声明一个数组时,指定其尺寸和另一个数组相同,而后者的尺寸则从花括号初始化式计算得出。
int keyVals[] = {1,3,7,9};
int mappedVals[arraySize(keyVals)];
相对于内建数组,应该优先选择std::array
std::array<int, arraySize(keyVals)> mappedVals;
函数实参
函数类型也可以退化成函数指针。
void someFunc(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
饰词的类型来处理 - 在模板类型推导过程中,数组或函数类型的实参会退化成对应的指针,除非它们被用来初始化引用
- 在模板类型推导过程中,具有引用类型的实参会被当成非引用类型来处理,换言之,其引用性会被忽略
标签:const,推导,int,param,引用,类型,条款 来源: https://blog.csdn.net/qq_36553387/article/details/116805749