Rvalues and Lvalues
The terms lvalue and rvalue do not typically come up often for most programmers. Occasionally you might see a compiler error message about rvalues. Other than that, it is not something you generally have to worry about.
If we consider the simple assignment:
a = b
there is an obvious symmetry; a
and b
are both variables and to all intents and purposes behave in the same way.
However, as the equals sign here indicates assignment, it makes a big difference which side of the operator the variable is situated. In this case, we want to copy the value of b
into a
.
As far as the compiler is concerned, in order to perform the assignment, it needs to know two things:
- the value of b,
- the location in memory of a.
In compiler terms, a
is an lvalue and b
is an rvalue. This is easy to remember as the lvalue is on the left and rvalue is on the right – this is presumably how they got their names.
When writing this post, finding a satisfactory definitions for lvalues and rvalues was not straightforward. The following definition from http://thbecker.net is probably as good as any:
An lvalue is an expression that refers to a memory location and allows us to take the address of that memory location via the & operator. An rvalue is an expression that is not an lvalue.
Rvalue references
C++11 added the concept of rvalue references. Lvalue and rvalue references are denoted by the & and && operators respectively:
double& <- lvalue reference
double&& <- rvalue reference
For example, in C++, this is not legal:
double& x = 42;
because you can’t bind a reference to an rvalue. However, in C++11, you can bind an rvalue reference to a rvalue:
double&& x = 42;
To understand where this could be useful, it is worth briefly discussing temporary objects.
Temporary Objects
According to the Microsoft’s Visual C++ documentation, (https://docs.microsoft.com), a temporary object is used to store the return value of a function which returns a user-defined type.
Suppose that we have created a Vector class for 3D geometry calculations. Typically we would want to overload the addition operator so that the following syntax can be used:
Vector p = q + r;
When the addition is performed, temporary storage is created to store the result of q + r
. The value of this temporary object can be assigned to p
. As soon as it the expression has been evaluated, the temporary object and associated storage is deleted.
Other than copying the value of the temporary object, there is not much else we can do with it (in C++03 at least). For example, the following gives a compilation error:
Vector& p = q + r
You can’t bind a reference to a temporary object. However, in C++11, you can bind an rvalue reference to a temporary object, as follows:
Vector&& p = q + r
This might not seem particularly useful and you may ask why was it added to the C++11 standard? This will only become clear when we move on to the topic of move semantics.
Summary
In this post we have discussed the following concepts:
- lvalues and rvalues
- rvalue references
- temporary objects
As a programmer, these are topics which you generally don’t have to think about. They are just the mechanisms by which compilers are able to take our symbolic code and make it execute in the way we expect.
But these concepts will be instructive in the next post when we discuss Move Semantics.