The arguments side, lens[2], rd, and *pd are type double data objects with names, so it is possible to generate a reference for them, and no temporary variables are needed. (Recall that an element of an array behaves like a variable of the same type as the element.) But although edge is a variable, it is of the wrong type. A reference to a double can’t refer to a long. The arguments 7.0 and side + 10.0, on the other hand, are the right type, but they are not named data objects. In each of these cases, the compiler generates a temporary, anonymous variable and makes ra refer to it. These temporary variables last for the duration of the function call, but then the compiler is free to dump them.
So why is this behavior okay for constant references but not otherwise? Recall the swapr() function from Listing 8.4:
void swapr(int & a, int & b) // use references
{
int temp;
temp = a; // use a, b for values of variables
a = b;
b = temp;
}
What would happen if you did the following under the freer rules of early C++?
long a = 3, b = 5;
swapr(a, b);
Here there is a type mismatch, so the compiler would create two temporary int variables, initialize them to 3 and 5, and then swap the contents of the temporary variables, leaving a and b unaltered.
In short, if the intent of a function with reference arguments is to modify variables passed as arguments, situations that create temporary variables thwart that purpose. The solution is to prohibit creating temporary variables in these situations, and that is what the C++ Standard now does. (However, some compilers still, by default, issue warnings instead of error messages, so if you see a warning about temporary variables, don’t ignore it.)
Now think about the refcube() function. Its intent is merely to use passed values, not to modify them, so temporary variables cause no harm and make the function more general in the sorts of arguments it can handle. Therefore, if the declaration states that a reference is const, C++ generates temporary variables when necessary. In essence, a C++ function with a const reference formal argument and a nonmatching actual argument mimics the traditional passing by value behavior, guaranteeing that the original data is unaltered and using a temporary variable to hold the value.
Note
If a function call argument isn’t an lvalue or does not match the type of the corresponding const reference parameter, C++ creates an anonymous variable of the correct type, assigns the value of the function call argument to the anonymous variable, and has the parameter refer to that variable.
Use const When You Can
There are three strong reasons to declare reference arguments as references to constant data:
• Using const protects you against programming errors that inadvertently alter data.
• Using const allows a function to process both const and non-const actual arguments, whereas a function that omits const in the prototype only can accept non-const data.
• Using a const reference allows the function to generate and use a temporary variable appropriately.
You should declare formal reference arguments as const whenever it’s appropriate to do so.
C++11 introduces a second kind of reference, called an
double && rref = std::sqrt(36.00); // not allowed for double &
double j = 15.0;
double && jref = 2.0* j + 18.5; // not allowed for double &
std::cout << rref << '\n'; // display 6.0
std::cout << jref << '\n'; // display 48.5;
The rvalue reference was introduced mainly to help library designers provide more efficient implementations of certain operations. Chapter 18, “Visiting will the New C++ Standard,” discusses how rvalue references are used to implement an approach called move semantics. The original reference type (the one declared using a single &) is now called an lvalue reference.
Using References with a Structure
References work wonderfully with structures and classes, C++’s user-defined types. Indeed, references were introduced primarily for use with these types, not for use with the basic built-in types.
The method for using a reference to a structure as a function parameter is the same as the method for using a reference to a basic variable: You just use the & reference operator when declaring a structure parameter. For example, suppose we have the following definition of a structure:
struct free_throws
{
std::string name;
int made;
int attempts;
float percent;
};
Then a function using a reference to this type could be prototyped as follows:
void set_pc(free_throws & ft); // use a reference to a structure
If the intent is that the function doesn’t alter the structure, use const:
void display(const free_throws & ft); // don't allow changes to structure