До C++23 CRTP (class Derived : public Base<Derived>) був основним способом статичного поліморфізму без vtable. Ціна: шаблонний базовий клас, шумний синтаксис, довгі помилки компілятора й ризик передати «не той» Derived у Base<...>.
З deducing this база може бути звичайним (нешаблонним) класом: тип об’єкта виклику виводиться в параметрі this Self&& / this Self&.
Fluent interface (method chaining)
Проблема: методи бази повертають *this, але в ланцюжку потрібен тип похідного класу.
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");
Мінуси: база — шаблон; легко написати class Bad : public BuilderCRTP<OtherClass>; потрібен static_cast.
C++23
class BuilderModern {
public:
template <typename Self>
Self& set_id(this Self& self, int id) {
self.id_ = id;
return self; // уже Derived& — без 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 виводиться як UserBuilder& на виклику з UserBuilder.
Статичний інтерфейс без 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"; }
};
Порівняння
| CRTP | Deducing this |
|
|---|---|---|
| База | Base<Derived> |
звичайний Base |
| Спадкування | class D : public B<D> |
class D : public B |
| Доступ до Derived | static_cast |
параметр self |
| Помилка «не той» тип у базі | можлива | тип завжди з об’єкта виклику |
Це не скасовує CRTP у всіх дизайнах, але для типових сценаріїв «база знає справжній тип виклику» deducing this прибирає шаблонну рекурсію.