Inheritance is one of the most used method for code reuse. Object Oriented Languages strive on the inheritance to collect the common functionality in a base class.
From stackoverflow:
If you think about the Greek roots of the term, it should become obvious.
Poly = many: polygon = many-sided, polystyrene = many styrenes (a), polyglot = many languages, and so on.Morph = change or form: morphology = study of biological form, Morpheus = the Greek god of dreams able to take any form.
So polymorphism is the ability (in programming) to present the same interface for differing underlying forms (data types).
C++ support both Dynamic and Static Polymorphism.
Static Polymorphism using the Curiously Recurring Template Pattern
Since this article is about static polymorphism, we will brush aside all the discussion on dynamic polymorphism.
Let's try to simulate calling different Binary Operators. In the grand-scheme of things this can be used when one wants their own Expression Template Library.
Let's see a simple example code.
template <typename Derived>
struct BinaryOperator{
float interface(float x, float y){
return static_cast<Derived*>(this)->implementation(x, y);
}
};
Here we have a base class template
BinaryOperator
(note that this is not a class as it won't instantiated will the Derived Type is known) with the interface method which calls the implementation of the derived class.struct Add: BinaryOperator<Add>{
float implementation(float x, float y){
std::cout << "Implementation Add" << std::endl;
return x + y;
}
};
struct Subtract: BinaryOperator<Subtract>{
float implementation(float x, float y){
std::cout << "Implementation Sub" << std::endl;
return x - y;
}
};
These are our derived classes for the
BinaryOperator
. It is after looking at BinaryOperator<Add>
that the compiler generates a specialised base class for the Add
and similarly for Subtract
.So now if we create objects of our derived classes and hold a reference to them using our base class, we can call the interface. This call will dispatch to the implementation of derived class.
int main(){
std::cout << std::endl;
BinaryOperator<Add>&& add = Add{};
BinaryOperator<Subtract>&& subtract = Subtract{};
std::cout << add.interface(4, 5) << std::endl;
std::cout << subtract.interface(4, 5) << std::endl;
std::cout << std::endl;
}
As expected the calls for add.interface will be dispatched to
Add's
implementation and similarly for Subtract
.Implementation Add
9
Implementation Sub
-1
Here is the whole code snippet.
#include <iostream>
template <typename Derived>
struct BinaryOperator{
float interface(float x, float y){
return static_cast<Derived*>(this)->implementation(x, y);
}
};
struct Add: BinaryOperator<Add>{
float implementation(float x, float y){
std::cout << "Implementation Add" << std::endl;
return x + y;
}
};
struct Subtract: BinaryOperator<Subtract>{
float implementation(float x, float y){
std::cout << "Implementation Sub" << std::endl;
return x - y;
}
};
int main(){
std::cout << std::endl;
BinaryOperator<Add>&& add = Add{};
BinaryOperator<Subtract>&& subtract = Subtract{};
std::cout << add.interface(4, 5) << std::endl;
std::cout << subtract.interface(4, 5) << std::endl;
std::cout << std::endl;
}
If one is not happy with explicitly working with floats, then we can also take the
DataType
as template parameter.#include <iostream>
template <typename Derived, typename DataType>
struct BinaryOperator{
DataType operator()(DataType x, DataType y){
return static_cast<Derived*>(this)->implementation(x, y);
}
};
template <typename DataType>
struct Add: BinaryOperator<Add<DataType>, DataType>{
DataType implementation(DataType x, DataType y){
std::cout << "Implementation Add" << std::endl;
return x + y;
}
};
template <typename DataType>
struct Subtract: BinaryOperator<Subtract<DataType>, DataType>{
DataType implementation(DataType x, DataType y){
std::cout << "Implementation Sub" << std::endl;
return x - y;
}
};
template <typename DType, template <typename> class Op>
DType execute(Op<DType> op, DType x, DType y){
return op(x, y);
}
int main(){
using DType = double;
std::cout << std::endl;
DType x = 4;
DType y = 5;
std::cout << execute(Add<DType>{}, x, y) << std::endl;
std::cout << execute(Subtract<DType>{}, x, y) << std::endl;
std::cout << std::endl;
}
Thanks for reading!