참조 : 씹어먹는 C++ - <9 - 4. 템플릿 메타 프로그래밍 2>

기본

  • 템프릿은 컴파일 시에 아무런 코드로 변환X
    • 문법 검사만 실행
  • 인스턴스화 했을 떄, 변환
  • typename T = int 와 같이 디폴트 값 설정 가능

클래스 템플릿

  •   template <typename T>  // <- <class T> 보다 typename 권장
      class Vector {
          T* data;
      }
      // 부분 특수화
      template <typename B>
      class test<int, B, double> {};
    
      // 완전 특수화
      template <>
      class test<int, int, double> {};

명시적 템플릿 인스턴스화

  • 일반적으로 클래스 템플릿은 객체 생성과 타입 지정이 명시적으로 이루어져야 하는 경우가 많음.

    • 중복 코드 방지 (같은 타입의 클래스가 여러 파일에서 중복 인스턴스화 문제)
    • ODR(One Definition Rule) 위반 링크 에러 가능성 높음
  • 함수 템플릿은 암시적 인스턴스화가 유연하게 잘 동작

    • 링크 단계에서 대부분 하나로 병합됨.
    • 즉, 컴파일러에서 최적화를 잘 해줌
  • 명시적 템플릿 인스턴스화 예시

      // 헤더 파일
      template<typename T>
      class MyTemplate {
      public:
          void func();
      };
    
      // 명시적 선언
      extern template class MyTemplate<int>;
    
      // 소스 파일
      template class MyTemplate<int>;

함수 템플릿

  • 기본 사용

      template <typename T>
      T max(T& a, T& b) {
        return a > b ? a : b;
      }
  • Functor : 함수는 아니지만 함수 인 척을 하는 객체

      template <typename Cont, typename Comp>
      void sort(Cont& cont, Comp& comp)
      {
          if(!comp(cont[1], cont[0]))
              cont.swap();
      }
    
      struct Comp1 {
    bool operator()(int a, int b) { return a > b; }
      };
    
    Comp1 comp1;
    sort(int_vec, comp1);
  • 디폴트 템플릿 인자 : 입력값 없을 떄, 기본 타입 지정

      template <typename Cont = int>
  • 템플릿 비타입 매개변수 : 컴파일 시간에 결정되는 정적 값

      template <typename Cont, int num = 5>
  • 팩토리얼 구현 예시

      #include <iostream>
    
      // 기본 템플릿 선언
      template <int N>
      struct Factorial {
          static constexpr int value = N * Factorial<N - 1>::value;
      };
    
      // 특수화: N == 0일 때 재귀 종료
      template <>
      struct Factorial<0> {
          static constexpr int value = 1;
      };
    
      int main() {
          // Factorial<3>의 결과 출력
          std::cout << "Factorial<3> = " << Factorial<3>::value << std::endl; // 출력: 6
          return 0;
      }
    

다양한 타입의 복제, 대입 생성자

  • 만약, double 타입과 int 타입의 템플릿으로 생성된 인스턴스가 있을 때는 다음과 같이 복제, 대입 연산자를 선언해야 한다.
      template <typename T>
      class Grid
      {
      public:
          template <typename E>
          Grid<const Grid<E>& src);
          template <typenmae E>
          Grid<T>& operator=(const Grid<E>& rhs);    
      }
      /* 소스파일 */
      template <typename T>
      template <typename E>    // template <typename T, typename E> 와 같이 작성 불가
      Grid<T>::Grid(const Grid<E>& src) : Grid { src.getWidth(), src.getHeight()}
      {
          ...
      }
      template <typename T>
      template <typename E>
      Grid<T>& Grid<T>::operator=(const Grid<E>& rhs)
      {
          ...
      }
    • template <typename T, typename E> 와 같이 작성 불가

