Дивно рекурсивний шаблон () — ідіома в мові програмування C++, коли клас X є похідним від шаблону класу, інстанційованого із використанням самого X як шаблонного аргументу. Назва для цієї ідіоми була запропонована Джимом Копліном,, котрий розглянув її в одному з найперших шаблонних кодів на C++.

Загальна форма

template <typename T> struct base { // ... }; struct derived : base<derived> { // ...

Цей підхід можна використати для реалізації статичного поліморфізму, а також в деяких інших техніках метапрограмування як описано Андрієм Александреску в його книзі — Сучасне проєктування на C++.

Статичний поліморфізм #include <iostream> using namespace std;

template<typename Derived> struct Base { void foo()

static_cast<Derived*>(this)->foo();

struct A : Base<A> { void foo()

cout << "A::foo();" << endl;

struct B : Base<B> { void foo()

cout << "B::foo();" << endl;

template<typename T> void bar(Base<T>& base) { base.foo(); }

int main() { A a; B b; bar(a); bar(b); return 0; } Цей підхід дає ефект подібний до використання віртуальних функцій, без ціни (і деякої гнучкості) динамічного поліморфізму. Це використання CRTP дехто називає «симулюванням динамічного зв'язування». Цей підхід широко використовується в Windows бібліотеках ATL і WTL.

Лічильник об'єктів

Головна ціль лічильника об'єктів — отримання статистики створення й руйнування об'єктів даного класу. Це можна легко реалізувати із використанням CRTP:

template <typename T> struct counter { static int objects_created; static int objects_alive;

counter()

++objects_created; ++objects_alive;

protected: ~counter()

--objects_alive;

template <typename T> int counter<T>::objects_created( 0 ); template <typename T> int counter<T>::objects_alive( 0 );

class X : counter<X> { // ...

class Y : counter<Y> { // ...

Ланцюг методів

Для спрощення багаторазового виклику методів одного об'єкта в об'єктно-орієнтованих мовах програмування існує популярний прийом, відомий як (ланцюг методів). Кожен з цих методів повертає об'єкт, дозволяючи тим самим виклик методів послідовно в одному виразі, без створення додаткових змінних для збереження тимчасових чи проміжних результатів.

Однак у випадках, коли цей прийом застосувати до об'єктів, що мають ієрархію, виникає ускладнення. Припустимо ми маємо наступний базовий клас:

class Printer { public: Printer(ostream& pstream) : m_stream(pstream) {}

template <typename T> Printer& print(T&& t) { m_stream << t; return *this; }

template <typename T> Printer& println(T&& t) { m_stream << t << endl; return *this; } private: ostream& m_stream;

Його методи можуть бути легко викликані по ланцюгу:

Printer{myStream}.println("hello").println(500);

Однак, коли ми задекларуємо похідний клас:

class CoutPrinter : public Printer { public: CoutPrinter() : Printer(cout) {}

CoutPrinter& SetConsoleColor(Color c) { ... return *this; }

то «втратимо» інформацію про клас, при спробі викликати метод базового класу:

CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!"); // помилка компіляції, бо ми тут маємо об'єкт 'Printer', а не 'CoutPrinter'

Це трапилось тому, що 'print' є функцією базового класу — 'Printer', вона повертає екземпляр класу 'Printer'.

Дивно рекурсивний шаблон може бути використаний щоб запобігти вказаній проблемі і реалізувати «Поліморфний ланцюг»:

// Базовий клас template <typename ConcretePrinter> class Printer { public: Printer(ostream& pstream) : m_stream(pstream) {}

template <typename T> ConcretePrinter& print(T&& t)

m_stream << t; return static_cast<ConcretePrinter&>(*this);

template <typename T> ConcretePrinter& println(T&& t)

m_stream << t << endl; return static_cast<ConcretePrinter&>(*this);

private: ostream& m_stream;

// Похідний клас class CoutPrinter : public Printer<CoutPrinter> { public: CoutPrinter() : Printer(cout) {}

CoutPrinter& SetConsoleColor(Color c) { ... return *this; }

// використання CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");

Домішки Дивно рекурсивний шаблон також може бути використаний для реалізації домішок. Поширеним прикладом є клас std::enable_shared_from_this, породжені класи від якого здатні повертати std::shared_ptr на свої екземпляри.

Тобто, якщо клас MySharedClass породжений від public std::enable_shared_from_this то він матиме метод shared_from_this, який повертатиме std::shared_ptr на екземпляр.

Примітки

Див. також Шаблони (C++)