Before C++23, CRTP (class Derived : public Base<Derived>) was the main tool for static polymorphism without a vtable. The cost: a templated base, noisy syntax, long compiler diagnostics, and the risk of passing the wrong Derived into Base<...>.
With deducing this, the base can be an ordinary (non-template) class: the type of the object of the call is deduced in this Self&& / this Self&.
Fluent interface (method chaining)
Problem: base methods return *this, but the chain needs the derived type.
CRTP
template <typename Derived>
class BuilderCRTP {
public:
Derived& set_id(int id) {
id_ = id;
return static_cast<Derived&>(*this);
}
protected:
int id_{0};
};
class UserBuilder : public BuilderCRTP<UserBuilder> {
public:
UserBuilder& set_name(std::string name) {
name_ = std::move(name);
return *this;
}
private:
std::string name_;
};
UserBuilder builder;
builder.set_id(42).set_name("Oleksii");
Downsides: the base is a template; easy to write class Bad : public BuilderCRTP<OtherClass>; needs static_cast.
C++23
class BuilderModern {
public:
template <typename Self>
Self& set_id(this Self& self, int id) {
self.id_ = id;
return self; // already Derived& — no static_cast
}
protected:
int id_{0};
};
class UserBuilder : public BuilderModern {
public:
UserBuilder& set_name(std::string name) {
name_ = std::move(name);
return *this;
}
void print() const {
std::cout << "ID: " << id_ << ", Name: " << name_ << '\n';
}
private:
std::string name_;
};
UserBuilder builder;
builder.set_id(42).set_name("Oleksii");
Self is deduced as UserBuilder& when called on a UserBuilder.
Static interface without a vtable
CRTP:
template <typename Derived>
struct Printable {
void print() const {
static_cast<const Derived*>(this)->print_impl();
}
};
struct Order : public Printable<Order> {
void print_impl() const { std::cout << "Order #123\n"; }
};
C++23:
struct Printable {
template <typename Self>
void print(this const Self& self) {
self.print_impl();
}
};
struct Order : public Printable {
void print_impl() const { std::cout << "Order #123\n"; }
};
Comparison
| CRTP | Deducing this |
|
|---|---|---|
| Base | Base<Derived> |
ordinary Base |
| Inheritance | class D : public B<D> |
class D : public B |
| Access to Derived | static_cast |
self parameter |
| Wrong type in the base | possible | always the type of the call object |
This does not erase CRTP from every design, but for the common “base needs the real call type” cases, deducing this removes the recursive template.