CRTP (Curiously Recurring Template Pattern)

개념

  • 기반 클래스에서 파생 클래스의 이름을 사용하는 패턴
  • 파생 클래스가 자신의 타입을 기반 클래스의 템플릿 인자로 전달
  • 가상함수를 사용하지않고 가상함수처럼 동작

예제: Window event 처리

  #include <iostream>

class BaseWindow {
public:
    void Click() { }
    void MouseMove() { }
};

template<typename T>  // CRTP
class Window : public BaseWindow {  // Thin Template
public:
    void event_loop() {
        static_cast<T*>(this)->Click();  // 파생 클래스의 Click 호출
    }
};

class MainWindow : public Window<MainWindow> {
public:
    void Click() { std::cout << "MainWindow Click\n"; }
};

class MainWindow2 : public Window<MainWindow2> {
public:
    void Click() { std::cout << "MainWindow2 Click\n"; }
};

int main() {
    MainWindow w;
    w.event_loop();  // MainWindow Click
}
  

CRTP 예제 2: 객체 수 제한

문제 상황

  • 각 클래스마다 생성할 수 있는 객체의 최대 개수를 제한하고 싶다.
  #include <iostream>
#include <exception>

class too_many_object : std::exception {};

template<typename T, int MAXCOUNT>
class LimitObjectCount {
    inline static int maxcnt = 0;
public:
    LimitObjectCount() {
        if (++maxcnt > MAXCOUNT)
            throw too_many_object();
    }
    ~LimitObjectCount() { --maxcnt; }
};

class Player : public LimitObjectCount<Player, 5> {};
class Judge : public LimitObjectCount<Judge, 3> {};

int main() {
    Player p[3];    // ok
    Judge j[5];     // runtime 예외 발생
}
  

SFINAE (Substitution Failure Is Not An Error)

개념

  • 함수 오버로딩 해석 시 타입 대입 실패는 에러가 아니라 다른 후보 탐색으로 이어짐

함수 찾는 순서:

  1. exact match
  2. template
  3. promotion
  4. standard conversion
  5. variable argument

※ ref, namespace, ADL 등이 섞이면 더 복잡

enable_if

개념

  • 특정 타입에 대해서만 템플릿 함수/클래스 사용 가능하도록 제한
  • C++20~: requires 사용
  • ~C++17: enable_if 사용

예제

  #include <print>
#include <type_traits>

void foo(...) { std::println("..."); }

template<typename T>
std::enable_if_t<std::is_integral_v<T>>
foo(T a) {
    std::println("T");
}

int main() {
    foo(3);    // T
    foo(3.4);  // ...
}
  

enable_if 생성자 예제

  #include <type_traits>

class Animal {};
class Dog : public Animal {};

template<typename T>
class smart_ptr {
    T* obj;
public:
    smart_ptr(T* p = nullptr) : obj{p} {}

    template<typename U, typename X = std::enable_if_t<std::is_convertible_v<U*, T*>>>
    smart_ptr(const smart_ptr<U>& other) : obj{other.obj} {}

    template<typename> friend class smart_ptr;
};

int main() {
    smart_ptr<Dog> sp1{new Dog};
    smart_ptr<Animal> sp2 = sp1;  // OK (Dog* → Animal* 변환 가능)
    smart_ptr<int> sp3 = sp1;     // 컴파일 에러
}
  

요약

  • CRTP: 파생 클래스 타입을 기반 클래스의 템플릿 인자로 전달
  • enable_if: 특정 조건 만족할 때만 템플릿 인스턴스 허용
  • SFINAE: 대입 실패는 에러가 아니고 다른 후보 탐색으로 넘어감