void f(); // f is overloaded void f(int x); f(); // calls f() f(10); // calls f(int) void g(int x = 0); // g has a default // parameter value g(); // calls g(0) g(10); // calls g(10)
The answer depends on two other questions. First, is there a value you can use for a default? Second, how many algorithms do you want to use? In general, if you can choose a reasonable default value and you want to employ only a single algorithm, you'll use default parameters (see also Item 38). Otherwise you'll use function overloading.
Here's a function to compute the maximum of
up to five int
s. This function uses -- take a deep breath and
steel yourself -- std::numeric_limits<int>::min()
as a
default parameter value. I'll have more to say about that in a moment,
but first, here's the code:
int max(int a, int b = std::numeric_limits<int>::min(), int c = std::numeric_limits<int>::min(), int d = std::numeric_limits<int>::min(), int e = std::numeric_limits<int>::min()) { int temp = a > b ? a : b; temp = temp > c ? temp : c; temp = temp > d ? temp : d; return temp > e ? temp : e; }
std::numeric_limits<int>::min()
is
just the fancy new-fangled way the standard C++ library says what C says
via the INT_MIN
macro in <limits.h>
: it's the minimum possible value for an
int
in whatever compiler happens to be processing your C++
source code. True, it's a deviation from the terseness for which C is
renowned, but there's a method behind all those colons and other
syntactic strychnine. Suppose you'd like to write a function template taking any built-in numeric type as its parameter, and you'd like the functions generated from the template to print the minimum value representable by their instantiation type. Your template would look something like this:
template<class T> void printMinimumValue() { cout << the minimum value representable by T; }
<limits.h>
and <float.h>
. You don't know
what T
is, so you don't know whether to print out INT_MIN
or DBL_MIN
or what.
To sidestep these difficulties, the standard C++
library (see Item 49) defines in the header <limits>
a
class template, numeric_limits
, which itself defines several
static member functions. Each function returns information about the type
instantiating the template. That is, the functions in
numeric_limits<int>
return information about type
int
, the functions in
numeric_limits<double>
return information about type
double
, etc. Among the functions in
numeric_limits
is min
. min
returns
the minimum representable value for the instantiating type, so
numeric_limits<int>::min()
returns the minimum
representable integer value.
Given numeric_limits
(which, like nearly everything in the standard library, is in namespace std
--
see Item 28; numeric_limits
itself is in the header <limits>
), writing
printMinimumValue
is as easy as can be:
template<class T> void printMinimumValue() { cout << std::numeric_limits<T>::min(); }
numeric_limits
-based approach
to specifying type-dependent constants may look expensive, but in fact
it's not. That's because the long-windedness of the source code
fails to be reflected in the resultant object code. In fact, calls to
functions in numeric_limits
generate no instructions at all.
To see how that can be, consider the following, which is an obvious way to
implement numeric_limits<int>::min
:
#include <limits.h> namespace std { inline int numeric_limits<int>::min() throw () { return INT_MIN; } }
INT_MIN
, which is
itself a simple #define
for some implementation-defined
constant. So even though the max
function at the beginning of this Item looks like it's making a
function call for each default parameter value, it's just using a
clever way of referring to a type-dependent constant, in this case the
value of INT_MIN
. Such efficient cleverness abounds in
C++'s standard library. You really should read Item 49.
Getting back to the max
function, the crucial observation is
that max
uses the same (rather inefficient) algorithm to
compute its result, regardless of the number of arguments provided by the
caller. Nowhere in the function do you attempt to figure out which
parameters are "real" and which are defaults. Instead, you have
chosen a default value that cannot possibly affect the validity of the
computation for the algorithm you're using. That's what makes the
use of default parameter values a viable solution.
For many functions, there is no reasonable default value. For example,
suppose you want to write a function to compute the average of up to five
int
s. You can't use default parameter values here,
because the result of the function is dependent on the number of parameters
passed in: if 3 values are passed in, you'll divide their sum by 3; if
5 values are passed in, you'll divide their sum by 5. Furthermore,
there is no "magic number" you can use
as a default to indicate that a parameter wasn't actually provided by
the client, because all possible int
s are valid values for the
parameters. In this case, you have no choice: you must use
overloaded functions:
int avg(int a); int avg(int a, int b); int avg(int a, int b, int c); int avg(int a, int b, int c, int d); int avg(int a, int b, int c, int d, int e);
// A class for representing natural numbers class Natural { public: Natural(int initValue); Natural(const Natural& rhs); private: unsigned int value; void init(int initValue); void error(const string& msg); }; inline void Natural::init(int initValue) { value = initValue; } Natural::Natural(int initValue) { if (initValue > 0) init(initValue); else error("Illegal initial value"); } inline Natural::Natural(const Natural& x) { init(x.value); }
int
has to perform error checking,
but the copy constructor doesn't, so two different functions are
needed. That means overloading. However, note that both functions must
assign an initial value for the new object. This could lead to code duplication in the two constructors, so you
maneuver around that problem by writing a private member function
init
that contains the code common to the two constructors.
This tactic -- using overloaded functions that call a common underlying
function for some of their work -- is worth remembering, because it's
frequently useful (see e.g., Item 12).