가변길이 템플릿

  • 템플릿 파라미터 팩(parameter pack)
    • template <typename T, typename... Types>
    • void print(T arg, Types... args)
  • 이전 print(T arg)가 있어야 됨.
     template <typename T>
     void print(T arg) {
       std::cout << arg << std::endl;
    }
     template <typename T, typename... Types>
     void print(T arg, Types... args) {
       std::cout << arg << ", ";
       print(args...);
     }
  • 기저 템플릿 없이 사용할려면
      template <typename T, typename... Types>
      void print(T arg, Types... args) {
        std::cout << arg << ", ";
        if constexpr (sizeof(args) > 0)
            print(args...);
      }
  • Fold 방식 (C++17)
    • 4가지 지원
    • 괄호 필수
      return (...+ nums);  // Left Fold
      return (nums + ...); // Right Fold
      return (initial + ... + args); //Left Fold with Initial
      return (args + ... + initial); // Right Fold with Initial

TMP(Template Meta Programing)

  • 예제

      template <int N, int D = 1>
      struct Ratio {
        typedef Ratio<N, D> type;
        static const int num = N;  // 분자
        static const int den = D;  // 분모
      };
      template <class R1, class R2>
      struct _Ratio_add {
        typedef Ratio<R1::num * R2::den + R2::num * R1::den, R1::den * R2::den> type;
      };
    
      template <class R1, class R2>
      struct Ratio_add : _Ratio_add<R1, R2>::type {};
    
      int main() {
        typedef Ratio<2, 3> rat;
        typedef Ratio<3, 2> rat2;
        typedef Ratio_add<rat, rat2> rat3;
    
        std::cout << rat3::num << " / " << rat3::den << std::endl;
    
        return 0;
      }
  • C++11 부터는 using 키워드로 대체가능

      typedef Ratio_add<rat, rat2> rat3;
      using rat3 = Ratio_add<rat, rat2>;
  • 의존 타입은 typename으로 타입임을 명시

where 제약 조건 사용법

  • C++20 이전 :

    • SFINAE (Substitution Failure Is Not An Error) 이용 <- 치환 실패 시, 오버로딩 후보에서 제외

    • enalbe_if 이용

      #include <iostream>
      #include <type_traits>
      
        // 첫 번째 조건: T1이 정수, T2가 실수
        // 템플릿 파라미터에 조건 걸기
        template <typename T1, typename T2,
                  typename std::enable_if<std::is_integral<T1>::value, void>::type* = nullptr,
                  typename std::enable_if<std::is_floating_point<T2>::value, void>::type* = nullptr>
        void func(T1 a, T2 b) {
            std::cout << "정수: " << a << ", 실수: " << b << std::endl;
        }
      
        // 템플릿 파라미터에 조건 걸기, 한 줄로 쓰기
        template <typename T1, typename T2,
              typename std::enable_if<std::is_integral<T1>::value && std::is_floating_point<T2>::value, void>::type* = nullptr>
        void func(T1 a, T2 b) {
            std::cout << "정수: " << a << ", 실수: " << b << std::endl;
        }
      
        // 반환 타입으로 사용
        template <typename T1, typename T2>
        typename std::enable_if<std::is_integral<T1>::value && std::is_floating_point<T2>::value, void>::type
        void func(T1 a, T2 b) {
            std::cout << "정수: " << a << ", 실수: " << b << std::endl;
        }
      
      
    int main() {
    func(42, 3.14);  // 올바른 호출
    // func(3.14, 42);  // 오류: 첫 번째 인자가 실수이므로 조건 불충족
    return 0;
}

    ```
  • C++20 이후 : Concepts

      #include <iostream>
      #include <concepts>
    
      // 정수 타입을 요구하는 Concept
      template <typename T>
      concept Integer = std::is_integral_v<T>;
    
      // Integer Concept을 만족하는 타입만 허용
      template <Integer T>
      T add(T a, T b) {
          return a + b;
      }
    
      int main() {
          std::cout << add(3, 4) << std::endl; // 올바른 호출, 정수 타입
          // std::cout << add(3.5, 4.5) << std::endl; // 컴파일 오류, 실수 타입
          return 0;
      }
    

'공부 > C++' 카테고리의 다른 글

문자열  (1) 2025.03.14
참조자(&)  (0) 2025.03.14
상속  (0) 2025.03.07
foreach 방식 (범위 기반 for 문)  (0) 2025.03.05
(C++17) optional & variant  (0) 2025.03.05

+ Recent posts

let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });