Lvalues and Rvalues
The value of every expression is classified as either an lvalue or an rvalue. A simple way of differentiating the two is thinking of lvalues as actual variables (including elements of arrays and associative arrays), and rvalues as temporary results of expressions (including literals).
As a demonstration, the first writeln()
expression below uses only lvalues and the other one uses only rvalues:
import std.stdio; void main() { int i; immutable(int) imm; auto arr = [ 1 ]; auto aa = [ 10 : "ten" ]; /* All of the following arguments are lvalues. */ writeln(i, // mutable variable imm, // immutable variable arr, // array arr[0], // array element aa[10]); // associative array element // etc. enum message = "hello"; /* All of the following arguments are rvalues. */ writeln(42, // a literal message, // a manifest constant i + 1, // a temporary value calculate(i)); // return value of a function // etc. } int calculate(int i) { return i * 2; }
Limitations of rvalues
Compared to lvalues, rvalues have the following three limitations.
Rvalues don't have memory addresses
An lvalue has a memory location to which we can refer, while an rvalue does not.
For example, it is not possible to take the address of the rvalue expression a + b
in the following program:
import std.stdio; void main() { int a; int b; readf(" %s", &a); // ← compiles readf(" %s", &(a + b)); // ← compilation ERROR }
Error: a + b is not an lvalue
Rvalues cannot be assigned new values
If mutable, an lvalue can be assigned a new value, while an rvalue cannot be:
a = 1; // ← compiles (a + b) = 2; // ← compilation ERROR
Error: a + b is not an lvalue
Rvalues cannot be passed to functions by reference
An lvalue can be passed to a function that takes a parameter by reference, while an rvalue cannot be:
void incrementByTen(ref int value) { value += 10; } // ... incrementByTen(a); // ← compiles incrementByTen(a + b); // ← compilation ERROR
Error: function deneme.incrementByTen (ref int value)
is not callable using argument types (int)
The main reason for this limitation is the fact that a function taking a ref
parameter can hold on to that reference for later use, at a time when the rvalue would not be available.
Different from languages like C++, in D an rvalue cannot be passed to a function even if that function does not modify the argument:
void print(ref const(int) value) { writeln(value); } // ... print(a); // ← compiles print(a + b); // ← compilation ERROR
Error: function deneme.print (ref const(int) value)
is not callable using argument types (int)
Using auto ref
parameters to accept both lvalues and rvalues
As it was mentioned in the previous chapter, auto ref
parameters of function templates can take both lvalues and rvalues.
When the argument is an lvalue, auto ref
means by reference. On the other hand, since rvalues cannot be passed to functions by reference, when the argument is an rvalue, it means by copy. For the compiler to generate code differently for these two distinct cases, the function must be a template.
We will see templates in a later chapter. For now, please accept that the highlighted empty parentheses below make the following definition a function template.
void incrementByTen()(auto ref int value) { /* WARNING: The parameter may be a copy if the argument is * an rvalue. This means that the following modification * may not be observable by the caller. */ value += 10; } void main() { int a; int b; incrementByTen(a); // ← lvalue; passed by reference incrementByTen(a + b); // ← rvalue; copied }
It is possible to determine whether the parameter is an lvalue or an rvalue by using __traits(isRef)
with static if
:
void incrementByTen()(auto ref int value) { static if (__traits(isRef, value)) { // 'value' is passed by reference } else { // 'value' is copied } }
We will see static if
and __traits
later in the Conditional Compilation chapter.
Terminology
The names "lvalue" and "rvalue" do not represent the characteristics of these two kinds of values accurately. The initial letters l and r come from left and right, referring to the left- and the right-hand side expressions of the assignment operator:
- Assuming that it is mutable, an lvalue can be the left-hand expression of an assignment operation.
- An rvalue cannot be the left-hand expression of an assignment operation.
The terms "left value" and "right value" are confusing because in general both lvalues and rvalues can be on either side of an assignment operation:
// rvalue 'a + b' on the left, lvalue 'a' on the right:
array[a + b] = a;