Skillversitas

Function Overloading and Default Arguments in C++

Function Overloading

Function overloading allows several functions with the same name but different parameters within the same scope. The compiler determines which function is called based on the number, type, and order of the arguments. This is called compile-time polymorphism or static polymorphism.

Overloading rules:
- Parameters must differ in number OR type OR order.
- Differing only in return type is NOT enough for overloading.
- const and non-const parameters are considered the same for overloading with pass by value (but different for pass by reference).

Name Mangling

The compiler changes the names of overloaded functions to make them unique in the symbol table. This process is called name mangling. Example: add(int, int) might become _Z3addii in GCC. This allows several functions with the same name to be linked together.

Default Arguments

Default arguments allow parameters to have a built-in value. If an argument is not provided at the call site, the default value is used. Rule: default parameters must go from right to left (no non-default parameters may follow a default one).

Code Example

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

// Overloading: different number of parameters
void display() {
    cout << "No parameters" << endl;
}

void display(int x) {
    cout << "Integer: " << x << endl;
}

void display(int x, int y) {
    cout << "Two integers: " << x << ", " << y << endl;
}

// Overloading: different parameter types
void process(int x) {
    cout << "Process int: " << x * 2 << endl;
}

void process(double x) {
    cout << "Process double: " << x * 2.5 << endl;
}

void process(string x) {
    cout << "Process string: " << x << "!" << endl;
}

// Overloading: different type order
void print(int a, double b) {
    cout << "int-double: " << a << ", " << b << endl;
}

void print(double a, int b) {
    cout << "double-int: " << a << ", " << b << endl;
}

// Default arguments
void greet(string name, string prefix = "Hello", string suffix = "!") {
    cout << prefix << " " << name << suffix << endl;
}

// Defaults combined with overloading
double computeArea(double side) {
    return side * side; // square
}

double computeArea(double length, double width = 0) {
    if (width == 0) width = length;
    return length * width; // rectangle
}

double computeArea(double base, double height, bool isTriangle) {
    if (isTriangle) return 0.5 * base * height;
    return base * height; // parallelogram
}

// Default arguments with const reference
void printData(const string& name, int age = 0,
               const string& city = "Unknown") {
    cout << "Name: " << name << ", Age: " << age
         << ", City: " << city << endl;
}

// Example: math functions with overloading
double compute(double a, double b, char op) {
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return (b != 0) ? a / b : 0;
        default: return 0;
    }
}

int compute(int a, int b, char op) {
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return (b != 0) ? a / b : 0;
        default: return 0;
    }
}

// Default arguments for a recursive function
void countdown(int n, string indent = "") {
    if (n <= 0) {
        cout << indent << "Done!" << endl;
        return;
    }
    cout << indent << n << "..." << endl;
    countdown(n - 1, indent + "  ");
}

int main() {
    // Overloading: different number of parameters
    cout << "--- Overloading (different count) ---" << endl;
    display();
    display(42);
    display(10, 20);

    // Overloading: different parameter types
    cout << "\n--- Overloading (different types) ---" << endl;
    process(10);
    process(3.14);
    process("Hello");

    // Overloading: different type order
    cout << "\n--- Overloading (different order) ---" << endl;
    print(1, 2.5);
    print(2.5, 1);

    // Default arguments
    cout << "\n--- Default Arguments ---" << endl;
    greet("John");                          // prefix & suffix default
    greet("Mike", "Good morning");          // suffix default
    greet("Anna", "Hey", "!!");           // all provided

    // Default arguments: only right-hand parameters can be defaults
    cout << "\n--- Area of Shapes ---" << endl;
    cout << "Square (5): " << computeArea(5.0) << endl;
    cout << "Rectangle (4, 6): " << computeArea(4.0, 6.0) << endl;
    cout << "Triangle (4, 5): " << computeArea(4.0, 5.0, true) << endl;

    // Data with defaults
    cout << "\n--- Data with Defaults ---" << endl;
    printData("Mike");
    printData("John", 25);
    printData("Anna", 22, "New York");

    // Calculator with overloading
    cout << "\n--- Calculator ---" << endl;
    cout << "5 + 3 = " << compute(5, 3, '+') << endl;
    cout << "5.5 * 2.0 = " << compute(5.5, 2.0, '*') << endl;
    cout << "10 / 3 (int) = " << compute(10, 3, '/') << endl;

    // Countdown with a default parameter
    cout << "\n--- Countdown ---" << endl;
    countdown(5);

    // Overloading with references
    cout << "\n--- Reference Overloading ---" << endl;
    int x = 10;
    const int cx = 20;

    auto displayInt = [](const int& val) { cout << "const ref: " << val << endl; };
    auto displayInt2 = [](int& val) { cout << "non-const ref: " << val << endl; };

    displayInt(x);    // a non-const can be called with a const ref
    displayInt(cx);   // a const works too
    displayInt2(x);   // a non-const ref only works with a non-const

    // Overloading to print various types
    cout << "\n--- Print Various Types ---" << endl;
    display();
    display(42);
    display(42, 100);

    return 0;
}

Code Explanation

  • void display() / void display(int x) / void display(int x, int y) — Three display functions with different numbers of parameters. The compiler picks the right one based on the number of arguments at the call site.
  • void process(int x) / void process(double x) / void process(string x) — Overloading based on parameter type. process(10) calls the int version, process(3.14) the double version.
  • void print(int a, double b) vs void print(double a, int b) — Overloading based on type order. The order of the arguments' types determines which function is called.
  • Return type alone is not enough: int someFunction(int x) and double someFunction(int x) will error because the parameters are the same. The compiler cannot distinguish them by return type alone.
  • string prefix = "Hello" — A default argument. If the caller does not provide prefix, the value "Hello" is used. Defaults must go from right to left.
  • void greet(string name, string prefix = "Hello", string suffix = "!") — Three parameters, two of them defaults. greet("John") = "Hello John!", greet("John", "Hey") = "Hey John!".
  • computeArea(double side) / computeArea(double length, double width = 0) — Overloading AND defaults combined. The compiler first matches the number of arguments, then the types.
  • Ambiguity: If there are too many defaults, ambiguity can occur. computeArea(5) could match the one-parameter version or the two-parameter version (with default width = 0). This is a compile error.
  • countdown(int n, string indent = "") — A default argument can differ on every call. indent gains spaces on each recursion, printing indented output.
  • Name mangling: Behind the scenes, the compiler changes display(int, double) into a unique name such as _Z7displayid. This allows the linker to find the correct function.

Everyday Use Cases

Flexible print function: In a logging system, you might need log("Error"), log("Error", 404), or log("Error", 404, "file not found"). Overloading allows a single function name for all three cases.

Geometry calculator: computeArea(side) for a square, computeArea(p, l) for a rectangle, computeArea(s, t, true) for a triangle. Same name, different parameters.

Backward-compatible API: Default arguments allow adding new parameters without changing old code. A function greet(name, prefix, suffix) can have a fourth parameter added without breaking code that only provides three arguments.

Conclusion

Function overloading allows a single function name for several input variations. The compiler chooses based on the number, type, and order of the parameters. Default arguments simplify calls by providing built-in values. Combining both creates flexible, easy-to-use APIs. Be careful of ambiguity when combining overloading with default arguments.