This page looks best with JavaScript enabled

[EMCpp]Item-24 Distinguish Universal References From Rvalue References

 ·  ☕ 3 min read · 👀... views

If a function template parameter has type T&& for a deduced type T, or if an object is declared using auto&&, the parameter or object is a universal reference.

T&& has two different meanings: one is for rvalue reference, another is for universal reference. Universal references are called “universal”, because they can bind to virtually anything:

  • bind to rvalues (behave like rvalue references T&&)
  • bind to lvalues (behave like lvalue references T&)
  • bind to objects that is const or non-const, volatile or non-volatile, or both const and volatile

Universal refenrences must company with a special form (i.e., the form of “T&&”) after the occurrence of type deduction, and typically arise in two contexts: the most common case is function template parameters, while another context is auto declarations:

1
2
3
4
5
6
7
template<typename T>
void f(T&& param);  // param is a universal reference
auto&& var2 = var1;  // var2 is a universal reference

Widget w;
f(w);  // lvalue passed to f; param's type is Widget&, act as an lvalue ref.
f(std::move(w));  // rvalue passed to f; param's type is Widget&&, act as an rvalue ref.

If there’s no type deduction, or if the type deduction form is incorrect (not in form of “T&&”), then there’s no universal references:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void f(Widget&& param);  // no type deduction; param is an rvalue reference
Widget&& var1 = Widget(); // no type deduction; param is an rvalue reference

template<typename T>
void f(std::vector<T>&& param);  // not in precise form of T&&, param here is an rvalue ref.

template<typename T>
void f(const T&& param); // not in precise form of T&&, param here is an rvalue ref.

template<typename T, class Allocator = allocator<T>>  // from C++ standards
class vector {
public:
    void push_back(T&& x);  // no type deduction here; x is rvalue reference;
    ...   // a particular vector instantiation must have occurred prior to any call to this function
}

template<typename T, class Allocator = allocator<T>>  // from C++ standards
class vector {
public:
    template<class... Args>
    void emplace_back(Args&&... args);  // universal reference! because:
    ...  // 1. in correct form of "T&&"; 2. type parameter Args must be deduced each time emplace_back is called
}

// this function can time pretty much any function execution. more information is in EMCpp item 30
auto timeFuncInvocation =    // C++14
    [] (auto&& func, auto&&... param)  // func is a universal reference that can be bound to any callable object, lvaue or rvalue
    {   // param is zero or more universal references (i.e., a universal reference parameter pack) that can be bound to any number of objects of arbitrary types
        // start timer;
        std::forward<decltype(func)>(func)(  // invoke func on params
            std::forward<decltype(params)>(params)...
        );
        // stop timer and record elapsed time;
    };

In fact, the foundation of univeral references is a lie (an “abstraction”), with underlying truth being known as reference collapsing discussed in EMCpp Item 28. Distinguishing between rvalue references and universal references will help us read source code more accurately.

Share on
Support the author with