KoreanFoodie's Study

C++ 기초 개념 9-4 : 템플릿 메타 프로그래밍 2 (의존 타입, auto) 본문

Tutorials/C++ : Beginner

C++ 기초 개념 9-4 : 템플릿 메타 프로그래밍 2 (의존 타입, auto)

GoldGiver 2022. 3. 18. 15:26

모두의 코드를 참고하여 핵심 내용을 간추리고 있습니다. 자세한 내용은 모두의 코드의 씹어먹는 C++ 강좌를 참고해 주세요!

TMP를 이용한 소수 판별 프로그램

컴파일러는 구조상 어떠한 식별자를 보았을때 '값'인지 '타입'인지 결정을 해야 한다. 다음의 예시를 보자.

template <typename T>
int func() {
  T::t* p;
}

class A {
  const static int t;
};

class B {
  using t = int;
};

 

위에서, 클래스 A 에 대해 func 함수를 특수화한다면, t 가 어떠한 int 값이 되어 T::t* p; 가 단순히 클래스 A 의 t 와 p 를 곱하는 식으로 해석이 된다.

반면, func 함수가 클래스 B 에 대해 특수화 된다면, T::t* p; 는 int 형 포인터 p 를 선언하는 꼴이 된다. 따라서 컴파일러는 이 두 상황을 명확히 구분하기 위해 T::t 가 타입인지 아니면 값인지 명확하게 알려줘야만 한다. 

이렇게 템플릿 인자에 따라 어떠한 타입이 달라질 수 있는 것을 의존 타입(dependent type) 이라고 부른다. 따라서 컴파일러가 문장을 성공적으로 해석하기 위해서는 반드시 우리가 쓸 T 가 타입인지 값인지 알려줘야 한다. 타입임을 명시할 때는 typename 을 붙여주기만 하면 된다! (값일  경우 필요없음. 컴파일러는 어떤 식별자를 보았을 때 기본적으로 '값'이라고 생각하기 때문)

 

이제 완성된 소수 판별 프로그램 코드를 보자.

#include <iostream>

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, typename 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, typename divide<N, two>::result> {
  static const bool result = (N::num % (N::num / 2) == 0);
};

template <int N>
struct is_prime {
  static const bool result = _is_prime<INT<N>>::result;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << "Is 2 prime ? :: " << is_prime<2>::result << std::endl;
  std::cout << "Is 10 prime ? :: " << is_prime<10>::result << std::endl;
  std::cout << "Is 11 prime ? :: " << is_prime<11>::result << std::endl;
  std::cout << "Is 61 prime ? :: " << is_prime<61>::result << std::endl;
}

의존 타입 문제가 생기는 경우는 다음과 같다 :

 

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

위 코드에서, check_div 부분이 바로 typename 을 명시함으로써 의존 타입 문제를 해결한 경우이다!

 

 

단위(Unit) 라이브러리

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 {
 private:
  const static int _gcd = GCD<N, D>::value;

 public:
  typedef Ratio<N / _gcd, D / _gcd> type;
  static const int num = N / _gcd;
  static const int den = D / _gcd;
};
template <class R1, class R2>
struct _Ratio_add {
  using type = Ratio<R1::num * R2::den + R2::num * R1::den, R1::den * R2::den>;
};

template <class R1, class R2>
struct Ratio_add : _Ratio_add<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_subtract {
  using type = Ratio<R1::num * R2::den - R2::num * R1::den, R1::den * R2::den>;
};

template <class R1, class R2>
struct Ratio_subtract : _Ratio_subtract<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_multiply {
  using type = Ratio<R1::num * R2::num, R1::den * R2::den>;
};

template <class R1, class R2>
struct Ratio_multiply : _Ratio_multiply<R1, R2>::type {};

template <class R1, class R2>
struct _Ratio_divide {
  using type = Ratio<R1::num * R2::den, R1::den * R2::num>;
};

template <class R1, class R2>
struct Ratio_divide : _Ratio_divide<R1, R2>::type {};

template <typename U, typename V, typename W>
struct Dim {
  using M = U;
  using L = V;
  using T = W;

  using type = Dim<M, L, T>;
};

template <typename U, typename V>
struct add_dim_ {
  typedef Dim<typename Ratio_add<typename U::M, typename V::M>::type,
              typename Ratio_add<typename U::L, typename V::L>::type,
              typename Ratio_add<typename U::T, typename V::T>::type>
      type;
};

template <typename U, typename V>
struct subtract_dim_ {
  typedef Dim<typename Ratio_subtract<typename U::M, typename V::M>::type,
              typename Ratio_subtract<typename U::L, typename V::L>::type,
              typename Ratio_subtract<typename U::T, typename V::T>::type>
      type;
};

template <typename T, typename D>
struct quantity {
  T q;
  using dim_type = D;

  quantity operator+(quantity<T, D> quant) {
    return quantity<T, D>(q + quant.q);
  }

  quantity operator-(quantity<T, D> quant) {
    return quantity<T, D>(q - quant.q);
  }

  template <typename D2>
  quantity<T, typename add_dim_<D, D2>::type> operator*(quantity<T, D2> quant) {
    return quantity<T, typename add_dim_<D, D2>::type>(q * quant.q);
  }

  template <typename D2>
  quantity<T, typename subtract_dim_<D, D2>::type> operator/(
      quantity<T, D2> quant) {
    return quantity<T, typename subtract_dim_<D, D2>::type>(q / quant.q);
  }

  // Scalar multiplication and division
  quantity<T, D> operator*(T scalar) { return quantity<T, D>(q * scalar); }

  quantity<T, D> operator/(T scalar) { return quantity<T, D>(q / scalar); }

  quantity(T q) : q(q) {}
};

template <typename T, typename D>
std::ostream& operator<<(std::ostream& out, const quantity<T, D>& q) {
  out << q.q << "kg^" << D::M::num / D::M::den << "m^" << D::L::num / D::L::den
      << "s^" << D::T::num / D::T::den;

  return out;
}

int main() {
  using one = Ratio<1, 1>;
  using zero = Ratio<0, 1>;

  quantity<double, Dim<one, zero, zero>> kg(2);
  quantity<double, Dim<zero, one, zero>> meter(3);
  quantity<double, Dim<zero, zero, one>> second(1);

  // F 의 타입은 굳이 알필요 없다!
  auto F = kg * meter / (second * second);
  std::cout << "2 kg 물체를 3m/s^2 의 가속도로 밀기 위한 힘의 크기는? " << F
            << std::endl;
}

위에서 auto 키워드를 통해 자동으로 타입을 추측하게 만들어 코드가 더욱 간결해졌다. auto 타입을 추론하는 방법은 템플릿에 들어갈 타입을 추론하는 것과 동일하다!

 

Comments