What's the standard/official name for universal references?
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 isT&&
and Ai isX&
, the adjusted Pi will be T,
causing T to be deduced asX&
. — 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:
Is there a difference between universal references and forwarding references?
Do they mean the same thing?
Universal reference was a term Scott Meyers coined to describe the concept of taking an rvalue reference to a cv-unqualified template parameter, which can then be deduced as either a value or an lvalue reference.
At the time the C++ standard didn't have a special term for this, which was an oversight in C++11 and makes it hard to teach. This oversight was remedied by N4164, which added the following definition to [temp.deduct]:
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.
Hence, the two mean the same thing, and the current C++ standard term is forwarding reference. The paper itself articulates why "forwarding reference" is a better term than "universal reference."
Is it only a forwarding reference if the function body calls
std::forward
?
Nope, what you do with a forwarding reference is irrelevant to the name. The concept forwarding reference simply refers to how the type T
is deduced in:
template <class T> void foo(T&& ); // <==
It does not need to be subsequently forwarded .
Why adding `const` makes the universal reference as rvalue
The official name is not universal reference, but forwarding reference. The Standard states that only rvalue references to cv-unqualified template parameters fall in this category:
14.8.2.1 Deducing template arguments from a function call [temp.deduct.call]
3 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 ]
Allowing const T&&
to behave as forwarding references, would make it impossible to overload a template function who takes only an rvalue reference as parameter.
Update: as @HowardHinnant mentions in the comments, const T&&
does have its uses (see also this Q&A).
Syntax for universal references
A universal reference such as T&&
can deduce T
to be an "object type", or a "reference type"
In your example it can deduce T
as int
when passed an rvalue, so the function parameter is int&&
, or it can deduce T
as int&
when passed an lvalue, in which case the function parameter is int&
(because the reference collapsing rules say std::add_rvalue_reference<int&>::type
is just int&
)
If T
isn't deduced by the function call (as in your X::baz
example) then it can't be deduced to int&
, so the reference isn't a universal reference.
So IMHO there's really no need for new syntax, it fits nicely into template argument deduction and reference collapsing rules, with the small tweak that a template parameter can be deduced as a reference type (where in C++03 a function template parameter of type T
or T&
would always deduce T
as an object type.)
These semantics and this syntax were proposed right from the beginning when rvalue references and a tweak to the argument deduction rules were proposed as the solution to the forwarding problem, see N1385. Using this syntax to provide perfect forwarding was proposed in parallel with proposing rvalue references for the purposes of move semantics: N1377 was in the same mailing as N1385. I don't think an alternative syntax was ever seriously proposed.
IMHO an alternative syntax would actually be more confusing anyway. If you had template<typename T> void bar(T&@)
as the syntax for a universal reference, but the same semantics as we have today, then when calling bar(i)
the template parameter T
could be deduced as int&
or int
and the function parameter would be of type int&
or int&&
... neither of which is "T&@
" (whatever that type is.) So you'd have grammar in the language for a declarator T&@
which is not a type that can ever exist, because it actually always refers to some other type, either int&
or int&&
.
At least with the syntax we've got the type T&&
is a real type, and the reference collapsing rules are not specific to function templates using universal references, they're completely consistent with the rest of the type system outside of templates:
struct A {} a;
typedef A& T;
T&& ref = a; // T&& == A&
Or equivalently:
struct A {} a;
typedef A& T;
std::add_rvalue_reference<T>::type ref = a; // type == A&
When T
is an lvalue reference type, T&&
is too. I don't think a new syntax is needed, the rules really aren't that complicated or confusing.
Why is const template parameter not a universal/forwarding reference
Foreword: The official term is forwarding reference.
So why is the second case not a universal reference?
Because it is a reference to const. And references to const are not forwarding references.
why?
The whole point of a forwarding reference is that when an rvalue is given as an argument, the parameter will be deduced as reference to non-const rvalue which allows such argument to be moved from when forwarding (while simultaneously allowing lvalues to not be moved from). You cannot move from a reference to const because the argument of the move constructor will be an rvalue reference to non-const which cannot be bound to a reference to const.
The language-lawyer answer is: Because the standard says so:
[temp.deduct.call] A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template ...
Different behaviour between int && and auto &&
In the first case int && y
, the variable y
can bind to only rvalue which x
is not.
In the second case auto && y
however, the variable y
can bind to anything, as the type of y
would be deduced anyway — and reference-collapsing will be applied accordingly — which is why your code compiles:
auto && y = x;
Since x
is an lvalue, auto
is deduced to be int&
, hence it becomes:
int& && y = x;
and after reference-collapsing, it becomes:
int & y = x;
which is fine.
To understand it in more detail, read about:
Universal Reference (or Forwarding Reference, as it has been proposed to improve the terminology)
Reference Collapsing
Hope that helps.
How I can a take a pointer to a universal reference?
Your universal reference[*] turns into an lvalue reference to const char*
after type deduction (E
is deduced to be const char*&
).
Thus, instead of this:
memcpy(X, &e, len);
// ^^
// This is a pointer to a pointer to const char!
// ^^^^^^^^^^^^
You should use this:
memcpy(X, e, len);
[*] The term "universal reference" was introduced by Scott Meyers and it is not part of the C++11 Standard. The Standard only defines reference collapsing rules, and the abstraction of a universal reference is only meant to provide an easier understanding of how those rules work.
Deducing LValue Reference type
There are no expressions of reference type in C++. The type of the variable ir
is int&
, but the type of the expression ir
is int
. This latter type is used for type deduction, since function arguments are always expressions (except in the special case of braced-init-lists).
See [expr.type]/1
If an expression initially has the type “reference to
T
” ([dcl.ref], [dcl.init.ref]), the type is adjusted toT
prior to any further analysis.
The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Universal reference l-value not copying object
However both objects addresses "a" and "b" are the same in the
run()
function.
When being passed an lvalue, T
is deduced as lvalue-reference, i.e. int&
. (int& &&
collapses to int&
, so the type of function parameter a
is int&
.) Then b
is declared as a reference binding to a
.
When being passed an rvalue, T
is deduced as int
. (So the type of function parameter a
is int&&
.) Then b
is declared as an independent variable copied from a
.
Related Topics
Win32 C/C++ Load Image from Memory Buffer
Understanding Virtual Base Classes and Constructor Calls
Why There Is No Std::Copy_If Algorithm
How to Get Process Handle from Process Id
Why Do I See 400X Outlier Timings When Calling Clock_Gettime Repeatedly
Visual Studio 2015 Code Analysis C6386 Warns of Buffer Overrun
Why Array Index Start from '0'
Purpose of Struct, Typedef Struct, in C++
Static Variable Initialization Over a Library
How to Control My Pc's Fan Speed Using C++ in Vista
Freopen() Equivalent for C++ Streams
Observable Behavior and Undefined Behavior -- What Happens If I Don't Call a Destructor
System':A Namespace with This Name Does Not Exist
What Are the Advantages and Disadvantages of Implementing Classes in Header Files