What's the standard/official name for universal references?
Asked Answered
D

1

25

I know that if a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is widely called a universal reference.

The term universal reference was introduced by Scott Meyers in his original talk “Universal References in C++11”. However, I wonder what's the official/standard term for universal references.

Dannadannel answered 24/11, 2015 at 21:46 Comment(0)
D
29

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:

Dannadannel answered 24/11, 2015 at 21:46 Comment(10)
A citation for "It is known that since C++11, a parameter of type T&& is called an rvalue reference" would be appreciated. In particular, if T is int&, a citation that T&& is still called an rvalue reference in the standard.Downbow
@Yakk I agree I'll try to find the standard wording for it.Dannadannel
"That is, unless T is a template parameter type or auto." or a typedef for some lvalue reference type.Foundling
Also, terse range-based for loops are out.Foundling
@Foundling thanks for the points, really appreciated. Feel free to edit the answer if there are any more inaccuracies.Dannadannel
An alternative syntax like T&&& or for T would have been much clearer :(Undertenant
@Undertenant Right now I'm at work, feel free to edit :).Dannadannel
@101010 Feel free to edit the C++ standard, you mean? brb... :)Undertenant
@Undertenant Ah... OK. Why not? the C++ committee has open positions don't they? :PPPPDannadannel
dcl.ref/6 -- If TR is int&, an attempt to create the type "rvalue reference to TR" creates TR. The use of "an attempt to create" implies no rvalue reference actually occurs there; instead, it is just "an attempt to create an rvalue reference". T&& is not an rvalue reference if T is a reference type, because dcl.ref/5 there shall be no reference to references. In essence, TR&& where TR is a reference is not a valid construct (and hence has no name) prior to dcl.ref/6. After dcl.ref/6, it means TR... maybe?Downbow

© 2022 - 2024 — McMap. All rights reserved.