If you do this, the compiler generates an error message when it finds code altering the value of ra.
Incidentally, if you need to write a function along the lines of this example (that is, using a basic numeric type), you should use passing by value rather than the more exotic passing by reference. Reference arguments become useful with larger data units, such as structures and classes, as you’ll soon see.
Functions that pass by value, such as the cube() function in Listing 8.5, can use many kinds of actual arguments. For example, all the following calls are valid:
double z = cube(x + 2.0); // evaluate x + 2.0, pass value
z = cube(8.0); // pass the value 8.0
int k = 10;
z = cube(k); // convert value of k to double, pass value
double yo[3] = { 2.2, 3.3, 4.4};
z = cube (yo[2]); // pass the value 4.4
Suppose you try similar arguments for a function with a reference parameter. It would seem that passing a reference should be more restrictive. After all, if ra is the alternative name for a variable, then the actual argument should be that variable. Something like the following doesn’t appear to make sense because the expression x + 3.0 is not a variable:
double z = refcube(x + 3.0); // should not compile
For example, you can’t assign a value to such an expression:
x + 3.0 = 5.0; // nonsensical
What happens if you try a function call like refcube(x + 3.0)? In contemporary C++, that’s an error, and most compilers will tell you so. Some older ones give you a warning along the following lines:
Warning: Temporary used for parameter 'ra' in call to refcube(double &)
The reason for this milder response is that C++, in its early years, did allow you to pass expressions to a reference variable. In some cases, it still does. What happens is that because x + 3.0 is not a type double variable, the program creates a temporary, nameless variable, initializing it to the value of the expression x + 3.0. Then ra becomes a reference to that temporary variable. Let’s take a closer look at temporary variables and see when they are and are not created.
Temporary Variables, Reference Arguments, and const
C++ can generate a temporary variable if the actual argument doesn’t match a reference argument. Currently, C++ permits this only if the argument is a const reference, but this was not always the case. Let’s look at the cases in which C++ does generate temporary variables and see why the restriction to a const reference makes sense.
First, when is a temporary variable created? Provided that the reference parameter is a const, the compiler generates a temporary variable in two kinds of situations:
• When the actual argument is the correct type but isn’t an
• When the actual argument is of the wrong type, but it’s of a type that can be converted to the correct type
What is an lvalue? An argument that’s an lvalue is a data object that can be referenced by address. For example, a variable, an array element, a structure member, a reference, and a dereferenced pointer are lvalues. Non-lvalues include literal constants (aside from quoted strings, which are represented by their addresses) and expressions with multiple terms. The term
Now, to return to our example, suppose you redefine refcube() so that it has a constant reference argument:
double refcube(const double &ra)
{
return ra * ra * ra;
}
Next, consider the following code:
double side = 3.0;
double * pd = &side
double & rd = side;
long edge = 5L;
double lens[4] = { 2.0, 5.0, 10.0, 12.0};
double c1 = refcube(side); // ra is side
double c2 = refcube(lens[2]); // ra is lens[2]
double c3 = refcube(rd); // ra is rd is side
double c4 = refcube(*pd); // ra is *pd is side
double c5 = refcube(edge); // ra is temporary variable
double c6 = refcube(7.0); // ra is temporary variable
double c7 = refcube(side + 10.0); // ra is temporary variable