computer science goes bonk / all posts / rss / about

rvalue references and rvalues

Since C++11 came off the presses, people have spent a lot of time talking about rvalues and rvalue references. What isn't always clearly explained is rvalues and rvalue references are completely separate concepts.

There are two things going on here

  • An rvalue reference is a type. Everyone knows what types are. Examples include T&&, const T&, and T.

  • The term rvalue is a property of an expression. In C++11, rvalues were renamed to prvalues, and every expression is either a prvalue, an lvalue, or an xvalue. The official names for these terms are value categories.

To reiterate: value categories (prvalues, lvalues, xvalues) are properties of expressions, whereas rvalue references (T&&) are types.

So what are prvalues, lvalues, and xvalues?

Every expression is either a prvalue, an lvalue, or an xvalue. In fact, there's a whole taxonomy that C++11 defines:

value categories

  • lvalues represent objects that will stick around for a while. Examples include named objects and functions calls that return references to objects. Because they're not going anywhere, you can assign things to them and take their address.

  • prvalues represent temporary objects, i.e. objects that will not persist past the outermost expression that they appear in. Examples include temporary objects explicitly created inline and function calls that return objects by value.

  • xvalues represent objects that are about to expire. A function call that returns an rvalue reference (like std::move(foo)) is an example of an xvalue.

glvalues are expressions that are either lvalues or xvalues. rvalues are expressions that are either prvalues or xvalues.

Here's an annotated example that shows value categories and types:

#include <string>

template <typename T>
void swap(T& t1, T& t2) {
//        --     --     types (T&)

   T tmp(std::move(t1));
// -                    type (regular T)
//                 --  lvalue
//       ------------- xvalue

   t1 = std::move(t2);
// --             --   lvalues
//      -------------  xvalue

   t2 = std::move(tmp);
// --             ---  lvalues
//      -------------  xvalue
}

std::string get_greeting() {
   return "Good news, everyone!";
}

int main() {
   std::string s1, s2;
// -----------          type (regular std::string)

   s1 = get_greeting();
// --                  lvalue 
//      -------------- prvalue

   s2 = std::move(get_greeting());
// --                             lvalue 
//                --------------  prvalue 
//      ------------------------- xvalue

   swap(s1, s2);
//      --  --   lvalues
}

Next up: why std::move(get_greeting()) in the above example is unecessary.