Reading

Section 4.6 of Problem Solving with C++.

Most of this lecture involves function overloading.
This concept/feature may not seem too important right now, but
as we progress in our understanding of programming and in our
knowledge of C++, the importance of overloading will become
more apparent. When you look at templates in higher
level classes, it will be more and more apparent. We're
talking about it now, because it should help you understand
the role of types in C++ even better, and why I say that
understanding types is one of the most important things you
can get out of this class!

Implicit type conversion and function calls

Consider the following bit of review.
Suppose I have a function with prototype: void
rep(char,int);, and with the following definition:

void rep(char c, int k)
{
for(int i = 0; i < k; i++)
cout << c;
}

Code

Resulting Output

Explanation

char g = '%';
int x = 10;
rep(g,x);

%%%%%%%%%%

Clear from the function definition.

int x = 10, y = 42;
rep(y,x);

**********

rep expects a char as its first
argument, but the first argument in the function call,
y, is an int. Thus,
y gets converted to a char to
match the prototype, and the char
interpretation of 42 (as we see from the ASCII
table) is '*'.

int x = 10;
double z = 42.553;
rep(z,x);

**********

rep expects a char as its first
argument, but the first argument in the function call,
z, is a double. Thus,
z gets converted to a char to
match the prototype, and the char
interpretation of 42.553 is ASCII value 42, which is
'*'.

Overloading Names

Now, what if I add another prototype and definition for the
function rep, but with different types of arguments?
For example, what if I have the following program

What happens here? Well, Alternative
One causes 6 X's to be printed, and
Alternative Two causes

555555555555

... to be printed. Why the difference? Which function gets used?
Well, in Alternative One we
used an object of type char as the first argument, so
it used the version of rep with an object of type
char as a first argument.
In Alternative Two we
used an object of type int as the first argument, so
it used the version of rep with an object of type
int as a first argument. This is called
overloading of function names. The idea is, that the
name of a function isn't just rep,
the name of the function is really rep(int,int)
or rep(char,int). So:

Overloading of a function name occurs when two or more
definitions are given for functions with the same name, but
different number or types of arguments.

Note: the return type of a function is not
considered in function overloading. Thus, it is illegal to have
two functions whose prototype differs only in the return type.
For example: double f(int); and
char f(int); cannot coexist!

Examples of Overloading

Definition 1

Definition 2

Discussion

int max(int a, int b)
{
if (a >= b)
return a;
else
return b;
}

int max(int a, int b, int c)
{
return max(max(a,b),c);
}

No conflict here, because I can look at the number of
arguments and decide which function to call

No mystery here! If you see a call like
mystery('X',13) you'd know that
mystery(char,int) (i.e. the second
definition) would be called. You just figure out which
prototype matches.

Implicit Conversions and Overloading

When you make a function call with arguments whose types and
number do not match any prototype, the compiler will try to use
implicit type conversion to match a prototype. This is pretty
straightforward (remember the example at the top of the page?)
when function name overloading is not used. With oveloading,
however, things can get complicated. For example:

Definition 1

Definition 2

Function Call

Discussion

void rep(char c, int k)
{
for(int i = 0; i < k; i++)
cout << c;
}

void rep(int n, int k);
{
for(int i = 0; i < k; i++)
cout << n;
}

rep(42.23,10);

Error! This function call is ambiguous! The
compiler has no way of knowing whether the
double 42.23 is supposed to be implicitly
converted
to the char* so we can use
void rep(char,int), or whether it should
be implicitly converted to the int 42 so we can use
void rep(int,int). This results in a
compiler error!

How is the compiler supposed to decide which implicit conversion
is best? When ambiguities like this arise, simply use explicit
conversion to disambiguate. For the previous example, I might
change the call rep(42.23,10); to
rep(char(42.23),10);, thus removing any
ambiguity.

A Common Use

One common use of overloading would go something like this: I've
defined the function void rep(char c, int k, ostream&
OUT);,
which writes the character ck times to
the output stream OUT. This allows me to write to
any output stream, of course, but I typically only want to write
to the screen, and I don't like writing rep('$',30,cout),
because I am evidently quite lazy, and instead I'd rather write
rep('$',30) with the understanding that if no output
stream is specified, rep should write to
cout. One way to accomplish this is to simply
overload rep by adding this function definition:

void rep(char c, int k)
{
rep(c,k,cout);
}

With this definition, I have my wish. It probably looks strange
to have the function rep calling the function
rep, but remember --- really we have the function
rep(char,int) calling the function
rep(char,int,ostream&).
Try stepping through this program using the
debugger, and watch how the call rep('$',30) actually
results in calls to both rep functions.

Recursion
← Note: this is just a teaser trailer
for recursion, it gets covered in detail later

Let's consider the following function, which is a
variation on our old familiar example: A function
line(int k) that prints a line of k
asterisks. And just to be on the safe side, let's make sure our
function still works properly when negative k's are
passed to it. "Works properly" in this case means, does nothing!

Well, I might decide to try the following idea. Since
line already does all the work of printing out a
line for me, why don't I just add a line(k-1) at the
end of my function definition? In other words, how aobut:

Well, if I compile and run this with something like
line(10), here's what I get:

**********
*********
********
*******
******
*****
****
***
**
*

So what happened? A function that "calls itself" as this one does
is called recursive, and the best way to understand
what's going on here is to step through it using the debugger.
So, step through this program using the
debugger and see what's really going on here!

Problems

Look at this program and explain what
happens, i.e. answer the question posed up in the comment block.

Look at this program, similar to the
one above, and explain what
happens, i.e. answer the question posed up in the comment
block. Hint: recall that char + int produces an int value!

Step through this program with the
debugger and figure out what useful work (if any) the mystery
function accoplishes.