До 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 прибирає шаблонну рекурсію.