Skip to Content
Course content

16: Functions and Overloading

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to start this lesson with a snippet of code that looks perfectly reasonable at first glance. Imagine you're building a simple logging system for a game engine. You want a way to log a basic message, but you also want the option to include a severity level for debugging. You decide to overload the function to keep the API clean.

#include <iostream>
#include <string>

void logMessage(std::string msg) {
    std::cout < "[LOG]: " < msg < std::endl;
}

void logMessage(std::string msg, int level = 0) {
    std::cout < "[LOG Lvl " < level < "]: " < msg < std::endl;
}

int main() {
    logMessage("System initialized"); // This line causes a compiler error
    return 0;
}

The Ambiguity Trap

If you try to compile this, the compiler is going to throw a fit. You'll see an error saying something like "call to 'logMessage' is ambiguous". Now, from a human perspective, this makes no sense. You have one function that takes a string, and another that takes a string and an integer. You passed a string. Why can't C++ just pick the first one?

Here is the catch: the second function has a default argument. Because level defaults to 0, the compiler sees logMessage("System initialized") as a valid call for both functions. It could call the first one, or it could call the second one and just use the default value for the integer. C++ refuses to guess. When two functions are equally valid matches, the compiler gives up and tells you to fix it.

Consolidating via Default Arguments

In this specific case, the fix is simple: you don't actually need two different functions. Overloading is powerful, but using it alongside default arguments is a recipe for disaster. The cleaner approach is to just have one function that handles both scenarios.

#include <iostream>
#include <string>

// We merge both into one. If no level is provided, it defaults to 0.
void logMessage(std::string msg, int level = 0) {
    std::cout < "[LOG Lvl " < level < "]: " < msg < std::endl;
}

int main() {
    logMessage("System initialized");    // Works! level is 0
    logMessage("Low memory warning", 2); // Works! level is 2
    return 0;
}

By removing the first function, we've removed the ambiguity. The compiler now has exactly one path to follow.

How the Compiler Actually Picks an Overload

When you overload a function, you aren't actually creating "one function with multiple versions." You're creating distinct functions that happen to share a name. The compiler distinguishes them by their signature—which consists of the function name and the types/order of its parameters.

It's important to note that the return type is not part of the signature. I've seen a lot of beginners try to do this:

int getValue() { return 42; }
double getValue() { return 42.0; } // Error!

This will fail because the compiler doesn't know which one to call based on the arguments (since there are none). It doesn't look at what you intend to do with the return value to decide which function to run.

When you call an overloaded function, C++ goes through a process called "Overload Resolution." It looks for an exact match first. If it can't find one, it looks for a "promotion" (like turning a char into an int) or a "standard conversion" (like turning an int into a double). If it finds multiple options that are equally "close," you hit that ambiguity error we saw earlier.

Keeping Overloads Intuitive

My advice? Only overload functions that do the same thing, just with different types of data. If you have a function called print(), it makes sense to overload it for int, double, and std::string. But if you find yourself overloading a function where the logic changes completely depending on the arguments, you're better off just giving the functions different names. It makes the code much easier for the next person (or future you) to read.




📋 Practical Task

Build a Polymorphic Volume Calculator

Your task is to create a small program that calculates the volume of different geometric shapes using function overloading. You need to implement a function named calculateVolume that handles the following three scenarios:

  • A Cube: Takes one double (side length). Formula: side^3.
  • A Cylinder: Takes two doubles (radius and height). Formula: 3.14159 * radius * radius * height.
  • A Rectangular Prism: Takes three doubles (length, width, and height). Formula: length * width * height.

In your main() function, call calculateVolume for each of these shapes with sample values and print the results to the console. Ensure that your function signatures are distinct enough that the compiler can resolve them without ambiguity.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.