Move semantic is achieved by adding a new category type to the type system, it is not a library feature and cannot be reproduced in C++03.
The value categories in C++ changed from C during it’s first standardisation, then changed in C++11 to enable move semantic, and again in C++17 to enable further optimisation (for example for returning non-movable and non-copyable types from functions).
So, depending on the context/language revision, categorie values have different definitions.
int i = 42; // i is lvalue, 42 is rvalue
42 = i; // invalid, 42 is not an rvalue
int* p = &i; // p is lvalue, i is lvalue, and &i is lvalue
int* q = &42; // invalid, 42 is not an rvaluei is an lvalue (on the left-hand side-of an assignment)
42 is an rvalue (only on the right-hand-side of an assignment)
Variables are lvalue, literals (except string literals) and functions are rvalues.
String literals are rvalue, otherwise it would not be possible to take the address to the first element. Function are lvalues, but it is still possible to take theyr address.
char (*string1)[] = &"Hello World"; // type char(*)[12]
char* string2 = "Hello World"; // type char*
char* string3 = &"Hello World"; // error, as char* is not compatible with char(*)[12]
"Hello World"[0] = 'a'; // valid, but UB
void fun(void);
void (*f1)(void) = fun;
void (*f2)(void) = &fun;
fun = ...; // invalid, as fun is rvalueSame as K&R C, but there is just lvalue.
Also some rules have been added to support const.
string literals are constant lvalues (but because of backwards compatibility, they still are still implicitely convertible non-constant lvalues). Function and other literals count as others.
const applies to lvalue expressions, they are not modifiable.
For example, they are not assignable.
A pointer to an non-const type can be implicitly converted to a pointer to a const-qualified version of the same or compatible type. The reverse conversion can be performed only with an explicit cast.
const int c = 0; // c is an lvalue
c = 42; // error, c is const-qualified and not assignable
const int* p = &c; // c is an lvalue
int* p = &c; // error, const int* is not convertible to int*
const int* q = &42; // invalid, 42 is an rvalue
"hello"[0] = 'a'; // valid, but UB
const char (*v)[12] = &"Hello World";
v[0] = 'a'; // invalid
const char* vv = "Hello World";
vv[0] = 'a'; // invalidC++98 and C++03 followed C and restored the name "rvalue" to non-lvalue expressions.
Some expression, like functions, are lvalues, and string literals are const withtout exceptions.
References can bind to lvalues, but only references to const can bind to rvalues. In the second case, the lifetime of the object is extended until the scope of the reference ends.
General rule: if it is possible to take an address/it has a name, then it’s a lvalue.
int& c = 42; // invalid, 42 is rvalue, cannot bind reference to it
const int& c = 42; // c is lvalue, 42 is rvalue, but references to const can bind to rvalues
int j = 42; // j is lvalue, 42 is rvalue
int& c = j; // c,j are both lvalue
void foo(int&);
foo(42); // error, cannot bind lvalue to rvalue
int baz(); // returns a rvalue
bar(const int&); // takes const reference
bar(baz()); // const reference can bind to rvalue
void f( const std::string& s );
f( "foobar" ); // const reference can bind to rvalue (array is converted to std::string)
"hello"[0] = 'a'; // error, "hello" is a const lvalueC++11 and C++14 introduced new values to enable move semantic. Value categories were redefined to characterize two independent properties of expressions:
-
has identity: it’s possible to determine if two expression refers to the same entity, for example by comparing addresses of the objects.
-
can be moved from: move constructor, or another function overload that implements move semantics can bind to the expression.
Thus there are expression that
-
have identity and cannot be moved from (lvalue expressions)
-
have identity and can be moved from (xvalue (eXpiring Value) expressions)
-
do not have identity and can be moved from are (prvalue ("pure rvalue") expressions);
-
do not have identity and cannot be moved from (unused).
The expressions that have identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions. The expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions.
Notice that both lvalue and rvalue where reused with similar meaning, and following relation holds in all C++ standards:
Every value is either an lvalue or an rvalue, never both.
std::string s;
&s; // ok, lvalue (just like c++ <11 lvalue)
s = "a"; // ok, lvalue (just like c++ <11 lvalue)
&std::string(s); // error, prvalue (just like c++ <11 rvalue)
std::string(s) = a; // error, prvalue (just like c++ <11 rvalue)
&std::move(s); // error, xvalue (similar to c++ <11 rvalue)
std::move(s) = "a"; // ok, xvalue (similar to c++ <11 lvalue)In practice:
-
Everything that has a name (variables, parameters, functions, …),
*this, and string literals are lvalue. -
All literals (
42,nullptr,-1f, enums, … strings exlcuded),this, lambda, return of constructor call and value returned from functions are prvalue. -
returned value reference (by std::move) and cast to rvalue reference are xvalue.
