CONTENTS

Chapter 8. ADVENTURES IN FUNCTIONS

In this chapter you learn

With the last chapter under your belt, you now know a lot about C++ functions, but there's much more to come. C++ provides many new function features that separate C++ from its C heritage. The new features include inline functions, passing variables by reference, default argument values, function overloading (polymorphism), and template functions. This chapter, more than any other you've read so far, explores features found in C++ but not C, so it marks your first major foray into plus-plussedness.

Inline Functions

Let's begin by examining inline functions, a C++ enhancement designed to speed up programs. The primary distinction between normal functions and inline functions is not in how you code them but in how the C++ compiler incorporates them into a program. To understand the distinction between inline functions and normal functions, you need to peer more deeply into a program's innards than we have so far. Let's do that now.

The final product of the compilation process is an executable program, which consists of a set of machine language instructions. When you start a program, the operating system loads these instructions into the computer's memory, so each instruction has a particular memory address. The computer then goes through these instructions step-by-step. Sometimes, as when you have a loop or a branching statement, program execution will skip over instructions, jumping back or forward to a particular address. Normal function calls also involve having a program jump to another address (the function's address) and then jump back when the function terminates. Let's look at a typical implementation of that process in a little more detail. When the program reaches the function call instruction, the program stores the memory address of the instruction immediately following the function call, copies function arguments to the stack (a block of memory reserved for that purpose), jumps to the memory location that marks the beginning of the function, executes the function code (perhaps placing a return value in a register), and then jumps back to the instruction whose address it saved.[1] Jumping back and forth and keeping track of where to jump means that there is an overhead in elapsed time to using functions.

The C++ inline function provides an alternative. This is a function whose compiled code is "in line" with the other code in the program. That is, the compiler replaces the function call with the corresponding function code. With inline code, the program doesn't have to jump to another location to execute the code and then jump back. Inline functions thus run a little faster than regular functions, but there is a memory penalty. If a program calls an inline function at 10 separate locations, then the program winds up with ten copies of the function inserted into the code. (See Figure 8.1.)

Figure 8.1. Inline functions versus regular functions.

graphics/08fig01.gif

You should be selective about using inline functions. If the time needed to execute the function code is long compared to the time needed to handle the function call mechanism, then the time saved is a relatively small fraction of the entire process. If the code execution time is short, then an inline call can save a large fraction of the time used by the non-inline call. On the other hand, you are now saving a large fraction of a relatively quick process, so the absolute time savings may not be that great unless the function is called frequently.

To use this feature, you must take at least one of two actions:

A common practice is to omit the prototype and to place the entire definition (meaning the function header and all the function code) where the prototype normally would go.

The compiler does not have to honor your request to make a function inline. It might decide the function is too large or notice that it calls itself (recursion is not allowed or indeed possible for inline functions), or the feature might not be implemented for your particular compiler.

Listing 8.1 illustrates the inline technique with an inline square() function that squares its argument. Note that we've placed the entire definition on one line. That's not required, but if the definition doesn't fit on one line, the function probably is a poor candidate for an inline function.

Listing 8.1 inline.cpp
// inline.cpp -- use an inline function
#include <iostream>
using namespace std;

// an inline function definition
inline double square(double x) {  return x * x; }

int main()
{
    double a, b;
    double c = 13.0;

    a = square(5.0);
    b = square(4.5 + 7.5);   // can pass expressions
    cout << "a = " << a << ", b = " << b << "\ n";
    cout << "c = " << c;
    cout << ", c squared = " << square(c++) << "\ n";
    cout << "Now c = " << c << "\ n";
    return 0;
}

Here's the output:

a = 25, b = 144
c = 13, c squared = 169
Now c = 14

The output illustrates that inline functions pass arguments by value just like regular functions do. If the argument is an expression, such as 4.5 + 7.5, the function passes the value of the expression, 12 in this case. This makes C++'s inline facility far superior to C's macro definitions. See the "Inline Versus Macros" sidebar.

Even though the program doesn't provide a separate prototype, C++'s prototyping features still are in play. That's because the entire definition, which comes before the function's first use, serves as a prototype. This means you can use square() with an int argument or a long argument, and the program automatically type casts the value to type double before passing it to the function.

Inline Versus Macros

graphics/common.gif

The inline facility is a C++ addition. C uses the preprocessor #define statement to provide macros, a crude implementation of inline code. For example, here's a macro for squaring a number:

#define SQUARE(X) X*X

This works not by passing arguments but by text substitution, with the X acting as a symbolic label for the "argument":

a = SQUARE(5.0); is replaced by a = 5.0*5.0;
b = SQUARE(4.5 + 7.5); is replaced by b = 4.5 + 7.5 * 4.5 + 7.5;
d = SQUARE(c++); is replaced by d = c++*c++;

Only the first example works properly. You can improve matters with a liberal application of parentheses:

#define SQUARE(X) ((X)*(X))

Still, the problem remains that macros don't pass by value. Even with this new definition, SQUARE(c++) increments c twice, but the inline square() function in Listing 8.1 evaluates c, passes that value to be squared, and then increments c once.

The intent here is not to show you how to write C macros. Rather, it is to suggest that if you have been using C macros to perform function-like services, consider converting them to C++ inline functions.

Reference Variables

C++ adds a new compound type to the language梩he reference variable. A reference is a name that acts as an alias, or alternative name, for a previously defined variable. For example, if you make twain a reference to the clemens variable, you can use twain and clemens interchangeably to represent that variable. Of what use is such an alias? Is it to help people who are embarrassed by their choice of variable names? Maybe, but the main use for a reference is as a formal argument to a function. By using a reference as an argument, the function works with the original data instead of with a copy. References provide a convenient alternative to pointers for processing large structures with a function, and they are essential for designing classes. Before you see how to use references with functions, however, let's examine the basics of defining and using a reference. Keep in mind that the purpose of the following discussion is to illustrate how references work, not how they typically are used.

Creating a Reference Variable

You might recall that C and C++ use the & symbol to indicate the address of a variable. C++ assigns an additional meaning to the & symbol and presses it into service for declaring references. For example, to make rodents an alternative name for the variable rats, do the following:

int rats;
int & rodents = rats;    // makes rodents an alias for rats

In this context, & is not the address operator. Instead, it serves as part of the type identifier. Just as char * in a declaration means pointer-to-char, int & means reference-to-int. The reference declaration allows you to use rats and rodents interchangeably; both refer to the same value and the same memory location. Listing 8.2 illustrates the truth of this claim.

Listing 8.2 firstref.cpp
// firstref.cpp -- defining and using a reference
#include <iostream>
using namespace std;
int main()
{
    int rats = 101;
    int & rodents = rats;   // rodents is a reference

    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << "\ n";
    rodents++;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << "\ n";

// some implementations require type casting the following
// addresses to type unsigned
    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << "\ n";
    return 0;

}

Note that the & operator in the statement

int & rodents = rats;

is not the address operator but declares that rodents is of type int &, that is, a reference to an int variable. But the & operator in the statement

cout<<", rodents address ="<< &rodents << "\ n";

is the address operator, with &rodents representing the address of the variable to which rodents refers. Here is the program's output:

rats = 101, rodents = 101
rats = 102, rodents = 102
rats address = 0x0065fd48, rodents address = 0x0065fd48

