C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
119: CRTP and Compile-Time Polymorphism
I remember the first time I tried to implement the Curiously Recurring Template Pattern (CRTP). I was trying to squeeze every last drop of performance out of a physics engine, and I decided that virtual functions—and their associated vtable lookups—were just too expensive for my inner loop. I wanted the "interface" of polymorphism but the speed of a direct function call.
I wrote what I thought was a clever base class, but the compiler absolutely hated it. Here is the kind of code I wrote back then (and the kind of code you'll likely write the first time you try this):
template <typename Derived>
class Shape {
public:
void draw() {
// I want the derived class to handle the actual drawing logic
this->draw_impl();
}
};
class Circle : public Shape<Circle> {
public:
void draw_impl() {
// Circle-specific drawing logic
}
};
int main() {
Circle c;
c.draw(); // Compiler Error: 'draw_impl' is not a member of 'Shape<Circle>'
}
The Missing Link Between Base and Derived
If you look at that code, it seems logically sound. Circle inherits from Shape<Circle>, so Circle should have access to draw(). But when the compiler looks at Shape::draw(), it has no idea that this is actually a Circle. In the eyes of the compiler, this is just a pointer to Shape<Circle>, and Shape doesn't have a draw_impl() method.
Unlike virtual functions, where the runtime looks up the correct function in a vtable, CRTP happens at compile time. We have to explicitly tell the compiler: "I know this Shape is actually a Derived type; please treat it as such."
Using static_cast to Enable Static Polymorphism
The fix is a static_cast. Since we've designed the class so that Shape is parameterized by the Derived class, we can safely cast this to the derived type. This allows the compiler to resolve the function call at compile time, which often allows the function to be inlined entirely.
template <typename Derived>
class Shape {
public:
void draw() {
// We cast 'this' to the template parameter 'Derived'
static_cast<Derived*>(this)->draw_impl();
}
};
class Circle : public Shape<Circle> {
public:
void draw_impl() {
// Actual implementation
}
};
class Square : public Shape<Square> {
public:
void draw_impl() {
// Actual implementation
}
};
Now, when you call c.draw(), the compiler sees that Derived is Circle. It replaces the static_cast with a direct call to Circle::draw_impl(). There is no vtable, no pointer chasing, and zero runtime overhead.
Trading Runtime Flexibility for Raw Speed
You might be wondering, "Why not just use virtual?" Well, if you are calling a function millions of times per second in a tight loop, that tiny overhead of a vtable lookup adds up. More importantly, virtual functions usually cannot be inlined because the compiler doesn't know which override will be called until the program is actually running.
However, there is a catch. CRTP is static polymorphism. This means you cannot do this:
// This WILL NOT work with CRTP
std::vector<Shape*> shapes;
shapes.push_back(new Circle());
shapes.push_back(new Square());
Why? Because Shape<Circle> and Shape<Square> are two completely different, unrelated types. They don't share a common non-templated base class. If you need a heterogeneous container (a list of different shapes), you're stuck with traditional virtual functions. But if you know the type at compile time, or you're using CRTP to implement a mixin or a policy-based design, the performance gains are massive.
📋 Practical Task
Implementing a High-Performance Packet Processor
You are building a network stack where different types of packets (TCP, UDP, ICMP) need to be processed using a common interface, but the processing happens in a high-frequency loop where performance is critical.
Your Task:
- Create a CRTP base class called
PacketProcessor. - The base class should have a public method
process()that calls a private/protected methoddo_process()on the derived class using the CRTP pattern. - Implement two derived classes:
TCPProcessorandUDPProcessor. - Each derived class should implement
do_process()to print a unique message (e.g., "Processing TCP packet..." and "Processing UDP packet..."). - In your
main()function, instantiate both processors and callprocess()on each to verify that the static polymorphism is working correctly.
There are no comments for now.