AlgoMaster Logo

CRTP

Low Priority16 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

CRTP, the Curiously Recurring Template Pattern, is a C++ idiom where a class inherits from a template base that takes the derived class itself as a template argument. The pattern looks strange at first (class Product : public Comparable<Product>), but it solves a real problem: getting polymorphic-looking code without paying the cost of virtual function calls. The base class can call methods on the derived class at compile time, with no vtable lookup, no indirect jump, and full opportunity for the compiler to inline everything away.

The Shape of the Pattern

The pattern has two pieces: a base class templated on a type parameter named (by convention) Derived, and a derived class that lists itself as that template argument.

Read the class header twice. class Concrete : public Base<Concrete>. The derived class hands itself to its own base as a template argument. That's where the "curiously recurring" name comes from: Concrete appears on both sides of the colon. At first glance, it looks like a typo or an infinite-regress error. It's neither. The C++ template system processes the base class template Base<Concrete> lazily enough that by the time it needs to know what Concrete is, the full class definition is available.

Inside Base::interface, the line static_cast<Derived*>(this)->implementation() is the trick that makes the pattern work. this is a Base<Derived>*, but because the actual most-derived object is always a Derived, the cast is safe (as long as users follow the rule that they only inherit from Base<X> from X itself). Once the cast happens, the call to implementation() is a normal, non-virtual call. The compiler knows the exact type. It can inline. The whole thing compiles down to a direct call with no runtime dispatch.

A CRTP call goes through one static_cast (a no-op at runtime) and one direct member call. No vtable lookup, no virtual function indirection. The compiler can inline both the cast and the call, which means a CRTP-based polymorphic call can have zero overhead compared to a hand-written direct call.

When to Use This Pattern

The natural question is "what's wrong with virtual functions?" In most code, nothing is wrong with them. They're flexible, easy to understand, and allow pointers to different derived types in the same container. CRTP is a more specialized tool, and it fits cases where one or more of the following matter.

Performance-sensitive hot paths. Virtual functions add an indirection. The CPU has to load the vtable pointer from the object, look up the right slot, then jump to the function. Modern CPUs predict these jumps well, and the cost is small for any one call. But in tight loops over millions of objects, where the function body itself is short, the indirection can dominate. CRTP eliminates the indirection entirely. The call is direct, the compiler can inline the body, and for trivially-small methods the function call disappears altogether.

Mixins that add behavior without a vtable. Sometimes a piece of functionality has to be bolted onto a class (a counter, a logger, a clone() method) without changing the class's runtime layout. CRTP writes that behavior once in a base template, and any class that needs it inherits from Mixin<Self>. No vtable pointer gets added, no slot in the object grows, and the new methods are still callable in a generic way.

Interface-style contracts with no runtime polymorphism. When template code needs to call .clone() or .compare() or .serialize() on a value, and the compiler has to enforce that the value's type provides those methods, CRTP provides a structured way to do it. The base class declares the interface, the derived class implements it, and any code that takes a Base<T>& or Base<T>* is automatically generic over types that opted into the contract.

The trade-off, and it's a real one, is the loss of heterogeneous storage. A std::vector<Comparable<Product>*> can hold pointers to Comparable<Product> objects, but those are all Products. To also hold an Order, a separate std::vector<Comparable<Order>*> is needed, and that's a different type. CRTP provides static polymorphism, not runtime polymorphism.

The diagram compares what happens at the machine level. A virtual call walks two pointers: load the vtable pointer from the object, then load the function pointer from the vtable slot, then jump. A CRTP call has neither hop. The cast is a compile-time relabel, and the function address is known statically, so the compiler emits a direct call (and often inlines the function body at the call site). The object itself is also smaller in CRTP because there's no vtable pointer to store. For a class that would otherwise have a single virtual function and a single data member, removing the vptr can be a sizeable chunk of the object's footprint.

Example: A Comparable Mixin for Products

Consider a real e-commerce class. Suppose every comparable thing in the store (products, orders, line items) needs the four ordering operators: <, <=, >, >=. They all derive from a single primitive operation: a compareTo that returns a negative number, zero, or a positive number. Writing four operators for every comparable class is repetitive boilerplate. CRTP defines the four operators once.