As you can see, both rats and rodents have the same value and the same address. Incrementing rodents by 1 affects both variables. More precisely, the rodents++ operation increments a single variable for which we have two names. (Again, keep in mind that although this example shows you how a reference works, it doesn't represent the typical use for a reference, which is as a function parameter, particularly for structure and object arguments. We look into these uses pretty soon.)

References tend to be a bit confusing at first to C veterans coming to C++ because they are tantalizingly reminiscent of pointers, yet somehow different. For example, you can create both a reference and a pointer to refer to rats:

int rats = 101;
int & rodents = rats;   // rodents a reference
int * prats = &rats;    // prats a pointer

Then, you could use the expressions rodents and *prats interchangeably with rats and use the expressions &rodents and prats interchangeably with &rats. From this standpoint, a reference looks a lot like a pointer in disguised notation in which the * dereferencing operator is understood implicitly. And, in fact, that's more or less what a reference is. But there are differences besides those of notation. For one, it is necessary to initialize the reference when you declare it; you can't declare the reference and then assign it a value later the way you can with a pointer:

int rat;
int & rodent;
rodent = rat;   // No, you can't do this.

Remember

graphics/arrow.gif

You should initialize a reference variable when you declare it.

A reference is more like a const pointer; you have to initialize it when you create it, and once a reference pledges its allegiance to a particular variable, it sticks to its pledge. That is,

int & rodents = rats;

is, in essence, a disguised notation for something like this:

int * const pr = &rats;

Here, the reference rodents plays the same role as the expression *pr.

Listing 8.3 shows what happens if you try to make a reference change allegiance from a rats variable to a bunnies variable.

Listing 8.3 secref.cpp
// secref.cpp -- defining and using a reference
#include <iostream>
using namespace std;
int main()
{
    int rats = 101;
    int & rodents = rats;   // rodents is a reference

    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << "\ n";

    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << "\ n";

    int bunnies = 50;
    rodents = bunnies;       // can we change the reference?
    cout << "bunnies = " << bunnies;
    cout << ", rats = " << rats;
    cout << ", rodents = " << rodents << "\ n";

    cout << "bunnies address = " << &bunnies;
    cout << ", rodents address = " << &rodents << "\ n";
    return 0;
}

Here's the output:

rats = 101, rodents = 101
rats address = 0x0065fd44, rodents address = 0x0065fd44
bunnies = 50, rats = 50, rodents = 50
bunnies address = 0x0065fd48, rodents address = 0x0065fd4

Initially, rodents refers to rats, but then the program apparently attempts to make rodents a reference to bunnies:

rodents = bunnies;

For a moment, it looks as if this attempt has succeeded, for the value of rodents changes from 101 to 50. But closer inspection reveals that rats also has changed to 50 and that rats and rodents still share the same address, which differs from the bunnies address. Because rodents is an alias for rats, the assignment statement really means the same as the following:

rats = bunnies;

That is, it means "assign the value of the bunnies variable to the rat variable." In short, you can set a reference by an initializing declaration, not by assignment.

Suppose you tried the following:

int rats = 101;
int * pi = &rats;
int & rodents = *pt;
int bunnies = 50;
pt = &bunnies;

Initializing rodents to *pt makes rodents refer to rats. Subsequently altering pt to point to bunnies does not alter the fact that rodents refers to rats.

References As Function Parameters

Most often, references are used as function parameters, making a variable name in the function an alias for a variable in the calling program. This method of passing arguments is called passing by reference. Passing by reference allows a called function to access variables in the calling function. C++'s addition of the feature is a break from C, which only passes by value. Passing by value, recall, results in the called function working with copies of values from the calling program. (See Figure 8.2.) Of course, C lets you get around the passing by value limitation by using pointers.

Figure 8.2. Passing by value and passing by reference.

graphics/08fig02.gif

Let's compare using references and using pointers in a common computer problem: swapping the values of two variables. A swapping function has to be able to alter values of variables in the calling program. That means the usual approach of passing variables by value won't work, because the function will end up swapping the contents of copies of the original variables instead of the variables themselves. If you pass references, however, the function can work with the original data. Alternatively, you can pass pointers in order to access the original data. Listing 8.4 shows all three methods, including the one that doesn't work, so that you can compare them.

Listing 8.4 swaps.cpp
// swaps.cpp -- swapping with references and with pointers
#include <iostream>
using namespace std;
void swapr(int & a, int & b);   // a, b are aliases for ints
void swapp(int * p, int * q);   // p, q are addresses of ints
void swapv(int a, int b);       // a, b are new variables
int main()
{
    int wallet1 = 300;
    int wallet2 = 350;

    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << "\ n";

    cout << "Using references to swap contents:\ n";
    swapr(wallet1, wallet2);   // pass variables
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << "\ n";

    cout << "Using pointers to swap contents:\ n";
    swapp(&wallet1, &wallet2); // pass addresses of variables
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << "\ n";

    cout << "Trying to use passing by value:\ n";
    swapv(wallet1, wallet2);   // pass values of variables
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << "\ n";
    return 0;
}

void swapr(int & a, int & b)    // use references{
    int temp;

    temp = a;       // use a, b for values of variables
    a = b;
    b = temp;
}

void swapp(int * p, int * q)    // use pointers
{
    int temp;

    temp = *p;      // use *p, *q for values of variables
    *p = *q;
    *q = temp;
}

void swapv(int a, int b)        // try using values
{
    int temp;
    temp = a;      // use a, b for values of variables
    a = b;
    b = temp;
}

Here's the output:

wallet1 = $300 wallet2 = $350           - original values
Using references to swap contents:
wallet1 = $350 wallet2 = $300           - values swapped
Using pointers to swap contents:
wallet1 = $300 wallet2 = $350           - values swapped again
Trying to use passing by value:
wallet1 = $300 wallet2 = $350           - swap failed

As we expected, the reference and pointer methods both successfully swap the contents of the two wallets, whereas the passing by value method fails.

Program Notes

First, note how each function is called:

swapr(wallet1, wallet2);        // pass variables
swapp(&wallet1, &wallet2);      // pass addresses of variables
swapv(wallet1, wallet2);        // pass values of variables

