What are const rvalue references good for?

You probably haven’t seen any practical code that uses const rvalue references (const T&&). And that’s not really surprising. The main purpose of rvalue references is to allow us to move objects instead of copying them. And moving the state of an object implies modification. As a result, the canonical signatures for the move constructor and the move assignment operator both take its argument as a non-const rvalue reference.

But const rvalue references are in the language and have well defined binding rules. Specifically, a const rvalue will prefer to bind to the const rvalue reference rather than the const lvalue reference. The following code fragment illustrates the binding preferences:

 
struct s {};
 
void f (      s&);  // #1
void f (const s&);  // #2
void f (      s&&); // #3
void f (const s&&); // #4
 
const s g ();
s x;
const s cx;
 
f (s ()); // rvalue        #3, #4, #2
f (g ()); // const rvalue  #4, #2
f (x);    // lvalue        #1, #2
f (cx);   // const lvalue  #2
 

Note the asymmetry: while a const lvalue reference can bind to an rvalue, a const rvalue reference cannot bind to an lvalue. In particular, this makes a const lvalue reference able to do everything a const rvalue reference can and more (i.e., bind to lvalues).

This makes const rvalue references pretty useless. Think about it: if all we need is a non-modifiable reference to an object (rvalue or lvalue), then a const lvalue reference does the job perfectly. The only situation where we want to single out rvalues is if we want to move them. And in that case we need a modifiable reference.

Plus, const rvalues don’t make much sense. Yes, we can have a function that returns a const value, like g() above, or call std::move() on a const object, but all this is rather pointless. Why place any restrictions on what a caller of our function can do with the their own copy of the returned value? And why try to move an immutable object?

So it seems the only use for const rvalue references is if you need to disable rvalue references altogether and you need to do it in a bullet-proof way that will deal with the above pointless but nevertheless legal cases.

Case in point is the std::reference_wrapper class template and its ref() and cref() helper functions. Because reference_wrapper is only meant to store references to lvalues, the standard disables ref() and cref() for rvalues. To make sure they cannot be called even for const rvalues, the standard uses the const rvalue reference:

 
template <class T> void ref (const T&&) = delete;
template <class T> void cref (const T&&) = delete;
 

Besides supporting the move semantics, the rvalue reference syntax is also used in perfect forwarding. Note, however, that adding const to T&& disables the special argument deduction rules used to support this feature. For example:

 
template <typename T> void f (T&&);
template <typename T> void g (const T&&);
 
s x;
 
f (s ()); // Ok, T = s
f (x);    // Ok, T = s&
 
g (s ()); // Ok, T = s
g (x);    // Error, binding lvalue to rvalue, T = s
 

2 Responses to “What are const rvalue references good for?”

  1. pizer Says:

    There are no const prvalues of scalar types. For a const rvalue you either need an Xvalue and/or a class-type object. I’m almost sure that a conforming compiler would treat f(g()) just like f(1) in terms of overload resolution since g just returns a value and not an object.

  2. Boris Kolpackov Says:

    Pizer, yes, you are correct. I’ve updated the examples in the post to use a class-type instead of int. Thanks for pointing this out.