C/C++ Arena

Step 8 of 9

CRTP and static polymorphism

Virtual functions choose which code to run at run time, through a hidden pointer to a table of functions. That's flexible, but every call is an indirect jump the compiler usually can't inline. When the concrete type is known at compile time anyway, the Curiously Recurring Template Pattern (CRTP) gets the same "the base class calls the derived class's version" effect with ordinary, inlinable calls. The name comes from its odd shape: a class inherits from a template instantiated with itself.

#include <iostream>
#include <string>

template <typename Derived>
struct Greeter {
    void greet() const {
        // The base knows its real type at compile time, so no virtual call is needed.
        const auto& self = static_cast<const Derived&>(*this);
        std::cout << "Hello from " << self.name() << "\n";
    }
};

struct London : Greeter<London> {
    std::string name() const { return "London"; }
};

struct Madrid : Greeter<Madrid> {
    std::string name() const { return "Madrid"; }
};

template <typename T>
void welcome(const Greeter<T>& g) { g.greet(); }

int main() {
    welcome(London{});
    welcome(Madrid{});
}
Hello from London
Hello from Madrid

How it works

What CRTP is used for

C++23 added "deducing this" (void greet(this const auto& self)), which covers many of these uses without the curious inheritance. You'll still see CRTP everywhere in existing code.

Your task

Write a CRTP base Counted<T> that tracks how many objects of each derived type are alive. Its constructor and copy constructor add one to a counter, its destructor subtracts one, and static int alive() returns the count. Because the counter lives in Counted<Enemy> and Counted<Bullet> separately, each type gets its own count.

Challenge 2

Write a CRTP base Ordered<T> that gives any class >, <= and >= for free, built only from the derived class's own operator<. Define them as friend functions inside Ordered that take const T&. No virtual functions.

Challenge 3

Write a CRTP base ShapeBase<T> with double doubled() const, twice the derived shape's area(), and std::string describe() const, the derived class's static std::string name() plus " " and the area. Reach the derived class with static_cast<const T&>(*this). Then make Square and Rect use it. No virtual functions.

Previous: SFINAE and enable_if Next: Challenge: compile-time computation