万能引用,引用折叠,右值变左值的情况


万能引用

左值引用只能引用左值,右值引用只能引用右值。
但是对于一个函数我们有时候并不清楚传入的参数是左值还是右值,这时候就需要写两个同名的函数重载,而万能引用的出现解决了这个问题。

万能引用需要和模板配合使用

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

如果一个变量或者参数被声明为T&&,其中T是被推导的类型,那这个变量或者参数就是一个万能引用。
当传入的参数是左值的时候,函数就会实例化出一个左值引用的函数,当传入的参数是右值的时候,函数就会实例化出一个右值引用的函数。

以下这种形式并不是万能引用:

template<typename T>
void f(std::vector<T>&& param);     // “&&” means rvalue reference

template<typename T>
void f(const T&& param);               // “&&” means rvalue reference

引用折叠

使用万能引用或者在其他类型推导的场景时会遇到一个问题,那就是我们传入的参数是一个引用类型,而接收的函数模板也是引用类型,这就会出现引用的引用。例如当模板参数T为Vector&或Vector&&,模板函数形参为T&&时,展开后变成Vector& &&或者Vector&&。

引用折叠 & &&
& & &
&& & &&
  • 如果传递过去的参数是左值,T 的推导结果是左值引用,也就是T&,那 T&&& 的结果仍然是左值引用——即T&,&& 折叠成了&
  • 如果传递过去的参数是右值,T 的推导结果是右值引用,但其推导类型为T(而不是T&&),最终T&&自然就是右值引用。

这也就是万能引用既能引用右值也能引用左值的原因。

其他类型推导的情况,比如auto:

Widget&& var1 = someWidget;      // var1 is of type Widget&& 
 
auto&& var2 = var1;              // var2 is of type Widget& 

var1 的类型是 Widget&&,但是在推导 var2 类型的时候被忽略了其引用成分,var1 这时候就被当做 Widget。
又因为var1是一个左值,其在初始化var2时,auto会自动推导var2类型为Widget&:

Widget& && var2 = var1;         

而在引用折叠之后,就变成了:

Widget& var2 = var1;         

右值变左值的情况

当我们使用了万能引用时,即使可以同时匹配左值、右值,但需要转发参数给其他函数时,会丢失引用性质。
这主要是因为形参是个左值,从而无法判断到底匹配的是个左值还是右值。

因为右值是无法取地址的,而一旦这个右值被右值引用,该引用可以被取地址,所以此时这个右值被当做是左值。

void f(int& param) 
{
    cout << "int&" << endl;
}

void f(int&& param) 
{
    cout << "int&" << endl;
}

int main() {
    int&& r1 = 10;

    cout << &r1 << endl;//右值引用可以取地址,右值引用是左值
   // cout << &10 << endl;//错误
    f(r1);//int&
}
void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; }
void Fun(const int& x) { cout << "const lvalue ref" << endl; }
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }
template<class T>
void PerfectForward(T&& t) 
{ 
	//这里的t是一个左值,因此即使t为右值引用,依然会调用左值引用的Fun
	Fun(t); 
}
int main()
{
	PerfectForward(10); 
	int a;
	PerfectForward(a); 
	PerfectForward(std::move(a)); 
	const int b = 8;
	PerfectForward(b); 
	PerfectForward(std::move(b)); 
	return 0;
}

这也就是完美转发需要解决的问题。
值得注意的是,如果需要多次转发参数,每次转发都要使用完美转发。

void Fun2(int& x)
{ 
	cout << "Fun2 lvalue ref" << endl; 
}
void Fun2(int&& x) 
{
	cout << "Fun2  rvalue ref" << endl; 
}
void Fun1(int& x)
{
	cout << "Fun1 lvalue ref" << endl;
	Fun2(std::forward<int>(x));
}
void Fun1(int&& x)
{
	cout << "Fun1 rvalue ref" << endl;
	Fun2(std::forward<int>(x));
}
template<class T>
void PerfectForward(T&& t)
{
	Fun1(std::forward<T>(t));
}
int main()
{
	PerfectForward(10);
	int a;
	PerfectForward(a); 
	return 0;
}

在这里插入图片描述


参考资料:
透彻理解C++11 移动语义:右值、右值引用、std::move、std::forward
现代C++之万能引用、完美转发、引用折叠(万字长文)

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>