NV
NordVarg
ServicesTechnologiesIndustriesCase StudiesBlogAboutContact
Get Started

Footer

NV
NordVarg

Software Development & Consulting

GitHubLinkedInTwitter

Services

  • Product Development
  • Quantitative Finance
  • Financial Systems
  • ML & AI

Technologies

  • C++
  • Python
  • Rust
  • OCaml
  • TypeScript
  • React

Company

  • About
  • Case Studies
  • Blog
  • Contact

© 2025 NordVarg. All rights reserved.

November 11, 2025
•
NordVarg Engineering Team
•

CRTP — Curiously Recurring Template Pattern in C++: elegant static polymorphism

How CRTP works, when to use it, policy/mixin patterns, C++20 improvements, pitfalls, and practical examples you can compile and run.

Generalc++patternsperformancetemplatescrtp
8 min read
Share:

TL;DR#

The Curiously Recurring Template Pattern (CRTP) is a simple, powerful pattern in C++ that gives you polymorphic-like behavior at compile time. Use CRTP when you need the extensibility and reuse of inheritance but want to avoid virtual calls and vtables for performance or compile-time interface validation. It's particularly useful for mixins, policy-based design, and zero-overhead abstractions.

Quick contract#

  • Inputs: concrete derived types implementing small static API.
  • Outputs: reusable base templates providing common logic and compile-time dispatch.
  • Error modes: clear compile-time diagnostics; misuse leads to template errors rather than runtime checks.

Edge cases to consider: object slicing, open hierarchies (where runtime polymorphism is required), heterogeneous containers of different CRTP types (requires wrappers), and ABI stability.

1 — What is CRTP?#

CRTP is when a class template uses a derived class as a template parameter. The derived class then inherits from the template instantiation. The result looks circular — hence the "curiously recurring" name — but is perfectly legal in C++ and powerful.

Minimal form:

cpp
1template <typename Derived>
2struct Base {
3  void interface() {
4    // static polymorphism: call derived implementation
5    static_cast<Derived*>(this)->implementation();
6  }
7};
8
9struct Derived : Base<Derived> {
10  void implementation() { /* ... */ }
11};
12

Base<T> can call methods on T via static_cast<T*>(this). Calls are resolved at compile time with no vtable.

2 — When to choose CRTP vs virtual functions#

Use CRTP when:

  • The set of types is known at compile time or you don't need runtime type erasure.
  • Performance matters and you want to avoid virtual calls.
  • You want to enforce interfaces at compile time.

Use virtual functions when:

  • You need open polymorphism (plugins, dynamic dispatch across translation units, or storing heterogeneous objects in a single container by base pointer).
  • You need ABI-stable polymorphic interfaces across library boundaries.

Trade-offs: CRTP offers zero-overhead and compile-time checks, but eliminates runtime flexibility.

3 — Implementing a CRTP base and derived class#

A practical example: a small logging mixin that provides log() but calls a format() provided by the derived type.

cpp
1#include <iostream>
2#include <string>
3
4template<typename Derived>
5struct Logger {
6  void log(const std::string& msg) {
7    std::cout << static_cast<Derived*>(this)->format(msg) << '\n';
8  }
9};
10
11struct Simple : Logger<Simple> {
12  std::string format(const std::string& msg) { return "[Simple] " + msg; }
13};
14
15int main() {
16  Simple s;
17  s.log("hello");
18}
19

Compile and run:

bash
1# g++ -std=c++20 -O2 -Wall crtp_logger.cpp -o crtp_logger && ./crtp_logger
2

Because log calls format via static_cast<Derived*>(this), the call is resolved statically — no virtual table nor runtime dispatch.

4 — CRTP as mixins and policy-based design#

CRTP shines for mixins: small behaviors you can "mix in" to types without repeating code.

Example: add equality comparable behavior using a derived equals method.

cpp
1template <typename Derived>
2struct EqualityComparable {
3  friend bool operator==(const Derived& a, const Derived& b) {
4    return a.equals(b);
5  }
6  friend bool operator!=(const Derived& a, const Derived& b) {
7    return !operator==(a, b);
8  }
9};
10
11struct Point : EqualityComparable<Point> {
12  int x, y;
13  bool equals(const Point& o) const { return x == o.x && y == o.y; }
14};
15

