Function Overloading
Notice that the three functions, print()
, have the same name, but they
compile just fine:
#include <iostream>
using namespace std;
#define SIZEOF(a) sizeof(a)/sizeof(*a)
void print(int arr[], int length);
void print(char arr[], int length);
void print(double arr[], int length);
int main() {
int intArr[]{1, 2, 3, 4};
char charArr[]{'a', 'b', 'c', 'd'};
double doubleArr[]{1.2, 2.9, 3.1, 3.10};
print(intArr, SIZEOF(intArr));
print(charArr, SIZEOF(charArr));
print(doubleArr, SIZEOF(doubleArr));
return 0;
}
void print(int arr[], int length) {
for (int i = 0; i < length; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void print(char arr[], int length) {
for (int i = 0; i < length; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void print(double arr[], int length) {
for (int i = 0; i < length; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
1 2 3 4
a b c d
1.2 2.9 3.1 3.1
This is an example of function overloading — the phenomenon of
when different functions performing different tasks, have the same
identifier. More abstractly, we can think of it as if the function —
an operator — can perform multiple tasks. In C++, we can accomplish
function overloading by either (a) having different type parameters, (b)
having a different number of parameters. In general, to accomplish function
overloading, we have to provide C++ a way to differentiate between the
functions. In the example of print()
, this is done by providing different
arguments.
Things that will not work, on their own, for differentiating: Return type, the order the functions are written, and default parameters. Using these differences alone are insufficient to allowing C++ to differentiate the functions, resulting in a name conflict.
Function overloading is tremendously useful. One of the hardest tasks in programming is naming things, made all the more difficult when we have functions that perform something very similar — like printing an array to the console — but differ by just one or two things, such as argument type. However, as with all features, there are tradeoffs.
Function overloading is a kind of polymorphism, a topic we will discuss
further in later sections. In a nutshell, polymorphism is when we have an
entity of a particular type behaving like something else of a particular
type. For example, a person's dog can behave like a loving friend. Yet in
other situations, a vicious guard. The same idea — and risk —
applies to overloaded functions. We must always be cognizant of what our
functions are capable of doing, just as a dog owner should be cognizant of
when and where their dogs are a danger to others. If a function is
overloaded with too many tasks, we risk losing track of what the function
does when passed certain arguments. A similar risk occurs when we overload
a function with tasks that don't intuitively correspond to the identifier.
For example, it would be a poor choice to allow print()
to average the
elements of an int
array — it's not clear how averaging something
relates to print
.