Week 2

Overloaded Functions

Function overloading allows you to use the same name for different functions in the same program, provided each takes a different type or number of arguments. The pattern of arguments received by a function—which refers only to the number and types of the arguments and not the parameter names—is called its signature.

Here's an example. Both the <cmath> header and <cstdlib> declare abs() functions for returning the absolute value of a number. Here are the three functions in <cstdlib>:

int abs(int n);
long abs(long n);
long long abs(long long n);

Here are the four versions of the function in <cmath>:

double abs(double x);
float abs(float x);
long double abs(long double x);
double abs(T x); // a template called when no other matches

There are even versions for complex numbers in the header <complex>. The only difference between these functions is the types of the parameters. The compiler chooses which version to call by looking at the types of the arguments supplied.

  • Called with an int, the compiler calls the version which takes an int.
  • Called with a double, the compiler will choose the version from <cmath>, which takes a double.

If you call abs() with an integer, and only include <cmath>, but forget <cstdlib>, then a special generic version of abs() that takes a type T parameter will be called. The difference between the generic version, and the overloaded abs(int) version, is that the generic version always returns a double, not an int.

Overloading makes it easier for programmers to remember function names when the same operation is applied in slightly different contexts. C, which does not have overloading, requires different names for each different absolute value function: iabs, fabs, dabs, labs, llabs, and so on.

Week 2

Overload Resolution

When you overload a function:

  • The parameter number must differ, or
  • The parameter types must differ, or
  • The parameter type order must differ

You cannot merely change the return type of a function. That is an error.

To determine which function is called, your compiler follows a process called overload resolution. Resolving which version of the abs() function to call is easy, since it only takes one argument. Things are more complex when a function takes several arguments.

Here are the rules:

  1. Functions with the same name are gathered into a candidate set.
  2. The candidate set is narrowed to produce the viable set: those functions that have the correct number of parameters and whose parameters could accept the supplied arguments using standard conversions.
  3. If there are any exact matches in the viable set, use that version.
  4. If there are no exact matches, find the best match involving conversions. The rules for this can be quite complex. You can find all of the details in the C++ Primer, Section 6.6.

There are two possible errors that can occur at the end of the matching process:

  • There are no members left in the viable set. This produces an undeclared name compiler error.
  • The process can't pick a winner among several viable functions. This produces an ambiguity compiler error.

When this occurs, the function definition is not in error, but the function call.

Week 2

Default Arguments

In your function declaration, you may indicate that certain arguments are optional by providing the parameter with a value to be used when no argument is passed in the call. These are called default arguments.

To indicate that an argument is optional, include an initial value in the declaration of that parameter in the function prototype. For example, you might define a procedure with the following prototype:

void formatInColumns(int nColumns = 2);

The = 2 in the prototype declaration means that this argument may be omitted when calling the function. You can now call the function in two different ways:

formatInColumns();    // use 2 (default) for nColumns
formatInColumns(3);   // use 3 for nColumns

The getline() function which you have been using, actually has a third parameter, the terminating character, which is given the default value '\n' in its declaration.

Since most of the time you want to read an entire line, ending in a newline, that makes sense. However, if you supply a third argument, say ';', then getline will only read up to a ';' instead of the entire line. This way you can use getline to read a series of delimited fields inside a single line.

Week 2

Default Argument Rules

Here are the rules that determine the use of default arguments:

  1. The default value appears only in the function prototype. If you repeat the default argument in the implementation file you will get a compiler error.
  2. Parameters with defaults must appear at the end of the parameter list and cannot be followed by a parameter without a default. Here’s a bad example:
  3. void badOrder(int a = 3, int b);    // how would you call this?
  4. Default arguments are only used with value, not reference parameters. Here’s another (bad) example:
    void badType(int& a = ????);    // what to set it to?
    Since a reference must refer to an lvalue, there is no way to specify which lvalue should be used when the function is called.

Prefer Overloading

Overloading is usually preferable to default arguments. Suppose for example, you wish to define a procedure named setLocation() which takes an x and a y coordinate as arguments.

You may write the prototype, using default arguments, like this:

void setLocation(double x = 0, double y = 0);

Now, the default location defaults to the origin (0, 0). However, it is possible to call the function with only one argument, which is confusing to anyone reading the code. It is better to just define a pair of overloaded functions like this:

void setLocation(double x, double y);   // supply both
inline void setLocation() { setLocation(0, 0); }

The body of the second function, can just calls the first, passing 0, 0 as the arguments. Since the function is very, very short, it can be marked inline which means it does not need to be defined inside the implementation file.