Policy-based design: make behavior configurable via template parameters.

cpp
1struct AllocatorPolicyDefault { /* ... */ };
2struct AllocatorPolicyCustom { /* ... */ };
3
4template <typename Derived, typename AllocPolicy = AllocatorPolicyDefault>
5class ContainerBase : public AllocPolicy {
6  // container implementation that depends on AllocPolicy and uses Derived for specifics
7};
8

This lets you compose behavior at compile-time with no runtime cost.

5 — Enforcing interfaces: static_assert, SFINAE, and Concepts#

Because CRTP relies on the derived type implementing specific functions, you can provide helpful compile-time diagnostics.

Pre-C++20 SFINAE helper (simple):

cpp
1#include <type_traits>
2
3template<typename T>
4using has_serialize_t = decltype(std::declval<T>().serialize());
5
6template<typename Derived>
7struct Serializer {
8  static_assert(std::experimental::is_detected_v<has_serialize_t, Derived>,
9                "Derived must implement serialize()");
10};
11

C++20 makes this cleaner with concepts:

cpp
1template <typename T>
2concept Serializable = requires(T a) {
3  { a.serialize() } -> std::convertible_to<std::string>;
4};
5
6template <Serializable Derived>
7struct SerializerCRTP {
8  std::string save() { return static_cast<Derived*>(this)->serialize(); }
9};
10

When the derived type doesn't satisfy Serializable, you get a readable compiler error.

6 — Combining CRTP with constexpr and C++20 features#

CRTP pairs well with constexpr and consteval to move even more behavior to compile time. Example: a compile-time registry of capabilities.

cpp
1#include <array>
2#include <string_view>
3
4template<typename Derived>
5struct Tag {
6  static constexpr std::string_view name() { return Derived::static_name(); }
7};
8
9struct Foo : Tag<Foo> { static constexpr std::string_view static_name() { return "Foo"; } };
10
11static_assert(Foo::name() == "Foo");
12

Use consteval factories and if consteval in complex metaprograms to reduce runtime cost.

7 — Pitfalls and caveats#

  • Object slicing: CRTP doesn’t create runtime-polymorphic objects; assigning a derived object to a base object slices derived parts. Typically you use CRTP for static composition, not runtime substitution.
  • Heterogeneous containers: you cannot store different CRTP-derived types in a single container of Base<...> without erasure. Solutions: type erasure wrappers, std::variant, or traditional virtual base pointers.
  • Binary/library ABI: templates are instantiated per-TU; CRTP-based APIs are less friendly as stable ABI boundaries than pure virtual interfaces.
  • Complex error messages: bad CRTP usage can produce long template diagnostics. Use concepts and static_assert messages to improve clarity.

8 — Real-world patterns and examples#

Serialization visitor-like pattern (compile-time visitor): when the set of types is closed or known, you can implement visitor-like behavior using CRTP helper templates instead of virtual visitors.

Example: variant-like static visitor aggregator.

cpp
1#include <iostream>
2
3template <typename Derived>
4struct Printer { // CRTP provides a generic print() that calls derived print_impl
5  void print() const { static_cast<const Derived*>(this)->print_impl(); }
6};
7
8struct A : Printer<A> { void print_impl() const { std::cout << "A\n"; } };
9struct B : Printer<B> { void print_impl() const { std::cout << "B\n"; } };
10
11int main() {
12  A a; B b; a.print(); b.print();
13}
14

Policy-based allocators, mixins for thread-safety, retry/backoff strategies, and compile-time feature toggles are all common uses.

9 — Tests, microbenchmarks and diagnostics#

A recommended minimal test strategy:

  • Unit test derived types that implement the expected API.
  • A small benchmark comparing CRTP dispatch vs virtual call dispatch for tight loops. Example benchmark skeleton:
cpp
1struct VBase { virtual int work() = 0; virtual ~VBase() = default; };
2struct VImpl : VBase { int work() override { return 1; } };
3
4template<typename D> struct CBase { int work() { return static_cast<D*>(this)->work_impl(); } };
5struct CImpl : CBase<CImpl> { int work_impl() { return 1; } };
6
7// measure loop calling vptr vs static call
8

