其他分享
首页 > 其他分享> > 条款1.类型推导

条款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的形式。具体分为三种情况:

情况1:ParamType是个指针或引用,但不是万能引用

在这种情况下,类型推导会这样运行

模式定义如下

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的引用

在各次调用中,对paramT的类型推导结果如下

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&&

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的类型的规则:

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不可修改,并不能断定其副本也不能修改。

需要重点说明的是,constvolatile仅会在按值形参处被忽略。若形参是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,推导,int,param,引用,类型,条款
来源: https://blog.csdn.net/qq_36553387/article/details/116805749