CppPolymorphism

Polymorphism

Polymorphism (Greek for “many forms”) is the ability to call the correct, specific behavior for an object without the calling code needing to know its exact concrete type. In practice, this usually means calling a function through a base-class pointer or reference and having the derived class's version of that function run.

Two kinds of polymorphism

Kind

Resolved when?

Mechanism in C++

Compile-time (static) polymorphism

At compile time, based on argument types

Function overloading, operator overloading, templates

Runtime (dynamic) polymorphism

At runtime, based on the object's actual type

virtual functions called through a base pointer/reference

Compile-time polymorphism

Function overloading picks which function to call based on argument types the compiler already knows about — no runtime decision is needed.

Compile-time polymorphism via overloading

CPP
void print(int value)         { std::cout << "int: " << value << "\n"; }
void print(double value)      { std::cout << "double: " << value << "\n"; }
void print(const std::string& value) { std::cout << "string: " << value << "\n"; }

int main() {
    print(42);       // calls print(int)   — decided at compile time
    print(3.14);     // calls print(double)
    print("hello");  // calls print(const std::string&)
}
Runtime polymorphism

Runtime polymorphism is what lets a single line of code — shape->area() — do different things depending on whether shape actually points to a Circle, a Rectangle, or any other derived type, decided while the program is running rather than baked in at compile time.

The goal: one call site, many behaviors

CPP
class Animal {
public:
    virtual void speak() const { std::cout << "...\n"; }
};

class Dog : public Animal {
public:
    void speak() const override { std::cout << "Woof!\n"; }
};

class Cat : public Animal {
public:
    void speak() const override { std::cout << "Meow!\n"; }
};

void makeItSpeak(const Animal& a) {
    a.speak(); // which version runs depends on the object's *actual* type
}

int main() {
    Dog d;
    Cat c;
    makeItSpeak(d); // Woof!
    makeItSpeak(c); // Meow!
}
Why this needs the virtual keyword

Here is the part that surprises many people coming from languages like Java or C# (where every method is virtual by default): in C++, calling a method through a base-class pointer or reference uses static binding — the compile-time type — unless that method is explicitly marked virtual. Without virtual, makeItSpeak above would print ... for both a Dog and a Cat, because the compiler would resolve a.speak() to Animal::speak at compile time based on the parameter's declared type (const Animal&), completely ignoring what the object actually is at runtime.

Note
This design choice is deliberate: non-virtual calls in C++ can be resolved and inlined at compile time, which is faster than the indirect lookup runtime polymorphism requires. You opt into the (slightly) more expensive dynamic dispatch only for the methods that actually need it, by marking them `virtual`. The full mechanics — and the `override` keyword that helps avoid mistakes here — are covered next in **Virtual Functions**.
What's next
  • See exactly how virtual changes what code gets called, including a worked with/without comparison, in Virtual Functions.

  • Abstract Classes build on virtual functions to define pure interfaces that cannot be instantiated on their own.