Passing by reference (swapr(wallet1, wallet2)) and passing by value (swapv(wallet1, wallet2)) look identical. The only way you can tell that swapr() passes by reference is by looking at the prototype or the function definition. However, the presence of the address operator (&) makes it obvious when a function passes by address ((swapp(&wallet1, &wallet2)). (Recall that the type declaration int *p means that p is a pointer to an int and therefore the argument corresponding to p should be an address, such as &wallet1.)

Next, compare the code for the functions swapr() (passing by reference) and swapv() (passing by value). The only outward difference between the two is how the function parameters are declared:

void swapr(int & a, int & b)
void swapv(int a, int b)

The internal difference, of course, is that in swapr() the variables a and b serve as aliases for wallet1 and wallet2, so swapping a and b swaps wallet1 and wallet2. But in swapv(), the variables a and b are new variables that copy the values of wallet1 and wallet2, so swapping a and b has no effect on wallet1 and wallet2.

Finally, compare the functions swapr() (passing a reference) and swapp() (passing a pointer). The first difference is in how the function parameters are declared:

void swapr(int & a, int & b)
void swapp(int * p, int * q)

The second difference is that the pointer version requires using the * dereferencing operator throughout when the function uses p and q.

Earlier, we said you should initialize a reference variable when you define it. You can consider reference function arguments as being initialized to the argument passed by the function call. That is, the function call

swapr(wallet1, wallet2);

initializes the formal parameter a to wallet1 and the formal parameter b to wallet2.

Reference Properties and Oddities

Using reference arguments has several twists about which you need to know. First, consider Listing 8.5. It uses two functions to cube an argument. One takes a type double argument, whereas the other takes a reference to double. The actual code for cubing is purposefully a bit odd to illustrate a point.

Listing 8.5 cubes.cpp
// cubes.cpp -- regular and reference arguments
#include <iostream>
using namespace std;
double cube(double a);
double refcube(double &ra);
int main ()
{
    double x = 3.0;

    cout << cube(x);
    cout << " = cube of " << x << "\n";
    cout << refcube(x);
    cout << " = cube of " << x << "\n";
    return 0;
}

double cube(double a)
{
    a *= a * a;
    return a;
}

double refcube(double &ra)
{
    ra *= ra * ra;
    return ra;
}

Here is the output:

27 = cube of 3
27 = cube of 27

Note that the refcube() function modifies the value of x in main() whereas cube() doesn't, which reminds us of why passing by value is the norm. The variable a is local to cube(). It is initialized to the value of x, but changing a has no effect on x. But because refcube() uses a reference argument, the changes it makes to ra actually are made to x. If your intent is that a function use the information passed to it without modifying the information, and if you're using a reference, you should use a constant reference. Here, for example, we should have used const in the function prototype and function heading:

double refcube(const double &ra);

Had we done this, the compiler would have generated an error message when it found code altering the value of ra.

Incidentally, if you need to write a function along the lines of this example, 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 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

double z = refcube(x + 3.0);  // may not compile

doesn't appear to make sense because the expression x + 3.0 is not a variable. 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 some compilers will tell you so. Others 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 this: 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 is a new restriction. 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:

An argument that's an Lvalue is a data object that can be referenced. For example, a variable, an array element, a structure member, a reference, and a dereferenced pointer are Lvalues. Non-Lvalues include literal constants and expressions with multiple terms. For example, suppose we redefine refcube() so that it has a constant reference argument:

double refcube(const double &ra)
{
     return ra * ra * ra;
}

Now 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

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 edge, although a variable, 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 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 of 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 we 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 do 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 that 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.

Remember

graphics/arrow.gif

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

graphics/common.gif

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, while a function omitting 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.

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 is the same as the method for using a reference to a basic variable; just use the & reference operator when declaring a structure parameter. The program in Listing 8.6 does exactly that. It also adds an interesting twist by having a function return a reference. This makes it possible to use a function invocation as an argument to a function. Well, that's true of any function with a return value. But it also makes it possible to assign a value to a function invocation, and that's possible only with a reference return type. We'll explain these points after showing the program's output. The program has a use() function that displays two members of a structure and increments a third member. Thus, the third member can keep track of how many times a particular structure has been handled by the use() function.

Listing 8.6 strtref.cpp
// strtref.cpp -- using structure references
#include <iostream>
using namespace std;
struct sysop
{
    char name[26];
    char quote[64];
    int used;
};

sysop & use(sysop & sysopref);  // function with a reference return type

int main()
{
// NOTE: some implementations require using the keyword static
// in the two structure declarations to enable initialization
    sysop looper =
    {
        "Rick \ "Fortran\ " Looper",
        "I'm a goto kind of guy.",
        0
    };

    use(looper);            // looper is type sysop
    cout << looper.used << " use(s)\n";

    use (use(looper));      // use(looper) is type sysop
    cout << looper.used << " use(s)\n";

    sysop morf =
    {
        "Polly Morf",
        "Polly's not a hacker.",
        0
    };
    use(looper) = morf;     // can assign to function!
    cout << looper.name << " says:\n" << looper.quote << '\n';
    return 0;
}

// use() returns the reference passed to it
sysop & use(sysop & sysopref)
{
    cout << sysopref.name << " says:\n";
    cout << sysopref.quote << "\n";
    sysopref.used++;
    return sysopref;
}

Here's the output:

Rick "Fortran" Looper says:
I'm a goto kind of guy.
1 use(s)
Rick "Fortran" Looper says:
I'm a goto kind of guy.
Rick "Fortran" Looper says:
I'm a goto kind of guy.
3 use(s)
Rick "Fortran" Looper says:
I'm a goto kind of guy.
Polly Morf says:
Polly's not a hacker.
Program Notes

The program ventures into three new areas. The first is using a reference to a structure, illustrated by the first function call:

use(looper);

It passes the structure looper by reference to the use() function, making sysopref a synonym for looper. When the use() function displays the name and quote members of sysopref, it really displays the members of looper. Also, when the function increments sysopref.used to 1, it really increments looper.used, as the program output shows:

Rick "Fortran" Looper says:
I'm a goto kind of guy.
1 use(s)

The second new area is using a reference as a return value. Normally, the return mechanism copies the returned value to a temporary storage area, which the calling program then accesses. Returning a reference, however, means that the calling program accesses the return value directly without there being a copy. Typically, the reference refers to a reference passed to the function in the first place, so the calling function actually winds up directly accessing one of its own variables. Here, for example, sysopref is a reference to looper, so the return value is the original looper variable in main().

Because use() returns a type sysop reference, it can be used as an argument to any function expecting either a sysop argument or a reference-to-sysop argument, such as use() itself. Thus, the next function call in Listing 8.6 is really two function calls, with one function's return value serving as the argument for the second:

use(use(looper));

The inner function call prints the name and quote members and increments the used member to 2. The function returns sysopref, reducing what's left to the following:

use(sysopref);

Because sysopref is a reference to looper, this function call is equivalent to the following:

use(looper);

So use() displays the two string members again and increments the used member to 3.

Remember

graphics/arrow.gif

A function that returns a reference is actually an alias for the referred-to variable.

The third new area the program explores is that you can assign a value to a function if that function has a reference type return value:

use(looper) = morf;

For nonreference return values, this assignment is a syntax error, but it's okay for use(). This is the order of events. First, the use() function is evaluated. That means looper is passed by reference to use(). As usual, the function displays two members and increments the used member to 4. Then, the function returns the reference. Because the return value refers to looper, this makes the final step equivalent to the following:

looper = morf;

C++ allows you to assign one structure to another, so this copies the contents of the morf structure into looper, as is shown when displaying looper.name produces Morf's name and not Looper's. In short, the statement

use(looper) = morf;  // return value a reference to looper

is equivalent to the following:

use(looper);
looper = morf;

Remember

graphics/arrow.gif

You can assign a value (including a structure or a class object) to a C++ function only if the function returns a reference to a variable or, more generally, to a data object. In that case, the value is assigned to the referred-to variable or data object.

You may have noticed that assigning to a reference return value produces confusing-looking code. The main reason for returning a reference is efficiency, not to enable this peculiar assignment mechanism. However, the assignment property occasionally is handy for some forms of operator redefinition. You can use it, for example, to redefine the array subscript operator [] for a class that defines a string or a more powerful version of an array.

Considerations When Returning a Reference or a Pointer

When a function returns a reference or a pointer to a data object, that object had better continue to exist once the function terminates. The simplest way to do that is to have the function return a reference or pointer that was passed to it as an argument. That way, the reference or pointer already refers to something in the calling program. The use() function in Listing 8.6 uses this technique.

A second method is to use new to create new storage. You've already seen examples in which new creates space for a string and the function returns a pointer to that space. Here's how you could do something similar with a reference:

sysop & clone(sysop & sysopref)
{
    sysop * psysop = new sysop;
    *psysop = sysopref;   // copy info
    return *psysop;       // return reference to copy
}

The first statement creates a nameless sysop structure. The pointer psysop points to the structure, so *psysop is the structure. The code appears to return the structure, but the function declaration indicates the function really returns a reference to this structure. You then could use the function this way:

sysop & jolly = clone(looper);

This makes jolly a reference to the new structure. There is a problem with this approach, which is that you should use delete to free memory allocated by new when the memory is no longer needed. A call to clone() hides the call to new, making it simpler to forget to use delete later. The auto_ptr template discussed in Chapter 16, "The String Class and the Standard Template Library," can help automate the deletion process.

What you want to avoid is code along these lines:

sysop & clone2(sysop & sysopref)
{
      sysop newguy;            // first step to big error
      newguy = sysopref;       // copy info
      return newguy;           // return reference to copy
}

This has the unfortunate effect of returning a reference to a temporary variable (newguy) that passes from existence as soon as the function terminates. (This chapter discusses the persistence of various kinds of variables later, in the section on storage classes.) Similarly, you should avoid returning pointers to such temporary variables.

When to Use Reference Arguments

There are two main reasons for using reference arguments:

The second reason is most important for larger data objects, such as structures and class objects. These two reasons are the same reasons one might have for using a pointer argument. This makes sense, for reference arguments are really just a different interface for pointer-based code. So, when should you use a reference? Use a pointer? Pass by value? Here are some guidelines.

A function uses passed data without modifying it:

A function modifies data in the calling function:

Of course, these are just guidelines, and there might be reasons for making different choices. For example, cin uses references for basic types so that you can use cin >> n instead of cin >> &n.

Default Arguments

Let's look at another topic from C++'s bag of new tricks梩he default argument. A default argument is a value that's used automatically if you omit the corresponding actual argument from a function call. For example, if you set up void wow(int n) so that n has a default value of 1, then the function call wow() is the same as wow(1). This gives you greater flexibility in how you use a function. Suppose you have a function called left() that returns the first n characters of a string, with the string and n as arguments. More precisely, the function returns a pointer to a new string consisting of the selected portion of the original string. For example, the call left("theory", 3) constructs a new string "the" and returns a pointer to it. Now suppose you establish a default value of 1 for the second argument. The call left("theory", 3) would work as before, with your choice of 3 overriding the default. But the call left("theory"), instead of being an error, would assume a second argument of 1 and return a pointer to the string "t". This kind of default is helpful if your program often needs to extract a one-character string but occasionally needs to extract longer strings.

How do you establish a default value? You must use the function prototype. Because the compiler looks at the prototype to see how many arguments a function uses, the function prototype also has to alert the program to the possibility of default arguments. The method is to assign a value to the argument in the prototype. For example, here's the prototype fitting this description of left():

char * left(const char * str, int n = 1);

We want the function to return a new string, so its type is char*, or pointer-to-char. We want to leave the original string unaltered, so we use the const qualifier for the first argument. We want n to have a default value of 1, so we assign that value to n. A default argument value is an initialization value. Thus, the prototype above initializes n to the value 1. If you leave n alone, it has the value 1, but if you pass an argument, the new value overwrites the 1.

When you use a function with an argument list, you must add defaults from right to left. That is, you can't provide a default value for a particular argument unless you also provide defaults for all the arguments to its right:

int harpo(int n, int m = 4, int j = 5);         // VALID
int chico(int n, int m = 6, int j);             // INVALID
int groucho(int k = 1, int m = 2, int n = 3);   // VALID

The harpo() prototype, for example, permits calls with one, two, or three arguments:

beeps = harpo(2);       // same as harpo(2,4,5)
beeps = harpo(1,8);     // same as harpo(1,8,5)
beeps = harpo (8,7,6);  // no default arguments used

The actual arguments are assigned to the corresponding formal arguments from left to right; you can't skip over arguments. Thus, the following isn't allowed:

beeps = harpo(3, ,8);   // invalid, doesn't set m to 4

Default arguments aren't a major programming breakthrough; rather, they are a convenience. When you get to class design, you'll find they can reduce the number of constructors, methods, and method overloads you have to define.

Listing 8.7 puts default arguments to use. Note that only the prototype indicates the default. The function definition is the same as it would have been without default arguments.

Listing 8.7 left.cpp
// left.cpp -- string function with a default argument
#include <iostream>
using namespace std;
const int ArSize = 80;
char * left(const char * str, int n = 1);
int main()
{
    char sample[ArSize];
    cout << "Enter a string:\n";
    cin.get(sample,ArSize);
    char *ps = left(sample, 4);
    cout << ps << "\n";
    delete [] ps;       // free old string
    ps = left(sample);
    cout << ps << "\n";
    delete [] ps;       // free new string
    return 0;
}

// This function returns a pointer to a new string
// consisting of the first n characters in the str string.
char * left(const char * str, int n)
{
    if(n < 0)
        n = 0;
    char * p = new char[n+1];
    int i;
    for (i = 0; i < n && str[i]; i++)
        p[i] = str[i];  // copy characters
    while (i <= n)
        p[i++] = '\ 0';  // set rest of string to '\ 0'
    return p;
}

Here's a sample run:

Enter a string:
forthcoming
fort
f

Program Notes

The program uses new to create a new string for holding the selected characters. One awkward possibility is that an uncooperative user requests a negative number of characters. In that case, the function sets the character count to zero and eventually returns the null string. Another awkward possibility is that an irresponsible user requests more characters than the string contains. The function protects against this by using a combined test:

i < n && str[i]

The i < n test stops the loop after n characters have been copied. The second part of the test, the expression str[i], is the code for the character about to be copied. If the loop reaches the null character, the code is zero, and the loop terminates. The final while loop terminates the string with the null character and then sets the rest of the allocated space, if any, to null characters.

Another approach for setting the size of the new string is to set n to the smaller of the passed value and the string length:

int len = strlen(str);
n = (n < len) ? n : len;    // the lesser of n and len
char * p = new char[n+1];

This ensures that new doesn't allocate more space than what's needed to hold the string. That can be useful if you make a call like left("Hi!", 32767). The first approach copies the "Hi!" into an array of 32767 characters, setting all but the first three characters to the null character. The second approach copies "Hi!" into an array of four characters. But, by adding another function call (strlen()), it increases the program size, slows the process, and requires that you remember to include the cstring (or string.h) header file. C programmers have tended to opt for faster running, more compact code and leave a greater burden on the programmer to use functions correctly. The C++ tradition, however, places greater weight on reliability. After all, a slower program working correctly is better than a fast program that works incorrectly. If the time taken to call strlen() turns out to be a problem, you can let left() determine the lesser of n and the string length directly. For example, the following loop quits when m reaches n or the end of the string, whichever comes first:

int m = 0;
while ( m <= n && str[m] != '\0')
      m++;
char * p = new char[m+1]:
// use m instead of n in rest of code

Function Polymorphism (Function Overloading)

Function polymorphism is a neat C++ addition to C's capabilities. While default arguments let you call the same function using varying numbers of arguments, function polymorphism, also called function overloading, lets you use multiple functions sharing the same name. The word "polymorphism" means having many forms, so function polymorphism lets a function have many forms. Similarly, the expression "function overloading" means you can attach more than one function to the same name, thus overloading the name. Both expressions boil down to the same thing, but we'll usually use the expression function overloading梚t sounds harder- working. You can use function overloading to design a family of functions that do essentially the same thing, but using different argument lists.

Overloaded functions are analogous to verbs having more than one meaning. For example, Miss Piggy can root at the ball park for the home team, and or she can root in the soil for truffles. The context (one hopes) tells you which meaning of root is intended in each case. Similarly, C++ uses the context to decide which version of an overloaded function is intended.

The key to function overloading is a function's argument list, also called the function signature. If two functions use the same number and types of arguments in the same order, they have the same signature; the variable names don't matter. C++ enables you to define two functions by the same name provided that the functions have different signatures. The signature can differ in the number of arguments or in the type of arguments, or both. For example, you can define a set of print() functions with the following prototypes:

void print(const char * str, int width);  // #1
void print(double d, int width);          // #2
void print(long l, int width);            // #3
void print(int i, int width);             // #4
void print(const char *str);              // #5

When you then use a print() function, the compiler matches your use to the prototype that has the same signature:

print("Pancakes", 15);         // use #1
print("Syrup");                // use #5
print(1999.0, 10);             // use #2
print(1999, 12);               // use #4
print(1999L, 15);              // use #3

For example, print("Pancakes", 15) uses a string and an integer as arguments, and that matches prototype #1.

When you use overloaded functions, be sure you use the proper argument types in the function call. For example, consider the following statements:

unsigned int year = 3210;
print(year, 6);           // ambiguous call

Which prototype does the print() call match here? It doesn't match any of them! A lack of a matching prototype doesn't automatically rule out using one of the functions, for C++ will try to use standard type conversions to force a match. If, say, the only print() prototype were #2, the function call print(year, 6) would convert the year value to type double. But in the code above there are three prototypes that take a number as the first argument, providing three different choices for converting year. Faced with this ambiguous situation, C++ rejects the function call as an error.

Some signatures that appear different from each other can't coexist. For example, consider these two prototypes:

double cube(double x);
double cube(double & x);

You might think this is a place you could use function overloading, for the function signatures appear to be different. But consider things from the compiler's standpoint. Suppose you have code like this:

cout << cube(x);

The x argument matches both the double x prototype and the double &x prototype. Thus, the compiler has no way of knowing which function to use. Therefore, to avoid such confusion, when it checks function signatures, the compiler considers a reference to a type and the type itself to be the same signature.

The function matching process does discriminate between const and non-const variables. Consider the following prototypes:

void dribble(char * bits);          // overloaded
void dribble (const char *cbits);   // overloaded
void dabble(char * bits);           // not overloaded
void drivel(const char * bits);     // not overloaded

Here's what various function calls would match:

const char p1[20] = "How's the weather?";
char p2[20] = "How's business?";
dribble(p1);      // dribble(const char *);
dribble(p2);      // dribble(char *);
dabble(p1);       // no match
dabble(p2);       // dabble(char *);
drivel(p1);       // drivel(const char *);
drivel(p2);       // drivel(const char *);

The dribble() function has two prototypes, one for const pointers and one for regular pointers, and the compiler selects one or the other depending on whether or not the actual argument is const. The dabble() function only matches a call with a non-const argument, but the drivel() function matches calls with either const or non-const arguments. The reason for this difference in behavior between drivel() and dabble() is that it's valid to assign a non-const value to a const variable, but not vice versa.

Keep in mind that it's the signature, not the function type, that enables function overloading. For example, the following two declarations are incompatible:

long gronk(int n, float m);      // same signatures,
double gronk(int n, float m);    // hence not allowed

Therefore, C++ won't permit you to overload gronk() in this fashion. You can have different return types, but only if the signatures also are different:

long gronk(int n, float m);        // different signatures,
double gronk(float n, float m);    // hence allowed

After we discuss templates later in this chapter, we'll further discuss function matching.

An Overloading Example

We've already developed a left() function that returns a pointer to the first n characters in a string. Let's add a second left() function, one that returns the first n digits in an integer. You can use it, for example, to examine the first three digits of a U.S. postal ZIP code stored as an integer, a useful act if you want to sort for urban areas.

The integer function is a bit more difficult to program than the string version, because we don't have the benefit of each digit being stored in its own array element. One approach is first to compute the number of digits in the number. Dividing a number by 10 lops off one digit, so you can use division to count digits. More precisely, you can do so with a loop like this:

unsigned digits = 1;
while (n /= 10)
    digits++;

This loop counts how many times you can remove a digit from n until none are left. Recall that n /= 10 is short for n = n / 10. If n is 8, for example, the test condition assigns to n the value 8 / 10, or 0, because it's integer division. That terminates the loop, and digits remains at 1. But if n is 238, the first loop test sets n to 238 / 10, or 23. That's nonzero, so the loop increases digits to 2. The next cycle sets n to 23 / 10, or 2. Again, that's nonzero, so digits grows to 3. The next cycle sets n to 2 / 10, or 0, and the loop quits, leaving digits set to the correct value, 3.

Now suppose you know the number has five digits, and you want to return the first three digits. You can get that value by dividing the number by 10 and then dividing the answer by 10 again. Each division by 10 lops one more digit off the right end. To calculate the number of digits to lop, just subtract the number of digits to be shown from the total number of digits. For example, to show four digits of a nine-digit number, lop off the last five digits. You can code this approach as follows:

ct = digits - ct;
while (ct--)
    num /= 10;
return num;

Listing 8.8 incorporates this code into a new left() function. The function includes some additional code to handle special cases, such as asking for zero digits or asking for more digits than the number possesses. Because the signature of the new left() differs from that of the old left(), we can, and do, use both functions in the same program.

Listing 8.8 leftover.cpp
// leftover.cpp -- overloading the left() function
#include <iostream>
using namespace std;
unsigned long left(unsigned long num, unsigned ct);
char * left(const char * str, int n = 1);
int main()
{
    char * trip = "Hawaii!!";   // test value
    unsigned long n = 12345678; // test value
    int i;
    char * temp;

    for (i = 1; i < 10; i++)
    {
        cout << left(n, i) << "\ n";
        temp = left(trip,i);
        cout << temp << "\ n";
        delete [] temp; // point to temporary storage
    }
    return 0;

}
// This function returns the first ct digits of the number num.
unsigned long left(unsigned long num, unsigned ct)
{
    unsigned digits = 1;
    unsigned long n = num;

    if (ct == 0 || num == 0)
        return 0;       // return 0 if no digits
    while (n /= 10)
        digits++;
    if (digits > ct)
    {
    ct = digits - ct;
    while (ct--)
        num /= 10;
    return num;         // return left ct digits
    }
    else                // if ct >= number of digits
        return num;     // return the whole number
}

// This function returns a pointer to a new string
// consisting of the first n characters in the str string.
char * left(const char * str, int n)
{
    if(n < 0)
        n = 0;
    char * p = new char[n+1];
    int i;
    for (i = 0; i < n && str[i]; i++)
        p[i] = str[i];  // copy characters
    while (i <= n)
        p[i++] = '\ 0';  // set rest of string to '\ 0'
    return p;
}

Here's the output:

1
H
12
Ha
123
Haw
1234
Hawa
12345
Hawai
123456
Hawaii
1234567
Hawaii!
12345678
Hawaii!!
12345678
Hawaii!!

When to Use Function Overloading

You might find function overloading fascinating, but don't overuse the facility. You should reserve function overloading for functions that perform basically the same task but with different forms of data. Also, you might want to check whether you can accomplish the same end with default arguments. For example, you could replace the single, string-oriented left() function with two overloaded functions:

char * left(const char * str, unsigned n);   // two arguments
char * left(const char * str);               // one argument

But using the single function with a default argument is simpler. There's just one function to write, instead of two, and the program requires memory for just one function, instead of two. If you decide to modify the function, there's only one you have to edit. However, if you require different types of arguments, default arguments are of no avail, so then you should use function overloading.

What Is Name Decoration?

graphics/common.gif

How does C++ keep track of which overloaded function is which? It assigns these functions a secret identity. When you use the editor of your C++ development tool to write and compile programs, your C++ compiler performs a bit of magic on your behalf梜nown as name decoration or name mangling梩hrough which each function name is encrypted based on the formal parameter types specified in the function's prototype. Consider the following undecorated function prototype:

long MyFunctionFoo(int, float);

This format is fine for us humans; we know that the function accepts two arguments of type int and float, and returns a value of type long. For its own use, the compiler documents this interface by transforming the name into an internal representation of a more unsightly appearance, perhaps something like this:

?MyFunctionFoo@@YAXH@Z

The apparent gibberish decorating the original name (or mangling it, depending upon your attitude) encodes the number and types of parameters. A different function signature would have resulted in a different set of symbols being added, and different compilers would use different conventions for their efforts at decorating.

Function Templates

Contemporary C++ compilers implement one of the newer C++ additions, function templates. Function templates are a generic function description; that is, they define a function in terms of a generic type for which a specific type, such as int or double, can be substituted. By passing a type as a parameter to a template, you cause the compiler to generate a function for that particular type. Because templates let you program in terms of a generic type instead of a specific type, the process is sometimes termed generic programming. Because types are represented by parameters, the template feature is sometimes referred to as parameterized types. Let's see why such a feature is useful and how it works.

Earlier we defined a function that swapped two int values. Suppose you want to swap two double values instead. One approach is to duplicate the original code but replace each int with double. If you need to swap two char values, you can use the same technique again. Still, it's wasteful of your valuable time to have to make these petty changes, and there's always the possibility of making an error. If you make the changes by hand, you might overlook an int. If you do a global search-and-replace to substitute, say, double for int, you might do something such as converting

int x;
short interval;

to the following:

double x;            // intended change of type
short doubleerval;   // unintended change of variable namee

C++'s function template capability automates the process, saving you time and providing greater reliability.

Function templates enable you to define a function in terms of some arbitrary type. For example, you can set up a swapping template like this:

template <class Any>
void Swap(Any &a, Any &b)
{
    Any temp;
    temp = a;
    a = b;
    b = temp;
}

The first line specifies that you are setting up a template and that you're naming the arbitrary type Any. The keywords template and class are obligatory, except that you can use the keyword typename instead of class. Also, you must use the angle brackets. The type name (Any in the example) is your choice, as long as you follow the usual C++ naming rules; many programmers use simple names like T. The rest of the code describes the algorithm for swapping two values of type Any. The template does not create any functions. Instead, it provides the compiler with directions about how to define a function. If you want a function to swap ints, then the compiler creates a function following the template pattern, substituting int for Any. Similarly, if you need a function to swap doubles, the compiler follows the template, substituting the double type for Any.

The keyword typename is a recent addition to C++. You can use instead of the keyword class in this particular context. That is, you can write the template definition this way:

template <typename Any>
void Swap(Any &a, Any &b)
{
    Any temp;
    temp = a;
    a = b;
    b = temp;
}

The typename keyword makes it a bit more obvious that the parameter Any represents a type; however, large libraries of code already have been developed by using the older keyword class. The C++ Standard treats the two keywords identically when they are used in this context.

Tip

graphics/bulb.gif

Use templates if you need functions that apply the same algorithm to a variety of types. If you aren't concerned with backward compatibility and can put up with the effort of typing a longer word, use the keyword typename rather than class when you declare type parameters.

To let the compiler know that you need a particular form of swap function, just use a function called Swap() in your program. The compiler checks the argument types you use and then generates the corresponding function. Listing 8.9 shows how this works. The program layout follows the usual pattern for ordinary functions with a template function prototype near the top of the file and the template function definition following main().

Compatibility Note

graphics/hands.gif

Noncurrent versions of C++ compilers might not support templates. New versions accept the keyword typename as an alternative to class. Older versions of g++ require that both the template prototype and the template definition appear before the template is used, but this has been fixed in newer releases.

Listing 8.9 funtemp.cpp
// funtemp.cpp -- using a function template
#include <iostream>
using namespace std;
// function template prototype
template <class Any>  // or typename Any
void Swap(Any &a, Any &b);

int main()
{
    int i = 10;
    int j = 20;
    cout << "i, j = " << i << ", " << j << ".\n";
    cout << "Using compiler-generated int swapper:\n";
    Swap(i,j);  // generates void Swap(int &, int &)
    cout << "Now i, j = " << i << ", " << j << ".\n";

    double x = 24.5;
    double y = 81.7;
    cout << "x, y = " << x << ", " << y << ".\n";
    cout << "Using compiler-generated double swapper:\n";
    Swap(x,y);  // generates void Swap(double &, double &)
    cout << "Now x, y = " << x << ", " << y << ".\n";

    return 0;
}

// function template definition
template <class Any>  // or typename Any
void Swap(Any &a, Any &b)
{
    Any temp;   // temp a variable of type Any
    temp = a;
    a = b;
    b = temp;
}

The first Swap() function has two int arguments, so the compiler generates an int version of the function. That is, it replaces each use of Any with int, producing a definition that looks like this:

void Swap(int &a, int &b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

You don't see this code, but the compiler generates, then uses it in the program. The second Swap() function has two double arguments, so the compiler generates a double version. That is, it replaces Any with double, generating this code:

void Swap(double &a, double &b)
{
    double temp;
    temp = a;
    a = b;
    b = temp;
}

Here's the program output; you can see the process has worked:

i, j = 10, 20.
Using compiler-generated int swapper:
Now i, j = 20, 10.
x, y = 24.5, 81.7.
Using compiler-generated double swapper:
Now x, y = 81.7, 24.5.

Note that function templates don't make your executable programs any shorter. In Listing 8.9, you still wind up with two separate function definitions, just as if you had defined each function manually. And the final code doesn't contain any templates; it just contains the actual functions generated for your program. The benefits of templates are that they make generating multiple function definitions simpler and more reliable.

Overloaded Templates

You use templates when you need functions that apply the same algorithm to a variety of types, as in Listing 8.8. It might be, however, that not all types would use the same algorithm. To meet this possibility, you can overload template definitions, just as you overload regular function definitions. As with ordinary overloading, overloaded templates need distinct function signatures. For example, Listing 8.10 adds a new swapping template, one for swapping elements of two arrays. The original template has the signature (Any &, Any &), whereas the new template has the signature (Any [], Any [], int). Note that the final argument in this case happens to be a specific type (int) rather than a generic type. Not all template arguments have to be template parameter types.

When, in twotemps.cpp, the compiler encounters the first use of Swap(), it notices that it has two int arguments and matches it to the original template. The second use, however, has two int arrays and an int value as arguments, and this matches the new template.

Listing 8.10 twotemps.cpp
// twotemps.cpp -- using overloaded template functions
#include <iostream>
using namespace std;
template <class Any>     // original template
void Swap(Any &a, Any &b);

template <class Any>     // new template
void Swap(Any *a, Any *b, int n);
void Show(int a[]);
const int Lim = 8;
int main()
{
    int i = 10, j = 20;
    cout << "i, j = " << i << ", " << j << ".\n";
    cout << "Using compiler-generated int swapper:\n";
    Swap(i,j);              // matches original template
    cout << "Now i, j = " << i << ", " << j << ".\n";

    int d1[Lim] = {0,7,0,4,1,7,7,6};
    int d2[Lim] = {0,6,2,0,1,9,6,9};
    cout << "Original arrays:\n";
    Show(d1);
    Show(d2);
    Swap(d1,d2,Lim);        // matches new template
    cout << "Swapped arrays:\n";
    Show(d1);
    Show(d2);

    return 0;
}

template <class Any>
void Swap(Any &a, Any &b)
{
    Any temp;
    temp = a;
    a = b;
    b = temp;
}

template <class Any>
void Swap(Any a[], Any b[], int n)
{
    Any temp;
    for (int i = 0; i < n; i++)
    {
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

void Show(int a[])
{
    cout << a[0] << a[1] << "/";
    cout << a[2] << a[3] << "/";
    for (int i = 4; i < Lim; i++)
        cout << a[i];
    cout << "\n";
}

Compatibility Note

graphics/hands.gif

Noncurrent versions of C++ compilers might not support templates. New versions might accept the keyword typename instead of class. Older versions of C++ are more picky about type matching and require the following code to make the const int Lim match the template requirement for an ordinary int:

Swap(xd1,d2, int (Lim));        // typecast Lim to non-const int

Older versions of g++ requires that the template definitions be placed ahead of main().

Here is the program's output:

i, j = 10, 20.
Using compiler-generated int swapper:
Now i, j = 20, 10.
Original arrays:
07/04/1776
07/20/1969
Swapped arrays:
07/20/1969
07/04/1776

Explicit Specializations

Suppose you define a structure like the following:

struct job
{
      char name[40];
      double salary;
      int floor;
};

Also, suppose you want to be able to swap the contents of two such structures. The original template uses the following code to effect a swap:

temp = a;
a = b;
b = temp;

Because C++ allows you to assign one structure to another, this works fine, even if type Any is a job structure. But suppose you only want to swap the salary and floor members, keeping the name members unchanged. This requires different code, but the arguments to Swap() would be the same as for the first case (references to two job structures), so you can't use template overloading to supply the alternative code.

However, you can supply a specialized function definition, called an explicit specialization, with the required code. If the compiler finds a specialized definition that exactly matches a function call, it uses that definition without looking for templates.

The specialization mechanism has changed with the evolution of the language. We look at the current form as mandated by the C++ Standard, then look at two older forms supported by older compilers.

Third Generation Specialization (ISO/ANSI C++ Standard)

After C++ experimented with the approaches described later, the C++ Standard settled upon this approach:

  1. For a given function name, you can have a non-template function, a template function, and an explicit specialization template function.

  2. The prototype and definition for an explicit specialization should be preceded by template <> and should mention the specialized type by name.

  3. A specialization overrides the regular template, and a non-template function overrides both.

Here's how prototypes for swapping type job structures would look for these three forms:

// non-template function prototype
void Swap(job &, job &);

// template prototype
template <class Any>
void Swap(Any &, Any &);

// explicit specialization for the job type
template <> void Swap<job>(job &, job &);

As mentioned previously, if more than one of these prototypes is present, the compiler chooses the non-template version over explicit specializations and template versions, and it chooses an explicit specialization over a version generated from a template.

...
template <class Any>          // template
void Swap(Any &, Any &);

// explicit specialization for the job type
template <> void Swap<job>(job &, job &);
int main()
{
    double u, v;
    ...
    Swap(u,v);  // use template
    job a, b;
    ...
    Swap(a,b);  // use void Swap<job>(job &, job &)
}

The <job> in Swap<job> is optional, for the function argument types indicate that this is a specialization for job. Thus, the prototype also can be written this way:

template <> void Swap(job &, job &);   // simpler form

The template function heading also can be simplified by omitting the <int> part.

In case you have to work with an older compiler, we'll come back to pre-Standard usage soon, but first, let's see how explicit specializations are supposed to work.

An Example

Listing 8.11 illustrates how explicit specialization works. It's set up to follow the C++ Standard.

Listing 8.11 twoswap.cpp
// twoswap.cpp -- specialization overrides a template
#include <iostream>
using namespace std;
template <class Any>
void Swap(Any &a, Any &b);

struct job
{
    char name[40];
    double salary;
    int floor;
};

// explicit specialization
template <> void Swap<job>(job &j1, job &j2);
void Show(job &j);

int main()
{
    cout.precision(2);
    cout.setf(ios::fixed, ios::floatfield);
    int i = 10, j = 20;
    cout << "i, j = " << i << ", " << j << ".\n";
    cout << "Using compiler-generated int swapper:\n";
    Swap(i,j);    // generates void Swap(int &, int &)
    cout << "Now i, j = " << i << ", " << j << ".\n";


    job sue = {"Susan Yaffee", 63000.60, 7};
    job sidney = {"Sidney Taffee", 66060.72, 9};
    cout << "Before job swapping:\n";
    Show(sue);
    Show(sidney);
    Swap(sue, sidney); // uses void Swap(job &, job &)
    cout << "After job swapping:\n";
    Show(sue);
    Show(sidney);

    return 0;
}
template <class Any>
void Swap(Any &a, Any &b)    // general version
{
    Any temp;
    temp = a;
    a = b;
    b = temp;
}

// swaps just the salary and floor fields of a job structure

template <> void Swap<job>(job &j1, job &j2)  // specialization
{
    double t1;
    int t2;
    t1 = j1.salary;
    j1.salary = j2.salary;
    j2.salary = t1;
    t2 = j1.floor;
    j1.floor = j2.floor;
    j2.floor = t2;
}

void Show(job &j)
{
    cout << j.name << ": $" << j.salary
         << " on floor " << j.floor << "\n";
}

Compatibility Note

graphics/hands.gif

This version of the program requires ISO/ANSI C++ support.

Here's the program output:

i, j = 10, 20.
Using compiler-generated int swapper:
Now i, j = 20, 10.
Before job swapping:
Susan Yaffee: $63000.60 on floor 7
Sidney Taffee: $66060.72 on floor 9
After job swapping:
Susan Yaffee: $66060.72 on floor 9
Sidney Taffee: $63000.60 on floor 7
Earlier Approaches to Specialization

If Listing 8.11 didn't work with your compiler, you may have to revert to earlier usage. The simplest is to provide an ordinary function defined for the particular type you wish to process. That is, in Listing 8.11, you would replace

template <> void Swap<job>(job &j1, job &j2);

with

void Swap(int & n, int & m);   // regular prototype

and replace

template <> void Swap<job>(job &j1, job &j2)  // specialization
{
...
}

with

void Swap(job &j1, job &j2)   // regular function
{
...// code unchanged
}

When the compiler reaches the Swap(sue, sidney) function call , it has the choice of generating a function definition using the template or of using the nontemplate Swap(job &, job &) function. The original template facility (as well as the current standard) has the compiler use the non-template version.

If this approach doesn't work for you, you may be using a compiler that installed a non-official predraft version of templates in which templates were chosen ahead of ordinary functions. With this rule in effect, the compiler would use the template Swap() instead of the job version. So to get the desired effect, you'd have to use an explicit specialization that didn't quite have the modern form. That is, instead of using

template <> void Swap<job>(job &j1, job &j2);  // ISO/ANSI C++

you would use the following form:

void Swap<job>(job &, job &);   // earlier form of specialization

Notice that it is missing the template <> preface. You'd make the same adjustment in the function heading. That is,

template <> void Swap<job>(job &j1, job &j2)  // specialization
{
...
}

becomes

void Swap<job>(job &j1, job &j2)  // old form  of specialization
{
...// code unchanged
}

If you're using a contemporary C++ compiler (and we hope you are), you won't have to deal with these adjustments.

Instantiations and Specializations

To extend our understanding of templates, we need to investigate the terms instantiation and specicalization. Keep in mind that including a function template in your code does not in itself generate a function definition. It's merely a plan for generating a function definition. When the compiler uses the template to generate a function definition for a particular type, the result is termed an instantiation of the template. For example, in Listing 8.11, the function call Swap(i,j) causes the compiler to generate an instantiation of the Swap() using int as the type. The template is not a function definition, but the specific instantiation using int is a function definition. This type of instantiation is termed an implicit instantiation, because the compiler deduces the necessity for making the definition by noting that the program uses a Swap() function with int parameters.

Originally, implicit instantiation was the only way the compiler generated function definitions from templates, but now C++ allows for explicit instantiation. That means you can instruct the compiler to create a particular instantiation, for example, Swap<int>(), directly. The syntax is to declare the particular variety you want, using the <> notation to indicate the type and prefixing the declaration with the keyword template:

template void Swap<int>(int, int);  // explicit instantiation

A compiler that implements this feature will, upon seeing this declaration, use the Swap() template to generate an instantiation using the int type. That is, this declaration means "Use the Swap() template to generate a function definition for the int type."

Contrast the explicit instantiation with the explicit specialization, which uses one or the other of these equivalent declarations:

template <> void Swap<int>(int &, int &);  // explicit specialization
template <> void Swap(int &, int &);       // explicit specialization

The difference is that these declarations mean "Don't use the Swap() template to generate a function definition. Instead, use a separate, specialized function definition explicitly defined for the int type." These prototypes have to be coupled with their own function definitions.

Caution

graphics/tnt.gif

It is an error to try to use both an explicit instantiation and an explicit specialization for the same type(s) in the same programming unit.

Implicit instantiations, explicit instantiations, and explicit specializations collectively are termed specializations. What they all have in common is that they represent a function definition using specific types rather than one that is a generic description.

The addition of the explicit instantiation led to the new syntax of using template and template <> prefixes in declarations to distinguish between the explicit instantiation and the explicit specialization. Often, the cost of doing more is more syntax rules. The following fragment summarizes these concepts:

...
template <class Any>
void Swap (Any &, Any &);  // template prototype

template <> void Swap<int>(job &, job &);  // explicit specialization for job
int main(void)
{
  template void Swap<char>(char &, char &);  // explicit instantiation for char
  short a, b;
  ...
  Swap(a,b);   // implicit template instantiation for short
  job n, m;
  ...
  Swap(n, m); // use explicit specialization for job
  char g, h;
  ...
   Swap(g, h);  // use explicit template instantiation for char
   ...
}

When the compiler reaches the explicit instantiation for char, it uses the template definition to generate a char version of Swap(). When the compiler reaches the function call Swap(a,b), it generates a short version of Swap(). When the compiler reaches Swap(n,m), it uses the separate definition (the explicit specialization) provided for the job type. When the compiler reaches Swap(g,h), it uses the template specialization it already generated when it processed the explicit instantiation.

Which Function?

What with function overloading, function templates, and function template overloading, C++ needs, and has, a well-defined strategy for deciding which function definition to use for a function call, particularly when there are multiple arguments. The process is called overload resolution. Detailing the complete strategy would take a small chapter, so let's take an overview of how the process works:

Consider a case with just one function argument, for example, the following call:

may('B');    // actual argument is type char

First, the compiler rounds up the suspects, which are functions and function templates having the name may(). Then, it finds those that can be called with one argument. For example, the following will pass muster because they have the same name:

void may(int);                            // #1
float may(float, float = 3);              // #2
void may(char);                           // #3
char * may(const char *);                 // #4
char may(const char &);                   // #5
template<class T> void may(const T &);    // #6
template<class T. void may(T *);          // #7

Note that just the signatures and not the return types are considered. Two of these candidates(#4 and #7), however, are not viable because an integral type cannot be converted implicitly (i.e., without an explicit type cast) to a pointer type. The remaining template will be used to generate a specialization with T taken as type char. That leaves five viable functions, each of which could be used if it were the only function declared.

Next, the compiler has to determine which of the viable functions is best. It looks at the conversion required to make the function call argument match the viable candidate's argument. In general, the ranking from best to worst is this:

  1. Exact match, with regular functions outranking templates.

  2. Conversion by promotion (the automatic conversions of char and short to int and of float to double, for example)

  3. Conversion by standard conversion (converting int to char or long to double, for example)

  4. User-defined conversions, such as those defined in class declarations

For example, function #1 is better than function #2 because char-to-int is a promotion (Chapter 3, "Dealing with Data"), whereas char-to-float is a standard conversion (Chapter 3). Functions #3, #5, and #6 are better than either #1 or #2, because they are exact matches. Both #3 and #5 are better than #6 because #6 is a template. This analysis raises a couple of questions. What is an exact match, and what happens if you get two of them?

Exact Matches and Best Matches

C++ allows some "trivial conversions" when making an exact match. Table 8.1 lists them, with Type standing for some arbitrary type. For example, an int actual argument is an exact match to an int & formal parameter. Note that Type can be something like char &, so these rules include converting char & to const char &. The Type (argument-list) entry means that a function name as an actual argument matches a function pointer as a formal parameter as long as both have the same return type and argument list. (Remember function pointers from Chapter 7, "Functions桟++'s Programming Modules," and how you can pass the name of a function as an argument to a function expecting a pointer to a function.) We discuss the volatile keyword later in this chapter.

Table 8.1. Trivial Conversions Allowed for an Exact Match
From an actual argument To a formal argument
Type Type &
Type & Type
Type [] * Type
Type (argument-list) Type (*)(argument-list)
Type const Type
Type volatile Type
Type * const Type *
Type * volatile Type *

Anyway, suppose you have the following function code:

struct blot {int a; char b[10];};
blot ink = {25, "spots"};
...
recycle(ink);

Then, all the following prototypes would be exact matches:

void recycle(blot);          // #1  blot-to-blot
void recycle(const blot);    // #2  blot-to-(const blot)
void recycle(blot &);        // #3  blot-to-(blot &)
void recycle(const blot &);  // #4  blot-to-(const blot &)

As you might expect, the result of having several matching prototypes is that the compiler cannot complete the overload resolution process. There is no best viable function, and the compiler generates an error message, probably using words like "ambiguous."

However, sometimes there can be overload resolution even if two functions are an exact match. First, pointers and references to non-const data preferentially are matched to non-const pointer and reference parameters. That is, if only functions #3 and #4 were available in the recycle() example, #3 would be chosen because ink wasn't declared as const. However, this discrimination between const and non-const applies just to data referred to by pointers and references. That is, if only #1 and #2 were available, you would get an ambiguity error.

Another case where one exact match is better than another is when one function is a non- template function and the other isn't. In that case, the non-template is considered better than a template, including explicit specializations.

If you wind up with two exact matches that both happen to be template functions, the template function that is the more specialized, if either, is the better function. That means, for example, that an explicit specialization is chosen over one generated implicitly from the template pattern:

struct blot {int a; char b[10];};
template <class Type> void recycle (Type t); // template
template <> void recycle<blot> (blot & t);   // specialization for blot
...
blot ink = {25, "spots"};
...
recycle(ink);  // use specialization

The term "most specialized" doesn't necessarily imply an explicit specialization; more generally, it indicates that fewer conversions take place when the compiler deduces what type to use. For example, consider the following two templates:

template <class Type> void recycle (Type t);    #1
template <class Type> void recycle (Type * t);..#2

Suppose the program that contains these templates also contains the following code:

struct blot {int a; char b[10];};
blot ink = {25, "spots"};
...
recycle(&ink);  // address of a structure

The recycle(&ink) call matches template #1 with Type interpreted as blot *. The recycle(&ink) function call also matches template #2, this time with Type being ink. This combination sends two implicit instantiations, recycle<blot *>(blot *) and recycle<blot>(blot *), to the viable function pool.

Of these two template functions, recycle<blot *>(blot *) is considered the more specialized because it underwent fewer conversions in being generated. That is, template #2 already explicitly said the function argument was pointer-to-Type so Type could be directly identified with blot. Template #1, however, had Type as the function argument, so Type had to be interpreted as pointer-to-blot. That is, in template #2, Type already was specialized as a pointer, hence the term "more specialized."

The rules for finding the most specialized template are called the partial ordering rules for function templates. Like explicit instantiations, they are new additions to the language.

A Partial Ordering Rules Example

Let's examine a complete program that uses the partial ordering rules for identifying which template definition to use. Listing 8.12 has two template definitions for displaying the contents of an array. The first definition (template A) assumes that the array passed as an argument contains the data to be displayed. The second definition (template B) assumes that the array argument contains pointers to the data to be displayed.

Listing 8.12 temptempover.cpp
// tempover.cpp --- template overloading
#include <iostream>
using namespace std;
template <typename T>            // template A
void ShowArray(T arr[], int n);

template <typename T>            // template B
void ShowArray(T * arr[], int n);

struct debts
{
    char name[50];
    double amount;
};

int main(void)
{
    int things[6] = { 13, 31, 103, 301, 310, 130} ;
    struct debts mr_E[3] =
    {
        {"Ima Wolfe", 2400.0} ,
        {"Ura Foxe ", 1300.0} ,
        {"Iby Stout", 1800.0}
    };
    double * pd[3];

    for (int i = 0; i < 3; i++)
        pd[i] = &mr_E[i].amount;

    cout << "Listing Mr. E's counts of things:\n";
    ShowArray(things, 6);  // uses template A
    cout << "Listing Mr. E's debts:\n";
    ShowArray(pd, 3);      // uses template B (more specialized)
    return 0;
}

template <typename T>
void ShowArray(T arr[], int n)
{
    cout << "template A\ n";
    for (int i = 0; i < n; i++)
        cout << arr[i] << ' ';
    cout << endl;
}

template <typename T>
void ShowArray(T * arr[], int n)
{
    cout << "template B\n";
    for (int i = 0; i < n; i++)
        cout << *arr[i] << ' ';
    cout << endl;
}

First, consider this function call:

ShowArray(things, 6);

The identifier things is the name of an array of int, so it matches the template

template <typename T>             // template A
void ShowArray(T arr[], int n);

with T taken to be type int.

Next, consider this function call:

ShowArray(pd, 3);

Here, pd is the name of an array of double *. This could be matched by template A:

template <typename T>            // template A
void ShowArray(T arr[], int n);

Here, T would be taken to be type double *. In this case, the template function would display the contents of the pd array, namely, three addresses. The function call also could be matched by template B:

template <typename T>            // template B
void ShowArray(T * arr[], int n);

In this case, T would be type double, and the function displays the dereferenced elements *arr[i], that is, the double values pointed to by the array contents. Of the two templates, template B is the more specialized because it makes the specific assumption that the array contents are pointers, so it is the template that gets used.

Here's the output:

Listing Mr. E's counts of things:
template A
13 31 103 301 310 130
Listing Mr. E's debts:
template B
2400 1300 1800

If you remove template B from the program, the compiler will then use template A for listing the contents of pd, so it will list the addresses instead of the values.

In short, the overload resolution process looks for a function that's the best match. If there's just one, that function is chosen. If there are more than one otherwise tied, but only one is a non-template function, that's chosen. If there are more than one candidates otherwise tied, and all are template functions, but one template is more specialized than the rest, that is chosen. If there are two or more equally good non-template functions, or if there are two or more equally good template functions, none of which is more specialized than the rest, the function call is ambiguous and an error. If there are no matching calls, of course, that also is an error.

Functions with Multiple Arguments

Where matters really get involved is when a function call with multiple arguments is matched to prototypes with multiple arguments. The compiler must look at matches for all the arguments. If it can find a function that is better than all the other viable functions, it's the chosen one. For one function to be better than another function, it has to provide at least as good a match for all arguments and a better match for at least one argument.

This book does not intend to challenge the matching process with complex examples. The rules are there so that there is a well-defined result for any possible set of function prototypes and templates.

Summary

C++ has expanded C function capabilities. By using the inline keyword with a function definition and by placing that definition ahead of the first call to that function, you suggest to the C++ compiler that it make the function inline. That is, instead of having the program jump to a separate section of code to execute the function, the compiler replaces the function call with the corresponding code inline. An inline facility should be used only when the function is short.

A reference variable is a kind of disguised pointer that lets you create an alias (a second name) for a variable. Reference variables primarily are used as arguments to functions processing structures and class objects.

C++ prototypes enable you to define default values for arguments. If a function call omits the corresponding argument, the program uses the default value. If the function includes an argument value, the program uses that value instead of the default. Default arguments can be provided only from right to left in the argument list. Thus, if you provide a default value for a particular argument, you also must provide default values for all arguments to the right of that argument.

A function's signature is its argument list. You can define two functions having the same name provided that they have different signatures. This is called function polymorphism, or function overloading. Typically, you overload functions to provide essentially the same service to different data types.

Function templates automate the process of overloading functions. You define a function using a generic type and a particular algorithm, and the compiler generates appropriate function definitions for the particular argument types you use in a program.

Review Questions

1:

What kinds of functions are good candidates for inline status?

2:

Suppose the song() function has this prototype:

void song(char * name, int times);
  1. How would you modify the prototype so that the default value for times is 1?

  2. What changes would you make in the function definition?

  3. Can you provide a default value of "O, My Papa" for name?

3:

Write overloaded versions of iquote(), a function that displays its argument enclosed in double quotation marks. Write three versions: one for an int argument, one for a double argument, and one for a string argument.

4:

Here is a structure template:

struct box
{
    char maker[40];
    float height;
    float width;
    float length;
    float volume;
};
  1. Write a function that has a reference to a box structure as its formal argument and displays the value of each member.

  2. Write a function that has a reference to a box structure as its formal argument and sets the volume member to the product of the other three dimensions.

5:

Here are some desired effects. Indicate whether each can be accomplished with default arguments, function overloading, both, or neither. Provide appropriate prototypes.

  1. mass(density, volume) returns the mass of an object having a density of density and a volume of volume, whereas mass(density) returns the mass having a density of density and a volume of 1.0 cubic meters. All quantities are type double.

  2. repeat(10, "I'm OK") displays the indicated string ten times, whereas repeat("But you're kind of stupid") displays the indicated string five times.

  3. average(3,6) returns the int average of two int arguments, whereas average(3.0, 6.0) returns the double average of two double values.

  4. mangle("I'm glad to meet you") returns the character I or a pointer to the string "I'm mad to gleet you" depending on whether you assign the return value to a char variable or to a char* variable.

6:

Write a function template that returns the larger of its two arguments.

7:

Given the template of Review Question 6 and the box structure of Review Question 4, provide a template specialization that takes two box arguments and returns the one with the larger volume.

Programming Exercises

1:

Write a function that normally takes one argument, the address of a string, and prints that string once. However, if a second, type int argument is provided and is nonzero, the function prints the string a number of times equal to the number of times that function has been called to at that point. (Note that the number of times the string is printed is not equal to the value of the second argument; it's equal to the number of times the function has been called.) Yes, this is a silly function, but it makes you use some of the techniques discussed in this chapter. Use the function in a simple program that demonstrates how the function works.

2:

The CandyBar structure contains three members. The first member holds the brand name of a candy bar. The second member holds the weight (which may have a fractional part) of the candy bar, and the third member holds the number of calories (an integer value) in the candy bar. Write a program that uses a function that takes as arguments a reference to a CandyBar, a pointer-to-char, a double, and an int and uses the last three values to set the corresponding members of the structure. The last three arguments should have default values of "Millennium Munch," 2.85, and 350. Also, the program should use a function taking a reference to a CandyBar as an argument and display the contents of the structure. Use const where appropriate.

3:

Following is a program skeleton. Complete it by providing the described functions and prototypes. Note that there should be two show() functions, each using default arguments. Use const arguments when appropriate. Note that set() should use new to allocate sufficient space to hold the designated string. The techniques used here are similar to those used in designing and implementing classes. (You might have to alter the header file names and delete the using-directive, depending upon your compiler.)

#include <iostream>
using namespace std;
#include <cstring>      // for strlen(), strcpy()
struct stringy {
    char * str;        // points to a string
    int ct;            // length of string (not counting '\0')
    };
// prototypes for set(), show(), and show() go here
int main()
{
    stringy beany;
    char testing[] = "Reality isn't what it used to be.";
   set(beany, testing);    // first argument is a reference,
                // allocates space to hold copy of testing,
                // sets str member of beany to point to the
                // new block, copies testing to new block,
                // and sets ct member of beany
    show(beany);      // prints member string once
    show(beany, 2);   // prints member string twice
    testing[0] = 'D';
    testing[1] = 'u';
    show(testing);    // prints testing string once
    show(testing, 3); // prints testing string thrice
    show("Done!");
    return 0;
}
4:

Write a template function max5() that takes as its argument an array of five items of type T and returns the largest item in the array. (Because the size is fixed, it can be hard-coded into the loop instead of passed as an argument.) Test it in a program that uses the function with an array of 5 int value and an array of 5 double values.

5:

Write a template function maxn() that takes as its arguments an array of items of type T and an integer representing the number of elements in the array and which returns the largest item in the array. Test it in a program that uses the function template with an array of 6 int value and an array of 4 double values. The program also should include a specialization that takes an array of pointers-to-char as an argument and the number of pointers as a second argument and which returns the address of the longest string. If there are more than one string having the longest length, the function returns the address of the first one tied for longest. Test the specialization with an array of 5 string pointers.

6:

Modify Listing 8.12 so that the template functions return the sum of the array contents instead of displaying the contents. The program now should report the total number of things and the sum of all the debts.

[1]  It's a bit like having to leave off reading some text to find out what a footnote says and then, upon finishing the footnote, returning to where you were reading in the text.

CONTENTS