Overview
It is known that since C++11, a parameter of type T&&
is called an rvalue reference [ISO/IEC 14882:2011 §8.3.2/p2 References [dcl.ref] ]. That is, unless T
is a template parameter type or auto
or a typedef
for some lvalue reference type.
examples:
template<typename T>
void foo(T&& p) { // -> T is a template parameter
...
}
auto &&p = expression;
Although technically T&&
in the examples above is still an rvalue reference, its behaviour differs significantly from a regular one.
Naturally, you would ask "why this special case doesn't have a special syntax". The answer is that the &&
syntax was intentionally overloaded for this special construct by the C++ committee. However, they missed to name this special case.
In the absence of a distinct name for this particular construct, Scott Meyers coined the widely known term/name universal references.
The committee however, decided that this name is not proper for a number of reasons. As such, the proposal N4164 made by Herb Sutter, Bjarne Stroustrup and Gabriel Dos Reis proposed to change the name to Forwarding References.
The name Forwarding References had the most support in informal discussions among committee members, including the authors of the proposal mentioned earlier. Interestingly enough, it was Scott Meyers himself that introduced that term in his original “Universal References” talk. However, later he decided to go along with the name universal references. For this decision played role the fact that at the time he didn't think that the term forwarding references included also the auto&&
case.
Why not universal references?
According to the proposal the term Universal references although is a reasonable name with an obvious meaning, it happens to be wrong in several aspects.
A universal reference must mean the following:
- A reference that can be used everywhere; or
- A reference that can be used for everything; or
- something similar.
Obviously this is not the case nor is the appropriate use of this construct. Furthermore, this name would encourage many people to consider that something having such a name is meant to be used "universally". Something that the committee considered it a bad thing.
Moreover, "universal references" aren’t even really
references per se, but rather a set of rules for using references in a particular way in a particular context with some language support for that use, and that use is forwarding.
Why auto&&
is Also Considered a Forwarding case
auto&&
is also considered a forward case since it follows the reference collapsing rules. For example in:
- Generic lambdas of the form,
[](auto&& x){ … }
for
-ranged loop of the form, for(auto &&i : v) { ... }
- Finally, in general is true that
auto&&
local variables are for forwarding.
Standard Wordings for Forwarding References
The term forwarding references is mentioned in the draft standard N4527 in the following places:
§14.8.2.1/ Deducing template arguments from a function call [temp.deduct.call] (Emphasis Mine):
If P is a cv-qualified type, the top level cv-qualifiers of P’s type
are ignored for type deduction. If P is a reference type, the type
referred to by P is used for type deduction. A forwarding
reference is an rvalue reference to a cv-unqualified template
parameter. If P is a forwarding reference and the argument is an
lvalue, the type “lvalue reference to A” is used in place of A for
type deduction. [ Example:
template <class T> int f(T&& heisenreference);
template <class T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&)
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which
// would bind an rvalue reference to an lvalue
— end example ]
§14.8.2.5/p10 Deducing template arguments from a type [temp.deduct.type]:
Similarly, if P has a form that contains (T), then each parameter type
Pi of the respective parameter-typelist of P is compared with the
corresponding parameter type Ai of the corresponding
parameter-type-list of A. If P and A are function types that
originated from deduction when taking the address of a function
template (14.8.2.2) or when deducing template arguments from a
function declaration (14.8.2.6) and Pi and Ai are parameters of the
top-level parameter-type-list of P and A, respectively, Pi is adjusted
if it is a forwarding reference (14.8.2.1) and Ai is an lvalue
reference, in which case the type of Pi is changed to be the template
parameter type (i.e., T&& is changed to simply T). [ Note: As a
result, when Pi is T&&
and Ai is X&
, the adjusted Pi will be T,
causing T to be deduced as X&
. — end note ] [Example:
template <class T> void f(T&&);
template <> void f(int&) { } // #1
template <> void f(int&&) { } // #2
void g(int i) {
f(i); // calls f<int&>(int&), i.e., #1
f(0); // calls f<int>(int&&), i.e., #2
}
— end example ] If the parameter-declaration corresponding to Pi is a
function parameter pack, then the type of its declaratorid is compared
with each remaining parameter type in the parameter-type-list of A.
Each comparison deduces template arguments for subsequent positions in
the template parameter packs expanded by the function parameter pack.
During partial ordering (14.8.2.4), if Ai was originally a function
parameter pack:
T
isint&
, a citation thatT&&
is still called an rvalue reference in the standard. – Downbow