When benchmarking, ensure:

  • Use -O2/3, avoid inlining issues that make virtual calls disappear.
  • Pin the hot loop and benchmark for many iterations.
  • Use a reliable timer (std::chrono::steady_clock) and repeat runs.

10 — Try it yourself (copy/paste runnable examples)#

Save this file as crtp_logger.cpp and compile:

cpp
1// crtp_logger.cpp
2#include <iostream>
3#include <string>
4
5template<typename Derived>
6struct Logger {
7  void log(const std::string& msg) {
8    std::cout << static_cast<Derived*>(this)->format(msg) << '\n';
9  }
10};
11
12struct Simple : Logger<Simple> {
13  std::string format(const std::string& msg) { return "[Simple] " + msg; }
14};
15
16int main() {
17  Simple s;
18  s.log("hello from CRTP");
19}
20

Compile and run:

bash
1g++ -std=c++20 -O2 -Wall crtp_logger.cpp -o crtp_logger && ./crtp_logger
2

For the equality mixin example, try:

cpp
1// crtp_eq.cpp
2#include <iostream>
3
4template <typename Derived>
5struct EqualityComparable {
6  friend bool operator==(const Derived& a, const Derived& b) {
7    return a.equals(b);
8  }
9  friend bool operator!=(const Derived& a, const Derived& b) {
10    return !operator==(a, b);
11  }
12};
13
14struct Point : EqualityComparable<Point> {
15  int x, y;
16  bool equals(const Point& o) const { return x == o.x && y == o.y; }
17};
18
19int main() {
20  Point a{1,2}, b{1,2}, c{2,3};
21  std::cout << std::boolalpha << (a == b) << " " << (a != c) << "\n";
22}
23

Compile and run similarly.

11 — Small FAQ#

Q: Can I store CRTP types in a vector?
A: You can store them by value if all are the same concrete type; for heterogenous types use std::variant or type-erasure wrappers.

Q: Is CRTP faster than virtual calls always?
A: In tight loops yes — CRTP eliminates the indirection. But profile your real workload: branch misprediction, cache effects and inlining can shift numbers.

Q: Does CRTP interact with RTTI?
A: CRTP is a compile-time technique; if you need runtime type information across an inheritance hierarchy you still rely on RTTI/virtuals.

12 — Conclusion and recommendations#

CRTP is a lightweight, zero-cost abstraction pattern that belongs in every modern C++ engineer's toolbox. Use it to:

  • Implement reusable mixins and policies.
  • Enforce interfaces at compile time using concepts.
  • Eliminate virtual calls where runtime polymorphism isn't required.

Prefer clear, small CRTP bases with well-documented expected derived API. When runtime flexibility or ABI stability matters, prefer virtual interfaces or combine CRTP for compile-time parts and virtuals for runtime-erased plumbing.

Further reading#

  • "Modern C++ Design" by Andrei Alexandrescu (policy-based design).
  • C++20 Concepts papers and proposals.
  • Standard library utilities: std::variant, std::visit as alternative for closed sets.
NET

NordVarg Engineering Team

Technical Writer

NordVarg Engineering Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.

c++patternsperformancetemplatescrtp

Join 1,000+ Engineers

Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.

✓Weekly articles
✓Industry insights
✓No spam, ever

Related Posts

Nov 11, 2025•12 min read
Latency Optimization for C++ in HFT Trading — Practical Guide
A hands-on guide to profiling and optimizing latency in C++ trading code: hardware-aware design, kernel-bypass networking, lock-free queues, memory layout, and measurement best-practices.
GeneralC++HFT
Nov 11, 2025•8 min read
Use std::variant + std::visit to avoid virtual dispatch in C++
When the set of types is known ahead of time, prefer std::variant and visitors to eliminate virtual calls and improve performance and ownership semantics.
GeneralC++performance
Nov 25, 2025•17 min read
Stress Testing and Scenario Analysis for Portfolios
Generalrisk-managementstress-testing

Interested in working together?