An rvalue expression is any expression which can be implicitly moved from, regardless of whether it has identity.

More precisely, rvalue expressions may be used as the argument to a function that takes a parameter of type T && (where T is the type of expr). Only rvalue expressions may be given as arguments to such function parameters; if a non-rvalue expression is used, then overload resolution will pick any function that does not use an rvalue reference parameter. And if none exist, then you get an error.

The category of rvalue expressions includes all xvalue and prvalue expressions, and only those expressions.

The standard library function std::move exists to explicitly transform a non-rvalue expression into an rvalue. More specifically, it turns the expression into an xvalue, since even if it was an identity-less prvalue expression before, by passing it as a parameter to std::move, it gains identity (the function’s parameter name) and becomes an xvalue.

Consider the following:

std::string str("init");                       //1
std::string test1(str);                        //2
std::string test2(std::move(str));             //3

str = std::string("new value");                //4 
std::string &&str_ref = std::move(str);        //5
std::string test3(str_ref);                    //6

std::string has a constructor which takes a single parameter of type std::string&&, commonly called a “move constructor”. However, the value category of the expression str is not an rvalue (specifically it is an lvalue), so it cannot call that constructor overload. Instead, it calls the const std::string& overload, the copy constructor.

Line 3 changes things. The return value of std::move is a T&&, where T is the base type of the parameter passed in. So std::move(str) returns std::string&&. A function call who’s return value is an rvalue reference is an rvalue expression (specifically an xvalue), so it may call the move constructor of std::string. After line 3, str has been moved from (who’s contents are now undefined).

Line 4 passes a temporary to the assignment operator of std::string. This has an overload which takes a std::string&&. The expression std::string("new value") is an rvalue expression (specifically a prvalue), so it may call that overload. Thus, the temporary is moved into str, replacing the undefined contents with specific contents.

Line 5 creates a named rvalue reference called str_ref that refers to str. This is where value categories get confusing.

See, while str_ref is an rvalue reference to std::string, the value category of the expression str_ref is not an rvalue. It is an lvalue expression. Yes, really. Because of this, one cannot call the move constructor of std::string with the expression str_ref. Line 6 therefore copies the value of str into test3.

To move it, we would have to employ std::move again.