假设右值引用是使用&&声明的,那么假设类型声明中出现&& 表示右值引用似乎是合理的。事实并非如此:
Widget&& var1 = someWidget; // here, “&&” means rvalue reference
auto&& var2 = var1; // here, “&&” does not mean rvalue reference
template<typename T>
void f(std::vector<T>&& param); // here, “&&” means rvalue reference
template<typename T>
void f(T&& param); // here, “&&”does not mean rvalue reference
类型声明当中的“&&”有的时候意味着rvalue reference,但有的时候意味着rvalue reference 或者 lvalue reference (两种都有可能)。因此,源代码当中出现的 “&&” 有可能是 “&” 的意思,即是说,语法上看着像rvalue reference (“&&”),但实际上却代表着一个lvalue reference (“&”)。在这种情况下,此种引用比lvalue references 或者 rvalue references都要来的更灵活。
Rvalue references&&只能绑定到右值上,lvalue references&除了可以绑定到左值上,特殊的常左值引用const…&可以绑定到右值上。
声明中带 “&&” 的,可能是lvalue references 或者 rvalue references 可以绑定到任何东西上。这种引用灵活也忒灵活了,值得单独给它们起个名字。我称它们为 universal references(万能引用或转发引用、通用引用)。
到底 “&&” 什么时候才意味着一个universal reference(万能引用)呢(即,代码当中的“&&”实际上可能是 “&”)
如果一个变量或者参数被声明为T&&,其中T是被推导的类型(T是模板参数 或者 auto 或者使用typedef和decltype的时候也可能会出现),那这个变量或者参数就是一个universal reference(万能引用)。
在实践当中,几乎所有的universal references都是函数模板的参数。因为auto声明的变量的类型推导规则本质上和模板是一样的,所以使用auto的时候你也可能得到一个universal references。
和所有的引用一样,你必须对universal references进行初始化,而且正是universal reference的initializer决定了它到底代表的是lvalue reference 还是 rvalue reference:
从结果来看,万能引用的结果如下
如果用来初始化universal reference的表达式是一个左值,那么universal reference就变成lvalue reference。
如果用来初始化universal reference的表达式是一个右值,那么universal reference就变成rvalue reference。
从中间发生的事来看,中间发生如下
万能引用+引用坍缩(万能引用的推导过程中会发生引用折叠)
对于 template
如果传递过去的实参是右值,T 的推导结果(万能引用的推导结果)是参数的类型本身(是实参类型本身 比如传入的实参是int型的右值—则T被推导为 int中类型 int)。 int&&不会发生引用折叠 T&&的结果自然就是一个右值引用。
例子
template<typename T>
void f(T&& param);
//T是被推导的类型,并且param被申明为T&&所以param变量的表达式值类别为universal references(万能引用)
此时
int a;//左值
f(a);//传入左值 那T&& 万能引用就变为了int& 左值引用
f(int(1));//传入字面量右值 那T&&万能引用就变为了int&&右值引用
区分万能引用
例1
Widget&& var1 = someWidget;
auto&& var2 = var1;
//因为var1是有名的所以var1是一个左值,var2的类型声明是auto&&---T&&,
//T需要被推导,所以它就是一个universal reference。
//并且var2是被 左值var1初始化,所以var2就被自动推导为一个左值引用(Widget&)。
//当一个universal reference开始被lvalue初始化的时候,
//var2就变成了lvalue reference
例2
std::vector<int> v;
...
auto&& val = v[0]; // val becomes an lvalue reference (see below)
//val是auto&&---T&&,T需要被推导,所以它就是一个universal reference。
//v[0]可被取地址所以是个左值, 所以val 就被自动推导为一个左值引用(int&)。
只有在发生类型推导的时候 “&&” 才代表 universal reference 吗。如果没有类型推导,就没有universal reference。这种时候,类型声明当中的“&&”总是代表着rvalue reference。
T&& 万能引用
template
void f(T&& param); // deduced parameter type ⇒ type deduction;
Widget&& 右值引用
void f(Widget&& param);
例3
template<typename T>
void f(std::vector<T>&& param); // “&&” means rvalue reference
//虽然T需要推导,但是不是T&&形式。所以param是个右值引用而不是万能引用
例4
template<typename T>
void f(const T&& param); // “&&” means rvalue reference
//有T&&,但是多了个const。const T&&不是万能引用而是一个右值引用。
Universal references只以 “T&&”的形式出现!即便是仅仅加一个const限定符都会使得“&&”不再被解释为universal reference
例5
template<typename MyTemplateParamType>
void f(MyTemplateParamType&& param); // “&&” means universal reference
//不是说非得叫T,MyTemplateParamType&&--param是个万能引用
例6
template <class T, class Allocator = allocator<T> >
class vector {
public:
...
void push_back(T&& x); // fully specified parameter type ⇒ no type deduction;
... // && ≡ rvalue reference
};
这里, T 是模板参数, 并且push_back接受一个``T&&, 但是这个参数却不是universal reference。
看push_back函数在类外是怎么声明的,就很清楚了。
template
void vector
push_back不能离开std::vector
例7—-6的具体例子
Widget makeWidget(); // factory function for Widget
std::vector<Widget> vw;
...
Widget w;
vw.push_back(makeWidget()); // create Widget from factory, add it to vw
代码中对 push_back 的使用会让编译器实例化类 std::vector
void std::vector
一旦我们知道了类是 std::vector
例8
template <class T, class Allocator = allocator<T> >
class vector {
public:
...
template <class... Args>
void emplace_back(Args&&... args); // deduced parameter types ⇒ type deduction;
... // && ≡ universal references
};
emplace_back 看起来需要多个参数(因为Args和args的声明当中都有…),但重点是每一个参数的类型都需要进行推导。函数的模板参数 Args 和类的模板参数T无关(Args是函数专用的模板 仅仅知道了T依旧不知道Args,所以Args依旧需要被推导,所以Args&&为万能引用),所以即使我知道这个类具体是什么(知道T),但是不知道Args,Args依旧需要推导 所以Args&&是万能引用。
template
void std::vector
实例化了T为Widget但是Args没有被实例化,Args依旧需要被推导所以Args&&是万能引用。
一个绑定到universal reference上的对象可能具有 lvalueness 或者 rvalueness,正是因为有这种二义性,所以催生了std::forward:希望在传递参数的时候,可以保存参数原来的lvalueness 或 rvalueness,即是说把参数转发给另一个函数。
引用折叠
例
template<typename T>
void f(T&& param);//T&&为万能引用
...
int x;
...
f(10); // invoke f on rvalue
f(x); // invoke f on lvalue
因为万能引用的特性,如果用来初始化万能引用的表达式是个左值,那万能引用就变为左值引用。如果用来初始化万能引用的表达式是个右值,那么万能引用就变为右值引用。
10是字面右值,万能引用自动推导为void f(int&& param);—-T被推导为int // f instantiated from rvalue
类型T的 rvalues 被推导为 T—-10是int型的rvalues,则万能引用会将T推导为int
ok没问题
x是左值,c++11以前 万能引用自动推导为
void f(int& && param); // initial instantiation of f with lvalue
类型T的lvalues被推导为T&—x是int型的lvalues,则万能引用会将T推导为int&
c++11以前不行,在c++11以后推出引用折叠解决了这个问题
c++11 f(x);万能引用自动推导为 void f(int& param);
引用折叠只有两条规则(就是上面那张图)
一个右值引用的右值引用会变成 (“折叠为”) 一个 rvalue reference.
所有其他种类的”引用的引用” (i.e., 组合当中含有lvalue reference) 都会折叠为 lvalue reference。引用折叠的解释 也是下面这个例子
对于 template
如果传递过去的实参是左值(不管是类型是左值引用,or 普通左值,or 右值引用类型的左值),T 的推导结果(万能引用的推导结果)是左值引用(比如传入实参是X&,X&&类型的有名左值,X普通非引用类型 —则T都被推导为 其中类型 X的左值引用 X&),那 T&& 的结果仍然是左值引用——即 X& && 坍缩成了X&
如果传递过去的实参是右值(纯右值临时对象or move后的左值—亡值 or static_cast<右值引用>—亡值 ),T 的推导结果(万能引用的推导结果)是参数的类型本身(是实参类型本身 比如传入的实参是X型的右值 or move(左值) or static_cast
int x;
...
int&& r1 = 10; // r1’s type is int&&
int& r2 = x; // r2’s type is int&
当一个变量本身的类型是引用类型时会出一些问题
例1
template<typename T>
void f(T &¶m) {
static_assert(std::is_lvalue_reference<T>::value, "T& is lvalue reference");
cout << "T& is lvalue reference" << endl;
}
int main() {
int x;
int &&r1 = 10;
int &r2 = x;
f(r1);
f(r2);
}
r1 和r2 的类型都被推导为 int&
因为r1和r2都是有名的,都是左值,传入万能引用中进行推导得到的就是左值引用类型的param