CRTP: the curiously recurring template pattern
How a base class template that takes its derived class as a parameter gives static polymorphism, mixins and per-type counters without virtual functions.
In the Curiously Recurring Template Pattern, a class inherits from a template instantiated with itself: struct Version : Ordered<Version>. Inside the base, the derived type is known at compile time, so the base can call the derived class's functions with a static_cast, with no virtual functions and no vtable. The compiler sees the exact function and can inline it.
- Mixins: write a feature once in the base and add it to any class. The example derives
>,<=and>=from a single<. - Per-type bookkeeping: static data in
Base<X>is separate for everyX, because each instantiation is a different class. - Static interfaces in performance-critical libraries, where virtual calls in tight loops would cost too much.
The price: Base<A> and Base<B> are unrelated types, so you can't keep an A and a B in one container. When you need that, use virtual functions or type erasure. C++20's <=> now covers the comparison case, and C++23's "deducing this" covers many others, but CRTP is everywhere in existing code.
Example
#include <iostream>
template <typename D>
struct Ordered {
friend bool operator>(const D& a, const D& b) { return b < a; }
friend bool operator<=(const D& a, const D& b) { return !(b < a); }
friend bool operator>=(const D& a, const D& b) { return !(a < b); }
};
struct Version : Ordered<Version> {
int major, minor;
Version(int ma, int mi) : major(ma), minor(mi) {}
friend bool operator<(const Version& a, const Version& b) {
return a.major != b.major ? a.major < b.major : a.minor < b.minor;
}
};
int main() {
Version a{1, 4}, b{2, 0};
std::cout << (b > a) << (a >= b) << (a <= a) << "\n";
return 0;
}
Output:
101