What's the Standard/Official Name for Universal References

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 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:

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 to T 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



Leave a reply



Submit