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&
, andT
. -
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:
-
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.