The curiously recurring template pattern
The curiously recurring template pattern passes the derived class to its own base: class square : public shape<square>. The base is a template, so it knows the exact type of the object it is part of and can call the derived version of a function without a virtual table.
That is static polymorphism. It costs no pointer per object and no indirect call, and the price is that the base and the derived types are fixed at compile time. The pattern is behind std::enable_shared_from_this and most operator-generating libraries.
A base that knows its derived type
The base template stores nothing and has one function. It casts this to the derived type and calls area_impl(), which the derived class must provide; the call is direct because the type is known when the template is instantiated.
The program creates a square and calls area() through the base interface. The output is 16, and the compiler inlined the derived function into the base one.
The derived class inherits from shape<square>, not from shape. Each shape<T> is a different base, which is why two shapes cannot share one base pointer: the pattern trades the common base type for speed.
That trade is the whole decision. When a program needs a container of different shapes behind one pointer, virtual functions are the tool; when the type is known at the call and the object is small, CRTP removes both the pointer and the indirection.
#include <iostream>
template <typename Derived>
class shape
{
public:
int area() const
{
return static_cast<const Derived*>(this)->area_impl();
}
};
class square : public shape<square>
{
public:
explicit square(int side) : side_(side) {}
int area_impl() const
{
return side_ * side_;
}
private:
int side_;
};
int main()
{
square s{4};
std::cout << s.area() << '\n';
}
g++ -std=c++23 -Wall -Wextra crtp.cpp -o crtp
./crtp
16
A counting mixin
The pattern also works for behaviour a class wants to inherit without adding data of its own. The mixin below counts live objects in a static member, and every class that derives from counted<ThatClass> gets its own counter.
The program creates two widgets in a block and prints the count inside and after it. Because counted<widget> and counted<gadget> are different types, the two counters do not interfere.
This is how a library adds features without a common base class: a mixin template with a static member or a member function, and the derived class names itself as the parameter.
#include <iostream>
template <typename Derived>
class counted
{
public:
counted()
{
++count_;
}
counted(const counted&)
{
++count_;
}
~counted()
{
--count_;
}
static int alive()
{
return count_;
}
private:
inline static int count_ = 0;
};
class widget : public counted<widget>
{
};
int main()
{
std::cout << widget::alive() << '\n';
{
widget a;
widget b;
std::cout << widget::alive() << '\n';
}
std::cout << widget::alive() << '\n';
}
g++ -std=c++23 -Wall -Wextra mixin.cpp -o mixin
./mixin
0
2
0
Operators from one comparison
A CRTP base can define the operators a derived class does not want to write. The derived class supplies one comparison and the base writes the others in terms of it, casting to the derived type.
The program's number has operator<, and the base adds operator<= and operator>. The three comparisons in main print in order, and the derived class wrote only one of them.
The standard library does this with std::rel_ops and, since C++20, with the spaceship operator; the CRTP version is the one that works in C++17 and shows the technique plainly.
#include <iostream>
template <typename Derived>
class comparable
{
public:
bool operator<=(const Derived& other) const
{
return !(other < static_cast<const Derived&>(*this));
}
bool operator>(const Derived& other) const
{
return other < static_cast<const Derived&>(*this);
}
};
class number : public comparable<number>
{
public:
explicit number(int value) : value_(value) {}
bool operator<(const number& other) const
{
return value_ < other.value_;
}
private:
int value_;
};
int main()
{
number a{3};
number b{5};
std::cout << (a < b) << ' ' << (a <= b) << ' ' << (a > b) << '\n';
}
g++ -std=c++23 -Wall -Wextra operators.cpp -o operators
./operators
1 1 0
What the pattern saves
A class with a virtual function carries a vtable pointer, as lesson 035 measured. A CRTP base adds nothing to the object at all, because there is no run-time type to store.
The program prints three sizes: a plain class with one int, the same class with a virtual function, and a CRTP class. The first and the third are both four bytes; only the virtual one is sixteen.
The saving is one pointer per object and one indirection per call. It matters for small objects stored in large numbers, and it does not matter for a few objects created once.
#include <iostream>
class plain
{
public:
int value() const
{
return value_;
}
private:
int value_ = 0;
};
class polymorphic
{
public:
virtual ~polymorphic() = default;
int value() const
{
return value_;
}
private:
int value_ = 0;
};
template <typename Derived>
class crtp_base
{
public:
int value() const
{
return static_cast<const Derived*>(this)->value_impl();
}
};
class crtp : public crtp_base<crtp>
{
public:
int value_impl() const
{
return value_;
}
private:
int value_ = 0;
};
int main()
{
std::cout << sizeof(plain) << ' ' << sizeof(polymorphic) << ' ' << sizeof(crtp) << '\n';
}
g++ -std=c++23 -Wall -Wextra size.cpp -o size
./size
4 16 4
The derived method that is missing
The base calls area_impl() on the derived type. If the derived class does not define it, the compiler reports the missing member inside the base template, at the instantiation with that derived type.
The program's broken class inherits from shape<broken> and has no area_impl. The message names the type and the base, and the note points at the call in the base, which is one level away from the class that should have provided the function.
The fix is to add the function, or to give the base a default implementation in terms of something the derived class already has. A static_assert in the base can also turn the missing method into a clearer message.
#include <iostream>
template <typename Derived>
class shape
{
public:
int area() const
{
return static_cast<const Derived*>(this)->area_impl();
}
};
class broken : public shape<broken>
{
};
int main()
{
broken b;
return b.area();
}
g++ -std=c++23 -Wall -Wextra missing.cpp -o missing
missing.cpp: In instantiation of 'int shape<Derived>::area() const [with Derived = broken]':
missing.cpp:20:18: required from here
20 | return b.area();
| ~~~~~~^~
missing.cpp:9:51: error: 'const class broken' has no member named 'area_impl'
9 | return static_cast<const Derived*>(this)->area_impl();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
Exercises
- Add a perimeter() to the CRTP base that calls a derived perimeter_impl() and implement it for square.
-
Derive gadget from counted
in mixin.cpp and check that its counter is separate from widget's. - Remove the static_cast in crtp.cpp and see which call the compiler rejects.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does the CRTP pass to the base class?
Answer
The derived class itself, as a template argument
class square : public shape<square>gives the base the exact derived type. -
What does the CRTP replace?
Answer
Dynamic dispatch through a virtual table
The base casts
thisto the derived type, so the call is resolved at compile time. -
Why can the CRTP call a derived member without a virtual function?
Answer
The base knows the derived type at compile time
The cast to
Derived*is known in the template instantiation, so the call is direct. -
What does the CRTP cost in object size?
Answer
Nothing extra, unlike a virtual table pointer
There is no vtable in the object, which is what the size comparison shows.
-
What does this program print?
#include <iostream> template <typename Derived> struct shape { int area() const { return static_cast<const Derived*>(this)->area_impl(); } }; struct square : shape<square> { int area_impl() const { return 16; } }; int main() { square s; std::cout << s.area(); }Answer
16
The base calls the derived implementation directly, and it returns 16.
-
What does this program print?
#include <iostream> template <typename Derived> struct counted { counted() { ++live; } ~counted() { --live; } static int live; }; template <typename D> int counted<D>::live = 0; struct widget : counted<widget> {}; int main() { std::cout << widget::live << '\n'; { widget a; widget b; std::cout << widget::live << '\n'; } std::cout << widget::live << '\n'; }Answer
0 / 2 / 0
Each instantiation gets its own static counter, and both objects die before the last print.
-
What does this program print?
#include <iostream> template <typename Derived> struct comparable { bool operator!=(const Derived& other) const { return !(static_cast<const Derived*>(this)->operator==(other)); } }; struct value : comparable<value> { explicit value(int n) : number(n) {} int number; bool operator==(const value& other) const { return number == other.number; } }; int main() { value a{1}, b{1}, c{2}; std::cout << (a == b) << ' ' << (a != c) << ' ' << (a != b); }Answer
1 1 0
The base builds != from ==, and the three comparisons are true, true and false.
-
What does this program print?
#include <iostream> struct plain { int value; }; template <typename Derived> struct base { int value; }; struct derived : base<derived> { }; int main() { std::cout << sizeof(plain) << ' ' << sizeof(derived) << ' ' << sizeof(base<derived>); }Answer
4 4 4
The empty base adds nothing beyond its member, so the three sizes are four bytes each on this build.
-
What does this code fail to compile with?
template <typename Derived> struct shape { int area() const { return static_cast<const Derived*>(this)->area_impl(); } }; struct broken : shape<broken> {}; int main() { broken b; return b.area(); }Answer
has no member named 'area_impl'
The derived class does not provide the function the base calls, and the error appears in the base instantiation.
-
When is the CRTP a better choice than a virtual function?
Answer
When the type is known at compile time and the call is hot
The CRTP removes the indirection, but it cannot hold different derived types behind one base pointer.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev