一、理解引用折叠
(一)引用折叠
1. 在C++中,“引用的引用”是非法的。像auto& &rx = x;(注意两个&之间有空格)这种直接定义引用的引用是不合法的,但是编译器在通过类型别名或模板参数推导等语境中,会间接定义出“引用的引用”,这时引用会形成“折叠”。
2. 引用折叠会发生在模板实例化、auto类型推导、创建和运用typedef和别名声明、以及decltype语境中。
(二)引用折叠规则
1. 两条规则
(1)所有右值引用折叠到右值引用上仍然是一个右值引用。如X&& &&折叠为X&&。
(2)所有的其他引用类型之间的折叠都将变成左值引用。如X& &, X& &&, X&& &折叠为X&。可见左值引用会传染,沾上一个左值引用就变左值引用了。根本原因:在一处声明为左值,就说明该对象为持久对象,编译器就必须保证此对象可靠(左值)。
2. 利用引用折叠进行万能引用初始化类型推导
(1)当万能引用(T&& param)绑定到左值时,由于万能引用也是一个引用,而左值只能绑定到左值引用。因此,T会被推导为T&类型。从而param的类型为T& &&,引用折叠后的类型为T&。
(2)当万能引用(T&& param)绑定到右值时,同理,右值只能绑定到右值引用上,故T会被推导为T类型。从而param的类型就是T&&(右值引用)。
【编程实验】引用折叠
#include <iostream> using namespace std; class Widget{}; template<typename T> void func(T&& param){} //Widget工厂函数 Widget widgetFactory() { return Widget(); } //类型别名 template<typename T> class Foo { public: typedef T&& RvalueRefToT; }; int main() { int x = 0; int& rx = x; //auto& & r = x; //error,声明“引用的引用”是非法的! //1. 引用折叠发生的语境1——模板实例化 Widget w1; func(w1); //w1为左值,T被推导为Widget&。代入得void func(Widget& && param); //引用折叠后得void func(Widget& param) func(widgetFactory()); //传入右值,T被推导为Widget,代入得void func(Widget&& param) //注意这里没有发生引用的折叠。 //2. 引用折叠发生的语境2——auto类型推导 auto&& w2 = w1; //w1为左值auto被推导为Widget&,代入得Widget& && w2,折叠后为Widget& w2 auto&& w3 = widgetFactory(); //函数返回Widget,为右值,auto被推导为Widget,代入得Widget w3 //3. 引用折叠发生的语境3——tyedef和using Foo<int&> f1; //T被推导为int&,代入得typedef int& && RvalueRefToT;折叠后为typedef int& RvalueRefToT //4. 引用折叠发生的语境3——decltype decltype(x)&& var1 = 10; //由于x为int类型,代入得int&& rx。 decltype(rx) && var2 = x; //由于rx为int&类型,代入得int& && var2,折叠后得int& var2 return 0; }