CRTP

CRTP (Curiously Recurring Template Pattern)

파생 클래스가 템플릿 매개변수로 자기 자신을 넘기는 패턴을 CRTP(Curiously Recurring Template Pattern, 이상하게 재귀하는 템플릿 패턴)라고 불러요. 다형성을 컴파일 타임에 구현하는 고전적인 기법이에요.

출처: cppreference

본문

CRTP는 클래스 X가 템플릿 매개변수 Z를 받는 클래스 템플릿 Y에서 파생되고, YZ = X로 인스턴스화되는 관용구(idiom)예요. 예를 들어:

template<class Z>
class Y {};

class X : public Y<X> {};

이 패턴에서 X는 자기 타입으로 Y<X>를 인스턴스화하므로, Y 안에서 Z를 통해 파생 타입 X를 알아낼 수 있어요.

예제 (Example)

CRTP는 기반 클래스가 인터페이스를 노출하고 파생 클래스가 그 인터페이스를 구현하는 "컴파일 타임 다형성"을 구현하는 데 쓰일 수 있어요.

#include <cstdio>

#ifndef __cpp_explicit_this_parameter // Traditional syntax

template <class Derived>
struct Base
{
    void name() { static_cast<Derived*>(this)->impl(); }
protected:
    Base() = default; // prohibits the creation of Base objects, which is UB
};
struct D1 : public Base<D1> { void impl() { std::puts("D1::impl()"); } };
struct D2 : public Base<D2> { void impl() { std::puts("D2::impl()"); } };

#else // C++23 deducing-this syntax

struct Base { void name(this auto&& self) { self.impl(); } };
struct D1 : public Base { void impl() { std::puts("D1::impl()"); } };
struct D2 : public Base { void impl() { std::puts("D2::impl()"); } };

#endif

int main()
{
    D1 d1; d1.name();
    D2 d2; d2.name();
}

출력 (Output):

D1::impl()
D2::impl()

전통적 구문의 Base::name()thisDerived*static_cast해서 파생 타입의 impl()을 호출해요. D1Base<D1>의, D2Base<D2>의 인스턴스라서, 같은 name()이지만 실제로는 각자의 impl()이 실행돼요. C++23의 deducing this 구문(this auto&& self)을 쓰면 기반 클래스를 템플릿화하지 않고도 같은 효과를 낼 수 있어요.

더 알아보기 (Learn more)

  • 명시적 객체 멤버 함수 (deducing this) (C++23).
  • std::enable_shared_from_this (C++11) — 객체가 자기 자신을 가리키는 shared_ptr을 만들 수 있게 해주는 클래스 템플릿.
  • std::ranges::view_interface (C++20) — CRTP를 사용해 view를 정의하기 위한 헬퍼 클래스 템플릿.