← Back

Polymorphism

2026-06-04

What is Polymorphism?

Poly(many) + morph(form) = One name, many behaviours.

Lets take an example, say we need to draw a shape. We figure out the shape and draw it according to the object we are holding/seeing. If we are holding a circle, we draw a circle. If we are holding a square, we draw a square. Say we have a function draw(shape). Now according to different shape, we will get different output.

When is the "figuring out" done?

Now, there are two places where this is done. One is during compile-time and other is during runtime.

Compile-time polymorphism

The compiler resolves which function to call at compile time - zero runtime cost, less flexible. There are many ways to do this

Function/Method Overloading

Same name, different parameter signatures. The compiler picks the right one based on argument types.

#include <bits/stdc++.h>
using namespace std;
void print(int x) {cout << "int " << x << endl; return;}
void print(double x) {cout << "double " << x << endl; return;}
void print(string x) {cout << "string " << x << endl; return;}
int main(){
    print(42); // int 42
    print(3.14); // double 3.14
    print("Hello World"); // string Hello World
    return 0;
}

When you write print(42), the compiler looks at 42, sees it's an int, and says: "the print for integers is at memory address X - I'll bake that in." When you write print(3.14), it sees a double, picks a different address, and bakes that in. By the time the program runs, these are just two hardcoded jumps. There's no decision happening at runtime at all. The compiler can only do this because it knows the types at the time it's compiling.

def print_thing(x):
    print("first")

def print_thing(x):
    print("second")

print_thing(5) # second

Operator Overloading

Operator overloading is just overloading applied to operators like +,==,<< etc.

In C++, operators like + are already defined for built-in types:

int a = 3 + 5;

But what about our own types?

Vector v1 = {1, 2};
Vector v2 = {3, 4};
Vector v3 = v1 + v2; // what does this mean? compiler has no idea

By default, the compiler has no idea what + means for two vectors. This is where operator overloading comes in. We define + for our vector. In cpp, each operator has a corresponding function name. + is operator+, == is operator==, << is operator<<. So when you write a + b, the compiler secretly reads it as operator+(a, b). That means overloading an operator is just defining that function.

#include <bits/stdc++.h>
using namespace std;
vector<int> operator+(vector<int> a, vector<int> b){
    return {a[0] + b[0], a[1] + b[1]};
}

void print(vector<int> a){
    cout << a[0] << " " << a[1] << endl;
    return ;
}

int main(){
    vector<int> v1 = {1, 2};
    vector<int> v2 = {3, 4};
    print(v1 + v2);
    return 0;
}

The compiler sees v1 + v2, knows both are vector type, finds the operator+ definition, and bakes in that call. Here, the operator+ was operator overloading and print was function overloading.

Note
When compiler sees v1 + v2, it does overload resolution. It collects all viable candidates and picks the best match. The candidates are:

All member operator+ functions on v1's type

All global operator+ functions that match the argument types

If there's exactly one candidate, it uses that. If there are multiple equally good candidates, it's ambiguous, and behavior is undefined or a compile error.

#include <bits/stdc++.h>
using namespace std;
struct Vector {
    float x, y;

    // member function
    Vector operator+(Vector& other) const {
        cout << "member function is used" << endl;
        return { x + other.x, y + other.y };
    }

    void print(){
        cout << x << " " << y << endl;
        return ;
    }
};

// global function
Vector operator+(const Vector &a, const Vector &b){
    cout << "global function is used" << endl;
    return {a.x + b.x, a.y + b.y};
}

void print(Vector a){
    cout << a.x << " " << a.y << endl;
    return ;
}

int main(){
    Vector v1 = {1, 2};
    Vector v2 = {3, 4};
    Vector v3 = v1 + v2; // compiler reads this as v1.operator+(v2) or operator+(v1, v2)
                         // returns {4, 6}
    print(v3);
    v3.print();
}

Here, we will see the member function is used, because it is better. Let's see this through the conversions:

if we use member, the conversion are v1 (Vector -> const Vector&) and v2(Vector -> Vector&). If we use global, the conversion are v1 (Vector -> const Vector&) and v2 (Vector -> const Vector&). So member is used here. If we remove the const from the global function parameter, then global will be used. If we add const in member as well, then it will be ambiguous and depend on the compiler.

There are situations where member functions can't be used. For example, in the case of cout << v, the compiler reads it like this: operator<<(cout, v). The left operand is cout of type ostream. We can't add a member to ostream, that's a standard library class. So we need a free global function here.

Note:
Overloading is always compile-time, as we always know that the type of the parameter during the compilation. In python, compile-time polymorphism doesn't exist, as it is interpreted language, and handles everything on the runtime. In python, if we define the same function twice, then the second function simply overwrites the first one.

Templates

With overloading, we need to create multiple same functions with different type, even though the functionality remains same. So we can use templates to create a blueprint, with the type left as a placeholder.

template<typename T>
T maxOf(T a, T b) {
    return a > b ? a : b;
}

T is just a placeholder type, which means whatever type you give. When it is called,

maxOf(3, 7);       // T = int,    compiler generates the int version
maxOf(2.5, 1.1);   // T = double, compiler generates the double version
maxOf(1.0f, 2.0f); // T = float,  compiler generates the float version

The fundamental difference between overloading and template is

Overloading -> you write multiple functions manually. Each one is a separate, explicitly written function. The compiler just picks between functions you wrote.

Template -> you write one blueprint. The compiler generates the functions for you, one per type used.

// overloading
int    maxOf(int a,    int b)    { return a > b ? a : b; }
double maxOf(double a, double b) { return a > b ? a : b; }
float  maxOf(float a,  float b)  { return a > b ? a : b; }

// template
template<typename T>
T maxOf(T a, T b) { return a > b ? a : b; }

Note: T must support whatever you do with it. If T doesn't support the operations used, you get a compile error.

Templates can be used for classes as well. This is what vector, vector etc are, the standard library's vector is a template, and vector is one instantiation of it.

#include <bits/stdc++.h>
using namespace std;

template<typename T>
class Vector{
    T value;

public:
    Vector(T v){
        value = v;
    }
    T get(){return value;}
};

int main(){
    Vector<int> v(5);
    cout << v.get() << endl;
}