std::enable_shared_from_this is CRTP. The exact pattern people label “advanced” and avoid is already sitting inside the standard library, used constantly by people who have never heard the term. Starting from that example is the fastest way to see what the pattern actually does.
What CRTP Is
CRTP, the Curiously Recurring Template Pattern, is a class deriving from a template instantiated with itself:
template<typename Derived>
struct Shape {
double area() const {
return static_cast<const Derived*>(this)->area_impl();
}
};
struct Circle : Shape<Circle> {
double r;
double area_impl() const { return 3.14159 * r * r; }
};
That looks circular because it is. Circle inherits from Shape<Circle>, which means Shape is parameterized on its own subclass. The reason this compiles and does something useful is that by the time Shape<Circle> is instantiated, the compiler already knows Circle is a complete type. The base class can refer to the derived type, and that reference resolves at compile time with full knowledge of what Circle is.
Why the Cast Works: the Compile-Time Guarantee
The mechanism is static_cast<const Derived*>(this). Inside Shape<Circle>::area(), this points to a Shape<Circle> subobject, which is part of a Circle object. The cast is valid because at the instantiation point, the compiler knows Derived is Circle and can verify the cast is safe. This is not a reinterpret_cast or a risk: CRTP subobjects are laid out so the cast is always well-defined.
Once cast, static_cast<const Derived*>(this)->area_impl() is a direct call to Circle::area_impl. The compiler resolves it at compile time. There is no vtable lookup, no function pointer load, no indirect call. The call goes directly to the concrete method.
This is the entire mechanism behind what people call static polymorphism or compile-time polymorphism: the same shape as virtual dispatch (base exposes interface, derived types implement differently) but without any of the vtable machinery. The abstraction costs nothing at runtime because it does not exist at runtime.
The Codegen Proof
The generated assembly confirms this directly. Both versions below call through what looks like a base-class interface, but the output is identical:
compute_classic(Circle const&):
movsd xmm1, QWORD PTR [rdi]
movsd xmm0, QWORD PTR [rip+0x0]
mulsd xmm0, xmm1
mulsd xmm0, xmm1
ret
compute_new(CircleNew const&):
movsd xmm1, QWORD PTR [rdi]
movsd xmm0, QWORD PTR [rip+0x0]
mulsd xmm0, xmm1
mulsd xmm0, xmm1
ret
movsd to load the radius, movsd to load pi, two mulsd to compute pi * r * r, ret. No vtable pointer load anywhere in either function. No indirect branch. The static_cast through the base class interface compiled away to nothing because the compiler folded the entire call chain into direct floating-point arithmetic.
A virtual equivalent of the same code would load the vtable pointer from the object, load the function pointer at a fixed offset in the vtable, then call through that pointer. The CPU cannot speculate well through an indirect call it has not seen before, and the vtable fetch is an additional memory access on every call. CRTP produces none of that.
std::enable_shared_from_this: CRTP in the Standard Library
class MyObject : public std::enable_shared_from_this<MyObject> {
public:
std::shared_ptr<MyObject> get_self() {
return shared_from_this();
}
};
enable_shared_from_this<T> stores a std::weak_ptr<T> internally. When a shared_ptr<MyObject> is first created, the constructor of shared_ptr detects that MyObject inherits from enable_shared_from_this<MyObject> and initializes the internal weak_ptr. When shared_from_this() is called later, it locks that weak_ptr and returns a shared_ptr<T>.
The CRTP part is why shared_from_this() returns std::shared_ptr<MyObject> and not std::shared_ptr<enable_shared_from_this<MyObject>>. The base class knows T is MyObject because of the template parameter. Without CRTP, the base class has no way to know the concrete derived type it is embedded in, and the return type would be the base class, useless to the caller.
This is not a hypothetical use case of CRTP. It is the standard library facility for safely obtaining a shared_ptr to this, used in every asynchronous callback pattern where a class needs to keep itself alive across a coroutine or an async operation.
CRTP for Mixin Injection
CRTP is not only for calling derived methods from a base. The pattern also runs in the other direction: the base class can inject operators, methods, or functionality into the derived class.
The classic example is comparison operator injection. Define one comparison in the derived class and get the rest for free:
template<typename Derived>
struct Comparable {
bool operator<=(const Derived& other) const {
return !(static_cast<const Derived&>(*this) > other);
}
bool operator>=(const Derived& other) const {
return !(static_cast<const Derived&>(*this) < other);
}
// and so on
};
struct Point : Comparable<Point> {
int x, y;
bool operator<(const Point& other) const { return x < other.x; }
bool operator>(const Point& other) const { return x > other.x; }
};
Point defines two operators. The CRTP base injects the rest. The injected operators call back into the derived type through the same static_cast mechanism. No virtual dispatch, no runtime cost, the entire chain inlines.
boost::operators uses exactly this pattern to generate the full set of comparison and arithmetic operators from a minimal implementation. The C++20 spaceship operator (<=>) largely replaces this specific use case, but the injection mechanism is still the right tool for other kinds of functionality that needs to be added to many unrelated classes without code duplication.
The Barton-Nackman trick extends this further: injecting friend functions into a class through CRTP. Friend functions defined inside a class body are only findable by argument-dependent lookup (ADL), which means you can define operator<< for a type without polluting the enclosing namespace, and have it work correctly when the type is used in std::cout.
C++23: Deducing This Removes the Template Machinery
C++23 added explicit object parameters, sometimes called “deducing this”. A member function can take the object itself as an explicit, deducible first parameter:
struct ShapeNew {
auto area(this auto&& self) {
return self.area_impl();
}
};
struct CircleNew : ShapeNew {
double r;
double area_impl() const { return 3.14159 * r * r; }
};
When c2.area() is called on a CircleNew, self is deduced as CircleNew. The call to self.area_impl() resolves to CircleNew::area_impl. The generated code is identical to the classic CRTP version, confirmed in the objdump above.
The difference is structural. Classic CRTP requires a template base class parameterized on the derived type, a static_cast, and an inheritance declaration that looks deliberately circular. Deducing this requires none of that: a plain non-template base class and a member function that deduces its object type at the call site.
Deducing this also enables patterns classic CRTP cannot: recursive lambdas, for example, where the lambda itself is the “derived type” and can reference itself without a named capture:
auto fib = [](this auto self, int n) -> int {
return n <= 1 ? n : self(n - 1) + self(n - 2);
};
Classic CRTP has no mechanism for this since lambdas are not classes you can inherit from. Deducing this handles it cleanly because the deduction happens at the call site regardless of the calling context.
The Tradeoff: What CRTP Cannot Do
CRTP fits when the set of concrete types is fixed and known at compile time. It does not fit when you need runtime substitutability.
A std::vector<Shape*> holding a mix of Circle, Rectangle, and Triangle and calling area() on each polymorphically requires virtual dispatch. The vtable is precisely the mechanism that allows a pointer to a base type to dispatch to the correct derived method without knowing the concrete type at compile time. CRTP cannot do this: the compiler needs to know Derived at the point of instantiation, which means every call site must know the concrete type.
This is not a deficiency that deducing this fixes. It changes the syntax but not the fundamental constraint: the type must be known at compile time, because the entire point of the pattern is that the dispatch is resolved at compile time.
The practical rule: use CRTP or deducing this when you want the performance of direct dispatch and the types are statically known. Use virtual dispatch when you genuinely need to swap implementations at runtime or hold heterogeneous collections behind a common pointer.
Run: main.cpp
g++ -O2 -std=c++26 main.cpp -o main
./main
Expect classic: 153.938 and new: 153.938, confirming both versions compute the identical result.
Run: codegen comparison
g++ -O2 -std=c++26 -c main.cpp -o main.o
objdump -d -M intel --no-show-raw-insn main.o | grep -A 8 "compute_classic"
objdump -d -M intel --no-show-raw-insn main.o | grep -A 8 "compute_new"
-c compiles to an object file without linking. Expect both functions to produce the same instruction sequence with no vtable pointer load and no indirect call in either one.
Output
$ ./main
classic: 153.938
new: 153.938
$ objdump -d -M intel --no-show-raw-insn main.o | grep -A 8 "compute_classic"
0000000000000000 <compute_classic(Circle const&)>:
0: movsd xmm1,QWORD PTR [rdi]
4: movsd xmm0,QWORD PTR [rip+0x0]
c: mulsd xmm0,xmm1
10: mulsd xmm0,xmm1
14: ret
$ objdump -d -M intel --no-show-raw-insn main.o | grep -A 8 "compute_new"
0000000000000020 <compute_new(CircleNew const&)>:
20: movsd xmm1,QWORD PTR [rdi]
24: movsd xmm0,QWORD PTR [rip+0x0]
2c: mulsd xmm0,xmm1
30: mulsd xmm0,xmm1
34: ret
Identical instruction sequence at different addresses. No vtable pointer load, no indirect call, in either version.
Quick Reference
Coming from other languages
Most languages with inheritance use runtime polymorphism as the default and only option. CRTP is a C++ pattern specifically for the cases where the compiler knows the complete type hierarchy at build time and you want the dispatch to be resolved then rather than at runtime. Languages with traits or typeclasses (Rust, Haskell) express a similar concept through their type system rather than through inheritance, with the compiler generating specialized code per concrete type. The deducing this feature in C++23 brings the syntax closer to how those languages express the pattern.
The 90% mental model
CRTP is a class inheriting from a template parameterized on itself. The base class stores this, casts it to the derived type via static_cast<Derived*>(this), and calls methods on the derived type directly. The call resolves at compile time because the concrete type is known at template instantiation. The result is zero-overhead polymorphism: same interface as virtual dispatch, same compiled output as a direct call. std::enable_shared_from_this is the standard library example everyone has used without knowing the name. C++23 deducing this (this auto&& self) achieves the same dispatch without the template base class or the manual cast. The tradeoff is that the concrete type must be known at compile time: no runtime substitutability, no heterogeneous collections behind a base pointer.