CRTP расшифровывается как «Любопытно повторяющийся шаблон шаблона». Это идиома C++, в которой класс определяется как специализация шаблона класса, из которого он получен. Этот шаблон можно использовать для достижения статического полиморфизма или полиморфизма, который разрешается во время компиляции, а не во время выполнения.

Вот пример того, как CRTP можно использовать в C++:

template <typename Derived>
class Base {
 public:
  void foo() {
    static_cast<Derived*>(this)->fooImpl();
  }
};

class Derived : public Base<Derived> {
 public:
  void fooImpl() {
    // implementation of Derived::foo()
  }
};

int main() {
  Derived d;
  d.foo();  // Calls Derived::fooImpl()
  return 0;
}

В этом примере класс Derived является производным от класса Base в качестве специализации шаблона Base<Derived>. Метод foo() в классе Base действует как диспетчерская функция, которая вызывает реализацию в классе Derived с помощью оператора static_cast. В функции main мы создаем экземпляр класса Derived, d, а затем вызываем для него метод foo. Поскольку Derived определен как подкласс Base<Derived>, вызов foo приведет к реализации foo в классе Base. Реализация foo в классе Base использует статическое приведение для приведения this к указателю на Derived, а затем вызывает метод fooImpl для этого указателя. Поскольку Derived предоставил реализацию для fooImpl, эта реализация будет выполняться, когда foo вызывается для экземпляра Derived.

Обратите внимание, что метод foo определен в классе Base, а его реализация невелика, поэтому компилятор может выбрать его встраивание, что приведет к еще более быстрому коду.

Используя CRTP, мы можем достичь статического полиморфизма без накладных расходов на виртуальные функции, поскольку отправка правильной реализации разрешается во время компиляции, а не во время выполнения.

Другим примером CRTP является реализация счетчика объектов. Класс счетчика объектов обеспечивает подсчет количества существующих объектов определенного типа. Вот пример:

template <typename T>
class ObjectCounter {
  public:
    ObjectCounter() {object_count_++;}
    ObjectCounter(const ObjectCounter&) { object_count_++; }
    ObjectCounter(ObjectCounter&&) noexcept { object_count_++; }
    ~ObjectCounter() { object_count_--; }
    static std::size_t object_count() { return object_count_; }
  private:
    static inline std::size_t object_count_{0}; // C++17
};

class MyClass: public ObjectCounter<MyClass> {
  // MyClass implementation
};

В этом примере ObjectCounter — это базовый класс, обеспечивающий подсчет количества существующих объектов определенного типа. Переменная-член object_count_ определяется как статическая переменная внутри шаблона, а функция object_count() определяется как статическая функция-член, которая возвращает значение object_count_.

Производный класс MyClass определяется как подкласс ObjectCounter<MyClass>. Это означает, что каждый экземпляр MyClass будет увеличивать значение object_count_ в своем базовом классе, и каждый раз, когда экземпляр MyClass уничтожается, он будет уменьшать значение object_count_.

Вы можете использовать функцию object_count(), чтобы получить количество существующих объектов типа MyClass:

int main() {
  MyClass a, b, c;
  std::cout << MyClass::object_count() << std::endl; // outputs 3
  return 0;
}

В этом примере a, b и c являются экземплярами MyClass, а вызов MyClass::object_count() выводит значение 3, указывающее, что существует три экземпляра MyClass.

Подводя итог, можно сказать, что Curiously Recurring Template Pattern (CRTP) — это мощная идиома C++, которая позволяет создавать классы, связанные посредством наследования и шаблонов. Если вы программист на C++, вам следует рассмотреть возможность использования CRTP.