템플릿 -3. 템플릿 메타프로그래밍 1부

·2022년 6월 6일
0

cpp_study

목록 보기
14/25

일반화 프로그래밍: 템플릿을 통해 타입이 마치 인자인 것처럼 사용하는 것

다른 클래스?

/* 나만의 std::array 구현 */ 
#include <iostream>

template <typename T, unsigned int N> 
class Array {
  T data[N];
public:
  // 배열을 받는 레퍼런스 arr 
  Array(T (&arr)[N]) {
    for (int i = 0; i < N; i++) { 
      data[i] = arr[i];
    }
  }

  T* get_array() { 
    return data;
  }

  unsigned int size() {
    return N;
  }

  void print_all() {
    for (int i = 0; i < N; i++) {
      std::cout << data[i] << ", ";
    }
    std::cout << std::endl;
  }
};

int main() {
  int arr[3] = {1, 2, 3}; // 배열 wrapper 클래스
  Array<int, 3> arr_w(arr); 
  arr_w.print_all();
}

그럼 Array<int, 5> Array<int, 3> 이 2개는 다른 클래스인가?

std::cout << (typeid(Array<int, 3>) == typeid(Array<int, 5>)) << std::endl;
// 출력: 0

다른 템플릿 인자로 인스턴스화되었기 때문에 다르다.

템플릿 메타 프로그래밍(TMP)

템플릿을 사용하면 객체를 안 생성해도 타입에 어떤 값을 부여할 수 있고, 또 그 타입들을 가지고 연산을 할 수 있음.

템플릿을 사용하면 컴파일 타임에 연산이 끝난다는 게 특징임.
따라서 다음과 같은 게 가능함.

#include <iostream>
#include <typeinfo>

template <int N> 
struct Int {
  static const int num = N; 
};

template <typename T, typename U>
struct add {
  typedef Int<T::num + U::num> result; 
};

int main() {
  typedef Int<1> one; typedef Int<2> two;
  typedef add<one, two>::result three;
  // 🥰 주목! 컴파일 타임에 3으로 치환됨
  std::cout << "Addtion result : " << three::num << std::endl;
}

즉, three라는 result가 3으로 치환되는 시점이 컴파일될 때인 것이다.

TMP를 왜 쓰는가?

장점

프로그램 실행 속도를 향상시킬 수 있다.
모든 값들이 컴파일될 때 치환되기 때문이다(대신 컴파일 시간<프로그램 실행 속도).

단점

버그를 찾는 게 힘듦.
복잡해서 프로그램 전체를 구현하기에 애매함

예시

#include <iostream>
#include <typeinfo>

template <int X, int Y> 
struct GCD {
  static const int value = GCD<Y, X % Y>::value; 
};

template <int X>
struct GCD<X, 0> {
  static const int value = X; 
};

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;
}

이와 같이 Ratio_add로 분자 분모를 계산해 분수의 계산을 할 수 있으며,
이를 더 확장하면 컴파일 타임에 유리수 사칙 연산을 계산할 수 있다.

의존 타입 (dependent type)

의존 타입: 템플릿 인자에 따라서 어떠한 타입이 달라질 수 있는 것

아래 코드는 컴파일 오류 발생. 이는 divide<N, two>::result가 값인 지 타입인 지 명시하지 않아서 발생하는 오류다.


template <int N> 
struct INT {
  static const int num = N; 
};

template <typename a, typename b> 
struct add {
  typedef INT<a::num + b::num> result; 
};

template <typename a, typename b> 
struct divide {
  typedef INT<a::num / b::num> result; 
};

using one = INT<1>; 
using two = INT<2>; 
using three = INT<3>;

template <typename N, typename d> 
struct check_div {
// result 중에서 한 개라도 true 면 전체가 true 
  static const bool result =
     (N::num % d::num == 0) || check_div<N, add<d, one>::result>::result;
};

template <typename N>
struct is_prime {
  static const bool result = !check_div<N, two>::result;
};

template <>
struct is_prime<two> {
  static const bool result = true;
};

template <>
struct is_prime<three> {
  static const bool result = true; 
};

template <typename N>
// 🥰 여기가 문제!
struct check_div<N, divide<N, two>::result> {
  static const bool result = (N::num % (N::num / 2) == 0); 
};

따라서
truct check_div<N, typename divide<N, two>::result> {
이렇게 typename 키워드를 붙여주면 됨.

auto 키워드

컴파일러가 타입을 정확히 알아낼 수 있는 경우 굳이 그 길고 긴 타입을 적지 않고 간단히 auto로 표현 가능함.
짧은 이름의 타입일 경우 그냥 써주는 게 좋음.

profile
이것저것 개발하는 것 좋아하지만 서버 개발이 제일 좋더라구요..

0개의 댓글