Product writes exactly one comparison method: compareTo. All four operators come from the base class. To add ordering to a new type, the only requirement is to inherit from Comparable<NewType> and write a single compareTo. There's no virtual function in sight. The four operators are non-virtual member functions of a non-polymorphic base class, and every call through them resolves at compile time to a call to Product::compareTo. The compiler can (and usually will) inline the whole chain.

Each operator does one static_cast (compile-time no-op) plus one call to compareTo. With optimization on, the compiler inlines compareTo directly into the operator and the operator into the call site. The final assembly is the same as a hand-written operator with the comparison inline.

The same trick works for any class. Add Order : public Comparable<Order> with an int compareTo(const Order& other) const that compares by date, and Order gets the same four operators with one line of inheritance and one method.

Example: A Cloneable Interface

Another classic use of CRTP is implementing a polymorphic clone() method without virtual functions. The problem: a hierarchy of cart items needs .clone() to return a deep copy whose dynamic type is the same as the source. With virtual functions, this needs a virtual clone() in every derived class that returns the right type, plus careful management of the return type with covariance. With CRTP, clone() is written once in the base.

Cloneable<Derived>::clone() is a small piece of glue: cast *this to the derived type, hand it to the copy constructor, return a unique_ptr. The reason this isn't trivial without CRTP is that the base class needs to know which copy constructor to call, and without templates, the base can't name the derived type. CRTP gives the base that name (Derived), so the cast and the construction both work.

A hierarchy of cart items (digital items, physical items, gift cards) that each clones correctly through a single clone() method can use the pattern above, with no virtual function and no duplicated clone body in each derived class. The downside is the same as before: a Cloneable<CartItem>* can only point at CartItems and things derived from CartItem. A Cloneable<Order>* can't sit next to it in a container.

Example: A withDiscount Mixin for Pricing

A third common use is adding a method that combines the derived class's data with some external operation. Suppose every priced item in the store should support withDiscount(percent) and return a new instance with the price reduced. The math is the same regardless of the item type, but the constructor signature differs. CRTP writes the discount logic once and has it call the derived type's constructor.

The discount logic lives in one place: Discountable::withDiscount. Both Subscription and GiftCard get the method by inheriting from Discountable<Self> and providing an applyDiscount(double) of their own. The two classes share no data, no common base in the virtual sense, and no vtable. Both opted in to the contract.

What's interesting here is that the return type of withDiscount differs per class: it returns Subscription for a Subscription and GiftCard for a GiftCard. With ordinary inheritance and virtual functions, this is awkward, either returning the base class type and losing information, or using covariant return types with pointer/reference returns. CRTP solves it naturally because the return type is Derived, which is the actual derived type.

withDiscount does one static_cast, one copy construction, one virtual-free method call (applyDiscount), and one return-by-value. The return is move-eligible by NRVO, so the caller typically gets the discounted object constructed directly in its storage with no extra work.

CRTP vs Virtual Functions

The two techniques solve overlapping problems but with different trade-offs. Pick deliberately.

AspectVirtual FunctionsCRTP
DispatchRuntime (vtable lookup)Compile-time (direct call)
Per-call costOne indirect jumpZero (inlinable)
Object size+1 pointer for vptrNo size overhead
Heterogeneous containersYes (vector<Base*>)No (each Base<T> is a different type)
Code size impactOne copy of each functionOne copy per Derived
Compile-time errors for missing methodAt derived class definition (with override)Where the base calls the method
Library/binary boundariesStable ABI, hidden implementationImplementation visible to users of the header
Conceptual frameClassic OOPStatic polymorphism

The most important rows are the first three and the fourth. Use virtual functions for heterogeneous containers of "things that share an interface", because that's what virtual dispatch enables. Use CRTP for the interface-style code organization when the concrete type is known at compile time and the code benefits from direct calls and smaller objects. Performance gaps are most visible in tight loops where the function body is small. For a function that does a database query or hits the network, the cost of a virtual call is invisible.

Both versions print the same thing. The virtual version stores unique_ptr<Priceable>, which is a small wrapper around a pointer to a heap-allocated object that carries a vtable pointer. The CRTP version stores CRTPBook directly, by value, with no vtable. The mixedShelf can hold any subclass of Priceable. The crtpShelf can hold only CRTPBook. To also track digital downloads in the same shelf, the virtual version handles it with one more class. The CRTP version doesn't.

