#088 – Crash course on function overloading

Since C++ is a strongly-typed language, it is very pedantic. For example, the function below only operates on 2 int types.

int MultiplyInt(int x, int y) { return x * y}

This strictness is useful but what if we want to add 2 floats? Well we can add another function, like this:

float MultiplyFloat(float x, float y) { return x * y}

However, apart from the function parameters and return type, the 2 functions are identical.

Also, you need a clear function naming standard that supports different parameter types. How will this naming standard be enforced with different developers across the team? Also, what happens if we want to add 3, 5 or 10 ints?

This approach quickly falls apart. We need a better solution.

Introducing Function Overloading

Function overloading allows us to create identical function names as long as they have different parameter types.

int Multiply(int x, int y) { return x * y; }

float Multiply(float x, float y) {return x +* y; }

// NOTE: Identical function name

The above program will not throw a compiler error due to a naming conflict. The compiler is smart enough, based on the parameters that are passed in to execute the desired function.

We say that this function has been overloaded.

Introduction to overload resolution

When we pass in arguments into a function that’s been overloaded, the compiler runs the correct overloaded function based on the arguments.

#include <iostream>

int multiply(int x, int y)
{
    return x * y;
}

float multiply(float x, float y)
{
    return x * y;
}

int main()
{
    std::cout << multiply(10, 9) << '\\n'; 
    // multiply(int, int)
    
    std::cout << multiply(11.2f, 53.1f) << '\\n';
    // multiply(float, float)
}

// terminal
90
594.72

Conditions for overload resolution

  1. The function signature of the overloaded functions must be sufficiently distinguished from the other functions.
  2. Each call to an overloaded function has to resolve to an overloaded function.
// These are valid overloads
void print(int value);
void print(double value);
void print(int value, int width);

NOTE: Functions can be overloaded by changing the function signature. The function signature includes the number of parameters and type of the parameters. However, it doesn’t include the function return type.

#include <iostream>

void print(int value)
{
    std::cout << "int: " << value << '\\n';
}

// Valid overload, parameter is different
void print(double value)
{
    std::cout << "double: " << value << '\\n';
}

// Invalid overload, function return type is insufficient to distinguish the function
int print(int value)
{
    return value;
}

int main()
{
    print(42);        // calls print(int)
    print(3.14);      // calls print(double)
}

// Output
/tmp/vZcN8sKFJe/main.cpp:15:5: error: ambiguating new declaration of 'int print(int)'
   15 | int print(int value)

The return type is not enough to overload the function. After all, what function does the compiler run when it encounters print(42)? The void or int version?

Ambiguous calls

Overloading can fail when the compiler cannot determine a single best match.

To demonstrate a poorly overloaded function, we pass in 2 double‘s as the arguments.

#include <iostream>

int Multiply2Num(int x, int y) { return x * y; }
float Multiply2Num(float x, float y) { return x * y; }

int main() {

    std::cout << Multiply2Num(2, 45) << '\\n'; 
    std::cout << Multiply2Num(11.2, 53.1); // An ambiguous function call
}

// terminal
/tmp/KFoCtj4kMG/main.cpp:10:30: error: call of overloaded
'Multiply2Num(double, double)' is ambiguous
   10 |     std::cout << Multiply2Num(11.2, 53.1); // Pass in 2 doubles

The compiler cannot pair this to a function call. The error is an ambiguous function call.

Conclusion

Function overloading allows you to use the same function identifier to represent the same operation across different parameters. The compiler matches the appropriate overloaded function to run via the arguments.

Function overloading provides a great way to reduce the complexity of your program by reducing the number of function names you need to remember. It can and should be used liberally.