DEV Community

Haris
Haris

Posted on

How to Distinguish Universal From R-Value References

To declare an R-value reference in a template you would usually see something like T&& it's not that simple, the T&& has two meanings one is R-value and one is that it can be both R-value and L-value. Furthermore, they can be bound to const and non-const values, volatile and non-volatile and can be bind to virtually anything.

Universal references arise in two contexts likely in templates.

template<typename T>
void func(T&& param);
Enter fullscreen mode Exit fullscreen mode

And the second in auto declarations:

auto&& var1 = var2;
Enter fullscreen mode Exit fullscreen mode

What both examples above have in common is the presence of type deduction now compare the above two to the examples below:

void func(Widget&& param); // R-value
Widget&& var1 = Widget(); // R-value since no type deduction exists
Enter fullscreen mode Exit fullscreen mode

Because universal references are references, they must be initialized, the initializer of the universal reference determines whether it represents R-value or L-value.
For example:

template<typename T>
void func(T&& param);

Widget w;
func(w); // since w is an l-value the universal reference will decay to an l-value.

func(std::move(w)) // this time we are first casting 'w' to an r-value then passing it into the template, thus the reference will decay to r-value.
Enter fullscreen mode Exit fullscreen mode

Edge Cases

For a reference to be universal type deduction is necessary, but it's not sufficient the form of reference declaration must also be correct, and that form is quite constrained, it must be precisely “T&&” look at an example below:

template<typename T>
void func(std::vector<T>&& param); // Parameter is an R-value
Enter fullscreen mode Exit fullscreen mode

The example above will result in a non-universal reference which means it will only take R-values, why you may ask? because there is no T&&, when function will be invoked the type of just T will be deduced not T&& as the && exists with the vector not our template type T.

So, this won’t work:

std::vector<int> vec;
func(vec); // Error, the function will only take R-values.
Enter fullscreen mode Exit fullscreen mode

Even a simple presence of const is enough to disqualify a universal reference.

template<typename T>
void func(const T&& param); // Having a const will make parameter an R-value.
Enter fullscreen mode Exit fullscreen mode

Another edge case where this fails is not always being in a template for example:

template<class T, class Allocator = allocator<T>>
class vector
{
public:
    void push_back(T&& x);
};
Enter fullscreen mode Exit fullscreen mode

Here you might think that push back’s T&& has the right to form a universal reference, but there is no type deduction in this case. This also disqualifies it for being a universal reference.

Top comments (0)