Computer scientists often face conflicting trade-offs. For example, the classic time-space trade-off: making a program run faster at the expense of using more memory or vice versa. Another trade-off is functional flexibility versus simplicity or ease of use. Imagine a program with a function that creates a window. A flexible function might allow a programmer to specify values for the window's width and height, its x and y location on the screen, and its color. Requiring five bits of information makes the function flexible but more difficult to use, especially if we don't care about the window's location or color. Alternatively, a simple version that only requires the window's size may not give us enough control when creating a complex user interface.
void window(int width, int height){...}
void window(int width, int height, int x, int y, int color){...}
Overloaded functions: simplicity vs. flexibility.
Overloaded functions are one way to maintain flexibility when needed but enjoy simplicity when it isn't.
The first function is simple to use: programmers calling it only need to supply the window size (the width and height). The second function is more flexible , allowing programmers greater control over the window.
A simple, easily used function might make some assumptions for often-used values and then call the complex function - chaining the function calls together. The Java API has many examples of this approach and calls the simple version a convenience method.
C++ provides another, more memory-efficient (and, in my opinion, a more elegant) choice: default arguments. A default argument is a value provided for an argument if the function call doesn't furnish one. Programmers specify the default values in the function declaration or prototype, as the next figure illustrates.
(a)
void window(int width, int height, int x = 0, int y = 0, int color = 0xFFFFFF) {...}
(b)
window(100, 200);
(c)
window(100, 200, 50);
(d)
window(100, 200, 50, 75);
(e)
window(100, 200, 50, 75, BLUE);
Default arguments: simplicity vs. flexibility. The window function provides three default arguments. The function calls must supply values for the first two arguments; they can either accept the defaults for the remaining arguments or supply values for them.
Supplies values for width and height, accepts the defaults for x, y, and color.
Supplies values for width, height, and x, accepts the defaults for y and color.
Supplies values for width, height, x and y, accepts the default for color.
Supplies values for all function arguments.
Default Argument Rules
We must follow four rules when using default arguments. Some may seem confusing, but the following elaborations and examples demonstrate that they are relatively straightforward. If we recall that C++ matches function call arguments to a function's parameters (Figure 2) in English's reading order - left to right - some are even intuitive.
Parameter lists may not have arguments without a default value following (i.e., to the right of) an argument with a default.
Once a function call accepts a default argument, it must accept all following defaults (i.e., function calls can't skip arguments).
If the function definition and declaration are separate, then the default values appear in the declaration (i.e., in the prototype, often written in a header file).
If a program overloads a function with default arguments, all possible function call argument lists must always match exactly one function.
Prototype (declaration) library.h
Definition library.cpp
void function(int a, int b = 1, int c = 2);
void function(int a, int b, int c)
{
cout << a << " " << b << " " << c << endl;
}
Rule 3: separate prototypes and definitions.
Suppose programmers implement a function as a separate prototype (in a header file) and a definition (in a source code file). In that case, they specify the default values in the function prototype.
void function(); // version 1
void function(int a, int b = 1, int c = 2); // version 2
(a)
function(); // version 1
function(10); // version 2
function(10, 20); // version 2
function(10, 20, 30); // version 2
(b)
void bad_function(int a); // version 1
void bad_function(int a, int b = 1, int c = 2); // version 2
(c)
bad_function(10); // version 1 or 2?
(c)
Rule 4: function calls may not be ambiguous.
Some or all overloaded functions may have default arguments. However, no pattern of function arguments may match more than one overloaded function.
The program calls version 1 without arguments, while it can call version 2 with one, two, or three.
Programs can call the two functions in four distinct ways: version 1 with no arguments and version 2 with one, two, or three. All valid function calls uniquely match only one version.
The two function prototypes violate rule 4 (the example highlights the error).
Demonstrates the problem with (c). The call matches both function versions when the version 2 defaults are accepted. The compiler cannot determine which function to call.
Default Argument Examples
void function(int a, int b = 1, int c = 2)
{
cout << a << " " << b << " " << c << endl;
}
function();
Function Definition
(a)
function(50);
function(60, 70);
(b)
(c)
function(60, 70, 80);
function(60, 70, 80, 90);
(d)
(e)
Calling a function with default arguments.
Examine the function definition and calls. Determine the output of each function call.