Understanding Lvalues and Rvalues: (Corrected)
Understanding Lvalues and Rvalues: (Corrected)
Understanding
Lvalues and Rvalues
(corrected)
Dan Saks
This talk explains the origins of the concepts of lvalue and rvalue,
from this historical perspective.
Lvalues
In The C Programming Language, Kernighan and Ritchie wrote:
E1 = E2
x[i + 1] = abs(p->value);
One answer:
So that compilers can assume that rvalues don’t necessarily
occupy storage.
This offers considerable freedom in generating code for rvalue
expressions.
10
11
In this case:
The rvalue 1 never appears as an object in the data space.
Rather, it appears as part of an instruction in the code space.
12
13
1 = n; // obviously silly
14
Recap
Every expression in C is either an lvalue or an rvalue.
In general:
An lvalue is an expression that refers to an object.
An rvalue is simply any expression that isn’t an lvalue.
Caveat: Although this is also true for non-class types in C++, it’s
not true for class types.
15
Literals
Most literals are rvalues, including:
numeric literals, such as 3 and 3.14159
character literals, such as 'a'
They don’t necessarily occupy data storage.
16
Enumeration Constants
When used in expressions, enumeration constants are also
rvalues:
17
int m, n;
~~~
m = n; // OK: m and n are both lvalues
18
int x;
~~~
~~~ x + 2 ~~~ // OK: lvalue + rvalue
~~~ 2 + x ~~~ // OK: rvalue + lvalue
19
20
21
Unary &
&e is a valid expression only if e is an lvalue.
int n, *p;
~~~
p = &n; // OK: n is an lvalue
&n = p; // error: &n is an rvalue
22
Unary *
In contrast to unary &, unary * yields an lvalue.
A pointer p can point to an object, so *p is an lvalue.
int a[N];
int *p = a;
char *s = NULL; // = nullptr in Modern C++
~~~
*p = 3; // OK
*s = '\0'; // undefined behavior
23
Unary *
Again, the result of the * operator is an lvalue.
However, its operand can be an rvalue.
For example,
*(p + 1) = 4; // OK
24
C and C++ let you assume that lvalues always do occupy storage.
25
Non-Modifiable Lvalues
In fact, not all lvalues can appear on the left of an assignment.
26
Non-Modifiable Lvalues
Lvalues and rvalues provide a vocabulary for describing subtle
behavioral differences…
…such as between enumeration constants and const objects.
27
Non-Modifiable Lvalues
When MAX appears in an expression, it yields an integer rvalue.
28
Non-Modifiable Lvalues
On the other hand, this MAX is a const-qualified object:
29
Recap
This table summarizes the behavior of lvalues and rvalues (of
non-class type):
30
Const Objects
A const object is addressable.
The compiler may generate storage to hold the const object’s
value.
The compiler might find that the program never needs storage
for a particular const object.
It often does.
In that case, the compiler need not allocate storage for that
object.
31
Reference Types
The concepts of lvalues and rvalues help explain C++ reference
types.
32
Reference Types
Consider the following code:
33
Reference Types
A reference is essentially a pointer that’s automatically
dereferenced each time it’s used.
You can rewrite most, if not all, code that uses a reference as code
that uses a const pointer, as in:
34
For instance,
35
36
37
38
40
int j = ++i; // OK
month n = ++m; // OK
42
43
That is, the calls look and act very much the same…
44
T x;
~~~
f(x); // by value, or by "reference to const"?
45
46
int i;
~~~
double *pd = &i; // can't convert pointers
double &rd = i; // can't bind this, either
47
48
49
50
long double x;
void f(long double ld); // by value
~~~
51
long double x;
void f(long double const &ld); // by reference to const
~~~
52
53
class string {
public:
string(string const &);
string(char const *); // converting constructor
string &operator=(string const &);
~~~
};
54
string s = "hello";
string t = "world";
~~~
s = s + ", " + t;
55
56
References
C++11 introduced another kind of reference.
57
Rvalue References
Whereas an lvalue reference declaration uses the & operator, an
rvalue reference uses the && operator.
For example, this declares ri to be an “rvalue reference to int”:
Rvalue References
Rvalue references bind only to rvalues.
For example,
int n = 10;
int &&ri = n; // error: n is an lvalue
int const &&rj = n; // error: n is an lvalue
59
Move Operations
Modern C++ uses rvalue references to implement move
operations that can avoid unnecessary copying:
class string {
public:
// copy operations
string(string const &); // constructor
string &operator=(string const &); // assignment
// move operations
string(string &&) noexcept; // constructor
string &operator=(string &&) noexcept; // assignment
};
60
Value Categories
Modern C++ introduces a more complex categorization of
expressions:
expression
glvalue rvalue
61
62