Scott Meyers' Universal References
I came across an another very clarifying talk of the legend Scott Meyers presenting
Universal References in C++11 back in 2012. In this talk
he calls our attention to the misleading usage of template parameters T&&, which we usually
assume being rvalue references in all situations. It happens that it is not always the case.
The fact is that T&& becomes a rvalue reference or a lvalue reference depending on the
case. Meyers defines a universal reference as a variable or parameter that fulfills the
following requirements
- It is declared as
T&&; Tis a deduced type;
If a universal reference is initialized with a lvalue, then it becomes a lvalue reference. If a universal reference is initialized with a rvalue, then it becomes a rvalue reference.
Let’s consider a template function f
and an instance of some arbitrary class
Then T&& will be interpreted by the compiler differently depending on the usage of f:
As you may notice, we get an lvalue reference in the first case.
const T&&is not a universal reference.
The same logic is
applied to the use of auto declarations:
Attention: In the case of a template class (such as
std::vector<T>) with method parameters using its templates (such asstd::vector<T>::push_back(const T&)andstd::vector<T>::push_back(T&&)), the type is resolved at class instantiation and therefore there is no universal reference – there is no type deduction.std::vector<T>::emplace_back<...Args>(Args&&...)on the other hand getsArgsresolved by deduction, therefore a universal reference.
The importance of distinguishing between lvalue and rvalue instantiations along with template references gets more clear when we do things like move constructors or function overloads:
If we don’t pay attention, the code might actually run one function instead of the function we might be mistakenly be expecting it to. Take some time to think about it: non-const lvalue are sent to the second function, and not the first.
Rules of Thumb⌗
Speaking of lvalues and rvalues, here are some tips to keep in mind:
Under the hood⌗
For the curious out there, this transformation between rvalue references to lvalue references caused by universal references happens because the way the compiler deduces reference types. For example,
f will be instantiated with its template parameter T deduced as SomeClass&:
However, reference of reference is not allowed, so the compiler resolves it by collapsing references following the rules
Then the final version for our function becomes
auto follows the exact same rules.