1
Before the 2011 update to the C++ specification, lvalues and rvalues could have been thought of as "addressable expressions" and "temporary expressions", respectively. Expressions whose value was stored in an address in memory that was made available to the programmer was an lvalue ("addressable expressions") while expressions that cannot be accessible in subsequent lines of code (say, a + b = 3 in an if statement; the result of a + b can't be used again in any context other than the if statement) were referred to as rvalues ("temporary expressions").
Note that when we say that a value was addressable we mean that it was addressable from the programmer's point of view. Many rvalue expressions have results that must be stored somewhere (a function call which returns an object is an rvalue, for example) but if the programmer cannot access that address with the & operator then it is was not an lvalue.
But the introduction of the rvalue reference cast in C++11, used to enable move semantics, created an expression that does not fit neatly into the old lvalue/rvalue dichotomy. Casting an object into an rvalue reference is a temporary value (if I don't store it in a variable the result of the cast won't be accessible in subsequent lines of code) but since it is a reference it is an explicit address in memory that is made available to the programmer. The C++ standards committee resolved this problem by introducing a third value category and changing the meaning of the word "rvalue":
- most of what we used to call rvalues are now called "prvalues" ("pure rvalues"),
- what we used to call lvalues are still called lvalues,
- an rvalue reference cast expression is of a new value category called "xvalue", which is like a prvalue in that it is a temporary expression but it is also like an lvalue because it is essentially a memory address made available to the programmer,
- rvalue is now an umbrella term; xvalues and prvalues are specific types of rvalues.
There also exists the less useful umbrella term glvalue, short for "general lvalue", which refers to either xvalues or lvalues. The new term xvalue was originally introduced without any meaning and has been retroactively defined in the standard as being short for "eXpiring value". I prefer to think of xvalue as being short for "cross value" since an xvalue contains a cross of some characteristics from lvalues and some characteristics from prvalues.
Now, to be more specific:
lvalue
- names of variables (or functions, templates, data members)
- all assignment expressions
- an expression using the "dereference" operator (*)
- string literals
Key properties:
> & is defined
> can be used as left operand of assignment operators, but only if value is modifiable
prvalue
- any non-string literal
- arithmetic expressions
- logical expressions
- comparison expressions
- this
- enumerator
- lambda
Key properties:
> & throws
> cannot be used in left operand of any assignment
> can be used to initialize const lvalue reference (const MyClass& myRef = <prvalue>;)
> can be used to initialize rvalue reference (MyClass&& myRef = <prvalue>;)
> function overload defined for rvalue reference parameter is used, if defined, if passed as argument to that function
xvalue
- a cast expression to rvalue reference to object type (static_cast<MyClass&&>(myClassInstance), or the shorthand std::move(myClassInstance))
Key properties:
> & throws
> cannot be used in left operand of any assignment
> can be used to initialize const non-rvalue reference (const MyClass& myRef = <xvalue>;)
> can be used to initialize rvalue reference (MyClass&& myRef = <xvalue>;)
> function overload defined for rvalue reference parameter is used, if defined, if passed as argument to that function
=================================
Additional cases:
=================================
functions:
- if the return type is an lvalue reference, then the function call is an lvalue
- if the return type is an rvalue reference, then the function call is an xvalue
- otherwise, prvalue
comma operator
- the value category of the final comma-separated expression is the value category of the comma expression
ternary
- the value category of the resulting branch is the value category of the ternary expression
subscript (a[n])
- an lvalue if one operand is an lvalue, an xvalue if one operand is an rvalue
pre-increment, pre-decrement (++a, --a)
- an lvalue (it is addressable since the result of the expression is the new value of the variable)
post-increment, post-decrement (a++, a--)
- a prvalue (it is temporary since the result of the expression is different from the value of the variable in subsequent lines of code)
a.m
-non-function, non-enumerator member
- an lvalue if a is an lvalue, an xvalue if a is an rvalue
-non-function, enumerator member
- prvalue
p->m
-non-function, non-enumerator member
- an lvalue
-non-function, enumerator member
- a prvalue
a.*p, a->*p
- an lvalue if a is an lvalue, an xvalue if a is an rvalue
function member access of object (a.f, p->f, a.*pf, p->*pf)
- a prvalue. these are particularly restricted, they can only be used as the left-hand argument of a function call and the compiler will throw if you try to use this expression for anything other than a function call. (you could argue this implies the existence of an additional value category if you wanted to make C++ developers lives even harder).
template parameters
- non-type template parameter of lvalue reference type
- lvalue
- non-type template parameter of a scalar type
- prvalue
requires expression
- prvalue
specialization of a concept
- prvalue
cast expressions
- xvalue if casted to rvalue reference type (by definition)
- lvalue if casted to lvalue reference type
- prvalue otherwise
void expressions
- void functions, casting something as void, and throw expressions are all prvalues, but these expressions are not allowed to be used as function arguments or used to initialize references. throw expressions may be used in either branch of the ternary operator. (you could argue this implies the existence of an additional value category if you wanted to make C++ developers lives even harder).
=================================
Properties of value categories:
=================================
lvalue
> & is defined
> can be used as left operand of assignment operators (if value is modifiable)
> can be used to initialize a non-rvalue reference
> can be converted to prvalue with implicit conversion
> lvalue-to-rvalue
> array-to-pointer
> function-to-pointer
> can be polymorphic
> can have incomplete type
prvalue
> & throws
> cannot be used in left operand of any assignment
> can be used to initialize const lvalue reference
> can be used to initialize rvalue reference
> function overload defined for rvalue reference parameter is used, if defined, if passed as argument to that function
> cannot be polymorphic
> cannot have incomplete type (except void, or when used in decltype specifier)
> cannot have abstract class type or an array of abstract class type
xvalue
> & throws
> cannot be used in left operand of any assignment
> can be used to initialize const non-rvalue reference
> can be used to initialize rvalue reference
> function overload defined for rvalue reference parameter is used, if defined, if passed as argument to that function
> can be converted to prvalue with implicit conversion
> lvalue-to-rvalue
> array-to-pointer
> function-to-pointer
> can be polymorphic
> can have incomplete type
glvalue (ie, properties shared between lvalues and xvalues)
> can be converted to prvalue with implicit conversion
> lvalue-to-rvalue
> array-to-pointer
> function-to-pointer
> can be polymorphic
> can have incomplete type
rvalue (ie, properties shared between prvalues and xvalues)
> & throws
> cannot be used in left operand of any assignment
> can be used to initialize const non-rvalue reference
> can be used to initialize rvalue reference
> function overload defined for rvalue reference parameter is used, if defined, if passed as argument to that function
For immediate assistance, please email our customer support: [email protected]