Polymorphism in C++ πŸ‘‹

Polymorphism means many forms. Ability of a message to be displayed in many forms is what it is all about. Polymorphism is very important feature in Object Oriented Programming. In C++, we have four different types of polymorphism as mentioned below

Polymorphism - Introduction

  1. Compile-time polymorphism
  2. Run time polymorphism
  3. Ad-hoc polymorphism
  4. Coercion polymorphism

The above four polymorphisms also have different names. Run time polymorphism is also called as Subtype polymorphism, Compile-time polymorphism is also called as the Parametric polymorphism, Ad-hoc polymorphism is also known as overloading, and Coercion polymorphism is also known as the (implicit or explicit) casting.

All this various polymorphisms will make sense after reading some examples which I have shown below.

Compile-time polymorphism (Parametric polymorphism)

Parametric polymorphism is all about executing the same code for any type. Templates are very good example for the parametric polymorphism. One of the simplest example using templates is shown below:

#include <iostream>
#include <string>

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

int main() {
  std::cout << max(9,5) << std::endl;
  std::cout << max(123.1902, 12332.29019) << std::endl;
  return 0;
}

Here the max() function is polymorphic on the type T. Note that if we specialize the max() function then it won’t be a parametric polymorphism anymore. It will be an example of Ad-hoc polymorphism or Function overloading.

Parametric polymorphism is also known as compile-time polymorphism because the types are applied to the template at the compile time and many forms of the template is created.

Runtime polymorphism (Subtype polymorphism)

Runtime polymorphism as the name defines is the ability to use the derived classes through the base class pointers and references. Never ever forget the virtual keyword when it comes to the runtime polymorphism. Overiding the base class function in the derived classes is all what triggers runtime polymorphism.

#include <iostream>
#include <string>

class Bike {
  public:
  virtual void speedup() = 0;
};

class Honda : public Bike {
  public:
  void speedup() {
    std::cout << "Honda speeds at 100 km per hour" << std::endl;
  }
};

class BMW : public Bike {
  public:
  void speedup() {
    std::cout << "BMW speeds at 200 km per hour" << std::endl;
  }
};


void do_speedup(Bike *bike_obj) {
  bike_obj->speedup();
}

int main() {
  Honda honda_bike_obj;
  BMW BMW_bike_obj;

  do_speedup(&honda_bike_obj);
  do_speedup(&BMW_bike_obj);

  return 0;
}

The resolution of polymorphic function calls happens at runtime through an indirection via the virtual table. Compiler does not locate the address of the function to be called at the compile-time, instead the function is called by dereferencing the right pointer in the virtual table.

So this is also called as inclusion polymorphism (or) runtime polymorphism (or) subtype polymorphism.

Ad-hoc polymorphism (Overloading)

Ad-hoc polymorphism allows functions with the same name to act differently for each type. For example, two int variables and the + operator, it adds them together. Given two std::string variables it concatenates them together. This is called overloading. Wait, not just overloading. Function overloading. We also have operator overloading which is to overload operators. Ad-hoc polymorphism appears in C++ if we specialize a template, overload a function or if we overload an operator.

Here is an example of Ad-hoc polymorphism that implements add for int and string variables,

#include <iostream>
#include <string>

int add(int a, int b) {
  return a + b;
}

std::string add(const char *a, const char *b) {
  std::string result(a);
  result +=b;
  return result;
}

int main() {
  std::cout << add(4,7) << std::endl;
  std::cout << add("hello", "world") << std::endl;
}

Ad-hoc polymorphism also appears when we specialize the templates as shown below

template<>
const char *max(const char *a, const char *b) {
  return strcmp(a, b) > 0? a: b;
}

Now we just call ::max(“aaa”,“bbb”) to find the maximum of strings “aaa” and “bbb”.

Coercion polymorphism (Casting)

Coercion happens when the object or a primitive is cast into another object type or primitive type. For example,

float b = 6; //example of implicit casting. int gets casted to float
int a = 9.99; //example of implicit casting. float gets casted to int

Explicit casting happens when you use C’s type casting expressions or C++ casting expressions like static_cast, const_cast, reinterpret_cast, or dynamic_cast.

Coercion also happens if the constructor if the constructor is not explicit, for example,

#include <iostream>

class A {
  int foo;
  public:
    A(int a) : foo(a) {}

    void method1(() {
      std::cout << foo << std::endl;
    }
};

void function1(A a) {
  a.method1();
}

int main() {
  function1(75); //explicitly converts the argument 75 to the parameter of object of A.
  return 1;
}

If you've liked this blog post, consider donating or otherwise supporting me.