The lvalue/rvalue metaphor
Every expression in C++ is either an lvalue or an rvalue. This distinction is what makes something like
5 = x; invalid, as the expression
5 is an rvalue expression and so cannot appear on the left of an assignment. This is in fact where the terms originate: only lvalues can appear on the left of an assignment, while rvalues must appear on the right  §3.10. However, this only really describes one particular case, so isn't useful for a general understanding of lvalues and rvalues.
For beginners, these value categories are a mild curiosity that appear only in error messages (for the above expression, GCC tells us "lvalue required as left operand of assignment"). However, since the introduction of move semantics and rvalue references in C++11, it has become more important than ever to understand them. One could try to learn every lvalue/rvalue rule in the standard, but there is a simple interpretation that can help you understand their purpose and reason about them. Lvalues and rvalues are just metaphors:
- Lvalues represent objects and rvalues represent values.
- Lvalue-to-rvalue conversion represents reading the value of an object.
std::moveallows you to treat any expression as though it represents a value.
std::forwardallows you to preserve whether an expression represented an object or a value.
Lvalues and rvalues
Every useful C++ program revolves around the manipulation of objects, which are regions of memory created at runtime in which we store values. A simple
int x;, for example, creates an object for storing integer values.
We also come across values that do not belong to any particular object. For example, the literal
5 represents the abstract value of 5, but is not stored in any object. Similarly, if we have two
y, the expression
x + y gives us a value representing the result of the addition — this value is also not stored in an object.
A simple interpretation of lvalues and rvalues is that lvalues represent objects and rvalues represent values. In the following code,
x denotes an object, so it's an lvalue.
x + 5 denotes a value, so it's an rvalue. The subexpression
5 is also an rvalue.
I'm being careful here by using the word "represent". The truth is that rvalue expressions can denote objects too, but they still represent values. For example, some rvalue expressions result in the creation of a temporary object — such as a function call that returns by value  §5.2.2. Although an object does really exist here, the expression can still be thought of as just representing a value of that type. Consider this function:
Elsewhere in your code, the function call
get_message() denotes the value of an
std::string containing "Hello, World!", rather than a persistent object that you are going to manipulate.
Most operators in C++ expect rvalues (values) as their operands  §5. If we want to perform addition, for example, we just need two values to add together — we don't care if they belong to objects. A notable exception is the assignment operator, which requires an lvalue (object) for its left operand  §5.17. This is also logical — assignment needs an object in which to store something.
We can, however, also use lvalues where rvalues are expected — this is permitted by the implicit lvalue-to-rvalue conversion  §4.1. Once again, this makes sense — if we provide an object where a value is expected, we can just read the value of the object. That is, lvalue-to-rvalue conversion represents reading the value of an object..
Moving and forwarding
A call to
std::move is always an rvalue (value) expression. Because of this,
std::move allows you to treat any expression as though it represents a value. What's the purpose of this? Well, objects are persistent regions of storage that we don't expect to change when doing non-destructive operations on them. However, if we know that we don't need the object any longer, we can often use destructive yet more efficient implementations. Values are inherently transient, so treating an object like a value allows us to perform these more efficient operations. For example, by treating objects as values, we can efficiently steal their resources when copying them (which we call moving, rather than copying). Look up move semantics to find out how to implement this for your classes.
In some cases, C++ will silently do this, treating your lvalues as rvalues  §12.8 (as though you had
std::moved them). For example, when returning a local object from a function, the compiler knows that the object is no longer required and so can treat the returned expression as though it just represents a transient value:
std::forward relies on a neat little trick involving type deduction and reference collapsing. Consider the following example:
When the argument passed to
wrapper is an lvalue expression of type
x is deduced to be of type
widget&. When it is an rvalue expression,
x is of type
widget&&. In both cases, the expression
x will just be an lvalue. However, the
std::forward function is cleverly designed so that
std::forward is an lvalue in the first case and an rvalue in the second case. Therefore,
std::forward allows you to preserve whether an expression represented an object or a value.
This concludes a simple, intuitive intepretation for understanding lvalues and rvalues in which they are a metaphor for objects and values respectively. By thinking of lvalues and rvalues in this way, it becomes easier to reason about the behavior of your code and to make the most of modern C++.