const
As with any other object, we can bind a reference to an object of a const
type. To do so we use a reference to const
, which is a reference that refers to a const
type. Unlike an ordinary reference, a reference to const
cannot be used to change the object to which the reference is bound:
const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are const
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object
Because we cannot assign directly to ci
, we also should not be able to use a reference to change ci
. Therefore, the initialization of r2
is an error. If this initialization were legal, we could use r2
to change the value of its underlying object.
const
In § 2.3.1 (p. 51) we noted that there are two exceptions to the rule that the type of a reference must match the type of the object to which it refers. The first exception is that we can initialize a reference to const
from any expression that can be converted (§ 2.1.2, p. 35) to the type of the reference. In particular, we can bind a reference to const
to a nonconst
object, a literal, or a more general expression:
int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1 * 2; // ok: r3 is a reference to const
int &r4 = r * 2; // error: r4 is a plain, non const reference
The easiest way to understand this difference in initialization rules is to consider what happens when we bind a reference to an object of a different type:
double dval = 3.14;
const int &ri = dval;
Here ri
refers to an int
. Operations on ri
will be integer operations, but dval
is a floating-point number, not an integer. To ensure that the object to which ri
is bound is an int
, the compiler transforms this code into something like
const int temp = dval; // create a temporary const int from the double
const int &ri = temp; // bind ri to that temporary
In this case, ri
is bound to a temporary object. A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an expression. C++ programmers often use the word temporary as an abbreviation for temporary object.
Now consider what could happen if this initialization were allowed but ri
was not const
. If ri
weren’t const
, we could assign to ri
. Doing so would change the object to which ri
is bound. That object is a temporary, not dval
. The programmer who made ri
refer to dval
would probably expect that assigning to ri
would change dval
. After all, why assign to ri
unless the intent is to change the object to which ri
is bound? Because binding a reference to a temporary is almost surely not what the programmer intended, the language makes it illegal.
const
May Refer to an Object That Is Not const
It is important to realize that a reference to const
restricts only what we can do through that reference. Binding a reference to const
to an object says nothing about whether the underlying object itself is const
. Because the underlying object might be nonconst
, it might be changed by other means:
int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const
Binding r2
to the (nonconst
) int i
is legal. However, we cannot use r2
to change i
. Even so, the value in i
still might change. We can change i
by assigning to it directly, or by assigning to another reference bound to i
, such as r1
.