Reference collapsing occurs in four contexts: template instantiation, auto type generation, creation and use of typedefs and alias declarations, and decltype.
The core concept in this item: A universal reference isn’t a new kind of reference, it’s actually an rvalue reference in a context where two conditions are satisfied:
- Type deduction distinguishes lvalues from rvalues. (Lvalues of type T are deduced to have type
T&
, while rvalues of typeT
yieldT
) - Reference collapsing occurs.
Specifically, if a reference to a reference is generated by compilers arises in one of the four context mentioned above, the references collapse to a single reference according this rule:
- if either of the original references is an lvalue reference, the result is an lvalue reference
- otherwise, it’s an rvalue reference
Template instantiation
Reference collapsing is a key part to make std::forward
work. For example, given an possible implementation of std::forward
and a function f
:
|
|
If we pass an lvalue of type Widget
to function f
, T
will then be deduced as Widget&
, and we get:
|
|
According to reference collapsing rule, we get:
|
|
If we pass to f
an rvalue of type Widget
, T
will then be Widget
, and we get:
|
|
According to reference collapsing rule, we get:
|
|
Overall, an rvalue arguement Widget
passed to f
will be bound to the lvalue parameter fParam
firstly, be casted into an rvalue, and then be forward
ed to someFunc
.
auto type generation
Type deduction for auto
variables is essentially the same as deduction for templates.
|
|
typedefs and alias declarations
|
|
Given Widget<int&> w;
, we get typedef int& && RvalueRefToT;
, which then collapses into typedef int& RvalueRefToT;
. Interestingly, the name turns out to be misleading: the typedef
actually refers to an lvalue reference to int
now if instantiated with an lvalue reference type int&
.
decltype
Same logic as above to analyze.