A virtual call typically costs 2-5 extra cycles compared to a direct call due to the load and indirect jump. In a loop that calls a 1-cycle function billions of times, that's a meaningful difference. In a loop that calls a 100-microsecond function, the cost is invisible. Profile before optimizing.

Common Pitfalls

CRTP is a sharp tool, and the type system doesn't catch every mistake.

The wrong-derived-class bug. This is the worst one. class Coupon : public Comparable<Product> compiles, but the cast inside the base treats a Coupon* as a Product*, and any access to Product's data members reads garbage. There's no language feature that prevents this. One mitigation is making the CRTP base's constructor private and declaring the derived class a friend:

Now class Coupon : public Comparable<Product> fails to compile because Coupon is not a friend of Comparable<Product>. This trick is occasionally called the "passkey idiom" applied to CRTP. It catches the most common form of the mistake.

Calling a method that doesn't exist on Derived. When the base does static_cast<Derived*>(this)->doSomething(), the call is checked only when the template is instantiated, not when the base template is defined. If a Derived forgets to provide doSomething, the error appears inside the base class, often with a confusing template instantiation backtrace.

Code bloat. Every distinct Derived produces a separate instantiation of every base method. If Comparable<Product>, Comparable<Order>, and Comparable<Customer> all live in the same binary, the four operators of Comparable are compiled three times. For tiny functions that inline anyway, this is invisible. For larger ones, it can balloon binary size. Inspect the .o files when this is a concern.

No heterogeneous storage. This bears repeating. Comparable<Product> and Comparable<Order> are unrelated types as far as C++ is concerned. There's no common base to store both in. If a design requires a container of mixed types, CRTP is the wrong tool.

Cross-cast confusion. dynamic_cast doesn't work on a CRTP base because the base typically has no virtual functions. A design that needs to ask "what's the actual type behind this pointer?" has reached for the wrong pattern.

Use static_cast when the type is known, or rethink the design otherwise.

Modern Alternatives

Some of what CRTP achieves can now be expressed more directly in modern C++.

C++20 concepts constrain a template parameter to require certain operations, without making the type inherit from anything. For "any type that has a compareTo method", a concept expresses that directly and skips the inheritance entirely:

This is cleaner when the goal is "constrain a generic function" rather than "add behavior to a class". It doesn't add <, <=, etc. to the class, though. CRTP fits that use case better.

C++23 deducing-this (explicit object parameter) is a feature designed for the mixin-style CRTP use case. Instead of taking the derived type through inheritance, a member function takes the object as an explicit this parameter whose type is deduced. The result is CRTP-style mixins without the awkward template-base inheritance:

this const Self& is the new syntax. It says "this method takes its own object as a parameter, and Self is whatever the dynamic type is". A class that wants operator< inherits from Comparable, with no template argument and no static_cast. As of mid-2025 this is supported in recent versions of GCC, Clang, and MSVC, but only with C++23 enabled. For codebases that need to compile with older standards, CRTP remains the way to express this pattern.

Use caseC++17 idiomC++20 optionC++23 option
Constrain a generic functionSFINAEConceptConcept
Add operators to a classCRTPCRTPDeducing this
Static dispatch in templatesCRTPCRTP or conceptDeducing this or concept
Mixin with extra stateCRTPCRTPDeducing this

CRTP is not obsolete. Most production C++ today is C++17 or C++20, and CRTP is well-understood by compilers, tools, and other programmers. On a new C++23 project, the deducing-this syntax is a cleaner way to write the same idea.

Putting It Together: Loggable Orders

A combined example: an Order class that gets a log() mixin and a clone() mixin at the same time, by inheriting from two CRTP bases.

Order inherits from two CRTP bases. Both bases get the same Derived (which is Order) as their template argument. The result is that Order gets log() from Loggable<Order> and clone() from Cloneable<Order>, with no virtual functions and no shared object layout. Each call is a direct, inlinable function call. The object itself is int + std::string + double, with no vtable pointer added.

This composability is one of the quieter wins of CRTP. With virtual-function inheritance, multiple inheritance runs into the diamond problem and virtual bases. With CRTP, each mixin is independent: no shared base, no diamond, no virtual inheritance. Orthogonal pieces of behavior layered on a concrete type.

Quiz

CRTP Quiz

8 quizzes