-
universal reference
:If a variable or parameter is declared to have type
T&&
for some deduced type T, that variable or parameter is auniversal reference
.-
deduced type T
Template parameter、auto declaration, ect.
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
-
deduced type
限制了universal reference
的使用场景。
- 在实践中,几乎所有的通用引用都是函数模板的参数。
- 由于
auto
声明的变量的类型推导规则与模板的规则基本相同,因此也可以使用auto
声明的universal reference
。 universal reference
也可能与typedef
和decltype
有关。
和所有引用一样,universal reference
必须初始化,并且universal reference
的初始化式决定了它是表示左值引用还是右值引用。
- If the expression initializing a universal reference is an lvalue, the universal reference becomes an lvalue reference.
- If the expression initializing the universal reference is an rvalue, the universal reference becomes an rvalue reference.
只有能够区分左值和右值时,此信息才有用。很难对这些术语进行精确的定义(c++ 11标准通常会根据具体情况指定表达式是左值还是右值).
但在实践中,以下内容就足够了:
- If you can take the address of an expression, the expression is an lvalue.
- If the type of an expression is an lvalue reference (e.g., T& or const T&, etc.), that expression is an lvalue.
- Otherwise, the expression is an rvalue. Conceptually (and typically also in fact), rvalues correspond to temporary objects, such as those returned from functions or created through implicit type conversions. Most literal values (e.g., 10 and 5.3) are also rvalues.
-
举例:
Widget&& var1 = someWidget; auto&& var2 = var1;
- 可以取到
var1
的地址,故var1
是lvalue
; auto&&
可得var2
的类型是universal reference
;由于其被lvalue (var1)
初始化,故var2
成了一个lvalue reference
template<typename T> void f(T&& param);
f(10); // 10 is an rvalue
- 字面量10 是
rvalue
, 故void f(T&& param);
中以rvalue
初始化,故参数中的T&&
(universal reference
)变成一个rvalue reference
.在此例中具体是int&&
.
- 字面量10 是
int x = 10; f(x); // x is an lvalue
- x是
lvalue
, 故void f(T&& param);
中以lvalue
初始化,故参数中的T&&
(universal reference
)变成一个lvalue reference
.在此例中具体是int&
.
- x是
- 可以取到
Remember that “&&” indicates a universal reference only where type deduction takes place
常见例子:
template<typename T>
void f(T&& param); // deduced parameter type ⇒ type deduction;
// && ≡ universal reference
template<typename T>
class Widget {
...
Widget(Widget&& rhs); // fully specified parameter type ⇒ no type deduction;
... // && ≡ rvalue reference
};
template<typename T1>
class Gadget {
...
template<typename T2>
Gadget(T2&& rhs); // deduced parameter type ⇒ type deduction;
... // && ≡ universal reference
};
void f(Widget&& param); // fully specified parameter type ⇒ no type deduction;
// && ≡ rvalue reference
疑难例子:
template<typename T>
void f(std::vector<T>&& param); // “&&” means rvalue reference
-
此处,
&&
为右值引用,而非万能引用(universal reference
),因为其参数不是T&&
,而是std::vector<T>&&
。 -
universal reference
只会出现在T&&
, 加const
修饰都不可以。
template<typename T>
void f(const T&& param); // “&&” means rvalue reference
template <class T, class Allocator = allocator<T> >
class vector {
public:
...
void push_back(T&& x); // fully specified parameter type ⇒ no type deduction;
... // && ≡ rvalue reference
};
-
此处的
&&
仍然是右值引用,是因为此处并未用到type deduced
:template <class T> void vector<T>::push_back(T&& x);
这是
push_back()
的简略声明表示,可以看到,当进行到T&&时,T的类型已经完全确定,不在进修类型推导了(vector<T>
)。
但在emplace()
中是universal reference
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 };
// 展开 template<class... Args> void std::vector<Widget>::emplace_back(Args&&... args);
容易看出,
emplace
的不确定参数类型,使得即使确定了T
的类型,也需要逐个推导每个args
的类型。(type deduction
)因此是universal references
.
表达式的左值或右值与其类型无关。 考虑类型为int的类型。 存在类型为int的左值(例如,声明为int的变量),并且存在类型为int的右值(例如,像10这样的字面量)。 用户定义的类型(例如Widget)是相同的。
Widget
对象可以是左值(例如,Widget
变量)或右值(例如,从创建widget的工厂函数返回的对象)。 表达式的类型不会告诉您它是左值还是右值。
由于表达式的左值性或右值性与它的类型无关,因此有可能具有类型为右值引用的左值,也可能具有右值引用类型的右值。
Widget makeWidget(); // factory function for Widget
Widget&& var1 = makeWidget() // var1 is an lvalue, but
// its type is rvalue reference (to Widget)
Widget var2 = static_cast<Widget&&>(var1); // the cast expression yields an rvalue, but
// its type is rvalue reference (to Widget)
// <=> Widget var2 = std::move(var1);
template<typename T>
class Widget {
...
Widget(Widget&& rhs); // rhs’s type is rvalue reference,
... // but rhs itself is an lvalue
};
template<typename T1>
class Gadget {
...
template <typename T2>
Gadget(T2&& rhs); // rhs is a universal reference whose type will
... // eventually become an rvalue reference or
}; // an lvalue reference, but rhs itself is an lvalue
在Widget
的构造函数中,rhs
是一个右值引用,因此它绑定到一个右值(即,将一个右值传递给了它),但是rhs本身是一个左值,因此,如果我们想将其转换回右值。 利用其绑定的右值性。 这样做的动机通常是将其用作移动操作,使用std :: move
。
Gadget
的构造函数中的rhs
是universal reference
,因此它可能绑定到左值或右值,但是无论绑定到什么,rhs
本身都是左值。
如果绑定到右值,并且我们想利用其右值性,则必须将rhs
转换回右值。 当然,如果绑定到左值,我们不想将其视为右值。
使用std::forward
(也称完美转发,当且仅当绑定的的表达式是右值,才将universal reference
转化为右值。实现绑定的左右值性与传递后的左右值性统一。即,传的什么我就转发什么)
引用坍缩
(实现universal reference
)规则只有两个:
An rvalue reference to an rvalue reference becomes (“collapses into”) an rvalue reference.
All other references to references (i.e., all combinations involving an lvalue reference) collapse into an lvalue reference.
Summary
In a type declaration, “&&” indicates either an rvalue reference or a universal reference – a reference that may resolve to either an lvalue reference or an rvalue reference. Universal references always have the form T&& for some deduced type T.
Reference collapsing is the mechanism that leads to universal references (which are really just rvalue references in situations where reference-collapsing takes place) sometimes resolving to lvalue references and sometimes to rvalue references. It occurs in specified contexts where references to references may arise during compilation. Those contexts are template type deduction, auto type deduction, typedef formation and use, and decltype expressions.