条款24.区分万能引用和右值引用

区分万能引用和右值引用

void f(Widget&& param);	//右值引用

Widget&& var1 = Widget();	//右值引用

auto&& var2 = var1;	//非右值引用

template<typename T>
void f(std::vector<T>&& param);	//右值引用

template<typename T>
void f(T&& param);			//非右值引用

实际上,T&&有两个不同的含义。其中一种含义,是右值引用,它们仅仅会绑定到右值,而其主要的存在理由,在于识别出可移对象。

T&&的另一种含义,它既可以是右值引用,也可以是左值引用。这种引用在源码里看起来像右值引用(即T&&),但是它们也可以表现得像是左值引用(T&)。它的二重性使它们既可以绑定到右值上(右值引用),也可以绑定到左值上(左值引用)。此外,它们还可以绑定到常量(const)和非常量(non-const)的对象上,以及volatile对象或非volatile对象,甚至绑定到那些既带有const又带有volatile饰词的对象。它们几乎可以绑定到万事万物。这种引用称为万能引用

万能引用会出现在两种场景下。最常见的一种是函数模板的形参

比如

template<typename T>
void f(T&& param);	

第二个场景是auto声明

auto&& var2 = var1;

这两个场景的共同之处在于它们都涉及类型推导。在模板f中,param的类型是推导得到的;而在var2的声明语句中,var2的类型也是推导的。

相比之下,这些例子则不涉及类型推导。如果你看到了T&&,却没有涉及到类型推导,那么,你看到的就是个右值引用

void f(Widget&& param);	//不涉及类型推导
						//param是个右值引用

Widget&& var1 = Widget();	//没有类型推导
							//var1是一个右值引用

因为万能引用首先是个引用,所以初始化是必需的万能引用的初始化物会决定它代表的是个左值还是右值引用;如果初始化物是右值,万能引用就会对应到一个右值引用;如果初始化物是左值,万能引用就会对应到一个左值引用。对于函数形参的万能引用而言,初始化物在调用处提供

template<typename T>
void f(T&& param);		//param是个万能引用

Widget w;
f(w);				//左值被传递给f
					//param的类型是Widget&(即一个左值引用)

f(std::move(w));	//右值被传递给f
					//param的类型是Widget&&(即一个右值引用)

若要使一个引用成为万能引用,其涉及类型推导是必要条件,但不是充分条件。引用声明的形式也必须正确无误,并且该形式被限定得很死;必须得正好形如T&&才可以

template<typename T>
void f(std::vector<T>&& param);		//param是右值引用

f被调用时,类型T被推导,但param的类型声明的形式不是T&&,而是std::vector<T>&&,这就排除了param是个万能引用的可能性。因此,param是个右值引用,这个事实在你试图传递一个左值给f时编译器会很乐意为你指出来。

std::vector<int> v;
f(v);		//错误,不能给一个右值引用绑定一个左值

即使是一个const饰词的存在,也足以使一个引用失去成为万能引用的资格

template<typename T>
void f(const T&& param);		//param是一个右值引用

如果你在一个模板内看到一个函数形参的类型写作T&&,你可能会想当然的认为它肯定是个万能引用。不能想当然,因为,“位于模板内”并不能保证“一定涉及类型推导”

考虑下面这个在std::vector内的push_back成员函数

template<class T, class Allocator=allocator<T>>
class vector{
public:
    void push_back(T&& x);
    ...
};

push_back的形参当然具备万能引用的正确形式。但在本例中,并不涉及类型推导。因为push_back作为vector的一部分,如果不存在特定的vector实例,则它也无法存在,该实例的具体类型完全决定了push_back的声明类型。即,给定:

std::vector<Widget> v;

会导致std::vector模板实例化为如下实例

class vector<Widget, allocator<Widget>>
{
public:
    void push_back(Widget&& x);		//右值引用
};

现在可以看得很清楚了:push_back未涉及任何类型推导,这个vector<T>push_back函数的形参声明都是指向T类型的右值引用类型。

作为对比,同样是在std::vector内,和push_back概念上相似的成员函数emplace_back却涉及类型推导。

template<class T, class Allocator=allocator<T>>
class vector{
public:
    template<class... Args>
    void emplace_back(Args&&... args);
    ...
};

其中的类型形参Args独立于vector的类型形参T,所以Args必须在每次emplace_back被调用时进行推导。

emplace_back的类型形参的名字是Args,但它仍然是一个万能引用,万能引用的形式必须是T&&,但没必要一定要取T这个名字

下面的模板是个万能引用,因为其形式type&&正确无误,并且param的类型是推导而来的。

template<typename MyTemplateType>
void someFunc(MyTemplateType&& param);

类型为auto的变量可以是万能引用。更准确地说,类型声明为auto&&的变量是万能引用,因为会发生类型推导,并且肯定有正确的形式T&&auto类型的万能引用不如模板函数参数中的万能引用常见,但是在C++11中常常突然出现。

要点速记

  • 如果函数模板形参具备T&&类型,并且T的类型是推导而来的,或如果对象使用auto&&声明其类型,则该形参或对象就是个万能引用
  • 如果类型声明并没有精确地具备type&&的形式,或者类型推导并没有发生,则type&&就代表右值引用
  • 若采用右值来初始化万能引用,就会得到一个右值引用;若采用左值来初始化万能引用,就会得到一个左值引用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值