Compile-Time Polymorphism with Templates
Can I use templates to implement compile-time polymorphism?
Yes, you can use templates to implement compile-time polymorphism in C++. This is often referred to as static polymorphism or compile-time polymorphism, as opposed to runtime polymorphism achieved through virtual functions and inheritance.
Compile-time polymorphism with templates allows you to write code that works with different types without the overhead of virtual function calls. It's resolved at compile-time, which can lead to better performance compared to runtime polymorphism.
Here's an example demonstrating compile-time polymorphism using templates:
#include <iostream>
#include <list>
#include <vector>
// Base template for Shape
template <typename T>
class Shape {
public:
void Draw() const {
static_cast<const T*>(this)->DrawImpl();
}
};
// Derived "class" Circle
class Circle : public Shape<Circle> {
public:
void DrawImpl() const {
std::cout << "Drawing a circle\n";
}
};
// Derived "class" Square
class Square : public Shape<Square> {
public:
void DrawImpl() const {
std::cout << "Drawing a square\n";
}
};
// Function template that works with any Shape
template <typename T>
void DrawShape(const Shape<T>& shape) {
shape.Draw();
}
int main() {
Circle circle;
Square square;
DrawShape(circle);
DrawShape(square);
// We can even use it with containers
std::vector<Shape<Circle>*> circles = {
new Circle, new Circle
};
std::list<Shape<Square>*> squares = {
new Square, new Square
};
for (auto& c : circles) DrawShape(*c);
for (auto& s : squares) DrawShape(*s);
// Clean up
for (auto& c : circles) delete c;
for (auto& s : squares) delete s;
}
Drawing a circle
Drawing a square
Drawing a circle
Drawing a circle
Drawing a square
Drawing a square
This example demonstrates the Curiously Recurring Template Pattern (CRTP), a form of compile-time polymorphism. Here's how it works:
- We define a base
Shape
template that declares aDraw()
function. This function callsDrawImpl()
on the derived class using static_cast. - Derived classes (like
Circle
andSquare
) inherit fromShape<DerivedClass>
and implement their ownDrawImpl()
method. - The
DrawShape()
function template can work with any type derived fromShape<T>
. - At compile-time, the correct
DrawImpl()
method is selected based on the actual type of the object.
Key benefits of this approach:
- Performance: There's no virtual function call overhead. The correct function is determined at compile-time.
- Flexibility: You can add new "derived" classes without modifying existing code.
- Static checking: Errors, like forgetting to implement
DrawImpl()
, are caught at compile-time.
Some considerations:
- Code bloat: Each instantiation of the template generates new code, which can increase compile times and binary size.
- Debugging: Template-heavy code can be more challenging to debug due to complex error messages.
- Lack of runtime polymorphism: You can't have containers of mixed shape types or change behavior at runtime.
Compile-time polymorphism with templates is a powerful technique in C++. It's particularly useful in scenarios where you need high performance and know the types you're working with at compile-time. However, it's not a replacement for runtime polymorphism in all cases. The choice between compile-time and runtime polymorphism depends on your specific requirements for flexibility, performance, and design.
Class Templates
Learn how templates can be used to create multiple classes from a single blueprint