Static Dispatch in C++ (ENG)

Static Dispatch has nothing to do with Static Keyword in C++.

Fake Polymorphism

Static dispatch, or you might be more familiar with the term "static polymorphism." In C++, static polymorphism is achieved using templates and function overloading. It allows polymorphism in compile-time, meaning the function to be called is determined at compile time. To make that happen, there has to be some way to distinguish overloaded functions.

What static polymorphism does is make the parameter a part of the function signature. In the following example, although we call the function print twice, because the parameter type is different, we actually call different functions:

// Achieve static polymorphism with function overloading:

#include <iostream>

void print(int x) {
    std::cout << "Called print(int): " << x << std::endl; // _Z5printi
}

void print(double x) {
    std::cout << "Called print(double): " << x << std::endl; // _Z5printd
}

int main() {
    print(4);   // Calls print(int)
    print(4.5); // Calls print(double)
    return 0;
}

This demonstrates how static polymorphism allows the compiler to determine the correct function to call based on the parameter types at compile time, ensuring efficient and type-safe code.

In C, static polymorphism is not allowed because the name mangling is different between C and C++. In C, the function name generated by the compiler is unique and does not include any parameter information. As a result, function overloading is not supported:

void print(int x){} // The compiler generates the symbol _print
void print(double x){} // Error: void print(int x) already exists

This is why you often see extern "C" used when incorporating a C library into a C++ project. It tells the C++ compiler to disable name mangling for the specified code, ensuring the function names remain compatible with the naming conventions in C.

Templates Made Your Day

Static polymorphism is always achieved using templates, allowing you to write generic code that works with any type. The template will help you generate the actual function code at compile time. In the code below, we only define one single function template, and the compiler will generate print<int>(int) and print<double>(double) for us automatically, which allows you to write flexible and reusable code.

// Achieve static polymorphism with templates:

#include <iostream>

template <typename T>
void print(T t) {
    std::cout << "Called print: " << t << std::endl;
}

int main() {
    print(4);   // Calls print<int>(int)
    print(4.5); // Calls print<double>(double)
    return 0;
}

This concept can also be applied to class member function overloading:

#include <iostream>

template <typename T>
void show(T t) {
    t.show(); // Static dispatch
}

class Base {
public:
    void show() const {
        std::cout << "Base class show" << std::endl;
    }
};

class Derived {
public:
    void show() const {
        std::cout << "Derived class show" << std::endl;
    }
};

int main() {
    Base b;
    Derived d;
    show(b); // Calls Base::show
    show(d); // Calls Derived::show
    return 0;
}

Now, you're welcomed here: Operator Overloading in C++ (ENG)