모두의 코드 내용을 공부하고 정리한 내용입니다.



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

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;
std::cout << "Addtion result : " << three::num << std::endl;
}  
  • 여기서 흥미로운 점은 3이라는 출력 값이 프로그램이 실행되면서 계산되는 것이 아니라 컴파일 시에 three::num를 3으로 이미 결정되어 있다
  • 타입은 반드시 컴파일 타임에 확정되어야 하므로, 컴파일 타임에 모든 연산이 끝난거다.


  • 이제까지 타입은 어떠한 객체에 무엇을 저장하느냐를 지정하는데 사용해 왔지, 타입 자체가 어떠한 값을 가지지는 않았다.
  • 템플릿을 사용하면 객체를 생성하지 않더라도 타입에 어떠한 값을 부여할 수 있고, 연산도 가능하다.
  • 또한 타입은 반드시 컴파일 타임에 확정되어야 하므로, 컴파일 타임에 모든 연산이 끝난다.
    • 컴파일 타임에 많은 연산을 하여 런타임의 연산을 줄여준다.


  • 타입을 가지고 컴파일 타임에 생성되는 코드로 프로그래밍을 하는 것을 메타 프로그래밍(meta programming) 이라고 한다.
  • C++ 의 경우 템플릿을 가지고 이러한 작업을 하기 때문에 템플릿 메타 프로그래밍, 줄여서 TMP 라고 한다.


  • 위 예제 처럼 객체를 생성하지 않고 타입에 어떠한 값을 부여할 수 있고 그 타입으로 연산을 한다.
  • 타입으로 연산한다 했지만 예시 처럼 컴파일 타임에 연산하기 위해서는 컴파일할 때 컴파일러가 템플릿의 인자를 정확하게 알고 있어야 하므로
    • 컴파일 시점에서 알 수 있는 상수 값이여야 한다.


TMP의 장단점점

  • 사실 어떠한 c++코드도 TMP코드로 변환할 수 있다
    • 코드가 길어지고, TMP는 매우 복잡하다.
  • TMP코드는 모두 컴파일 타임에 모든 연산이 끝나기 때문데 프로그램 실행 속도를 향상 시킬 수 있다는 장점이 있다.
    • 컴파일 시간 늘어남
  • TMP는 버그 찾기가 힘들다.
    • 컴파일 타임에 연산하기에 디버깅이 불가능하다.


  • 따라서 컴파일 타임에 여러 오류들을 잡아내고 속도가 중요한 프로그램인 경우 TMP를 통해 런타임 속도를 향상시킨다.
    • 많은 라이브러리들은 이미 TMP로 구현되어 있다.


그럼 어떻게 코드를 짜야하나?

int gcd(int a, int b) {
  if (b == 0) {
    return a;
  }

  return gcd(b, a % b);
}


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;
};
int main() {
  std::cout << "gcd (36, 24) :: " << GCD<36, 24>::value << std::endl;
}


  • 첫 번째 예제는 프로그래밍이 실행될 때 계산하고
  • 두 번째는 컴파일할 때 값을 구하면서 계산된다.
    • 여기서 GCD<>::value가 컴파일 시점에 계산되기 위해서 앞서 말한 컴파일 타임에 정의되어야 한다.


  • 그럼 앞에서 계속 말한 컴파일 시점에서 알 수 있는 상수 값은 어떻게 정의하는가?
    • 이를 위해 템플릿을 사용하는 것이다.
    • 템플릿 인자로 넘겨주는 타입의 경우 앞서 말해 반드시 컴파일 타임때 정의되기 때문이다.


  • 정수 아닌 유리수를 구하는 것을 구현해보자
template <int N, int D = 1>
struct Ratio {
typedef Ratio<N, D> type;   // this로 사용하기 위함함
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;
};

int main(){
typedef Ratio<1, 2> rat1;
typedef Ratio<2, 1> rat2;
typedef _Ratio_add<rat, rat2>::type rat3;
}


  • 여기서 ::tpye 을 붙이기 귀찮다면
template <class R1, class R2>
struct Ratio_add : _Ratio_add<R1, R2>::type {};
  • 처럼 _Ratio_add<R1, R2>::type 를 상속 받는 Ratio_add 클래스를 만들어 버리면 Ratio_add는 마치 Ratio 타입 처럼 사용할 수 있다.


  • C++11부터 typedef 대신에 좀 더 직관적인 using 키워드를 사용
typedef Ratio_add<rat, rat2> rat3;
using rat3 = Ratio_add<rat, rat2>;


  • 또 함수 포인터의 경우 만일 void 를 리턴하고 int, int 를 인자로 받는 함수의 포인터의 타입을 func 라고 정의하기 위해서는
    • typedef void (*func)(int, int); 처럼 작성하지만
  • using으로 더 보기 좋게 만들 수 있다. using func = void (*)(int, int);


  • 위 유리수 예제는 객체를 만들어 더하는 함수를 작동한 것 같지만 사실은 생성된 객체는 1개도 없고 단순히 타입들을 컴파일러가 만들어낸 것


의존 타입

  • 컴파일러는 어떠한 식별자(이름)을 보았을 때 값인지 타입인지 결정해야 한다.
template <typename T>
int func() {
T::t* p;
}

class A 
{
const static int t; 
};

class B 
{
using t = int; 
};
  • A클레스에 대해 func함수를 특수화 하면 t는 int형(타입)이 아닌 어떠한 int 값이 되어 t 곱하기 p로 동작
  • B클래스에 대해 특수화를 시키면 int형 포인터 p를 선언하는 꼴이 된다.
    • 이렇게 되면 컴파일러가 두 상황을 명확히 파악하기 위해 T::t가 타입인지 값인지 알려줘야한다.


  • 이렇게 템플릿 인자에 따라 타입이 달라질 수 있는 것을 의존 타입이라고 한다.
    • 위 func 템플릿 함수의 t는 T에 의존하기에 의존 타입이다.


  • 컴파일러가 성공적으로 해석하기 위해서 타입이라는 것을 알려주기 위해 간단하게 typename 키워드만 앞에 붙여주면 된다. 또는 using 사용
    • 값의 경우는 뭘 안 붙여도 괜찮다. 기본적으로 값으로 생각하기 때문이다.

TMP를 왜쓰지?

  • 앞을 보면 너무 복잡하다.
  • 그냥 일반적인 클래스로 만들면 훨씬 직관적이고 편하잖아.

  • 이론적으로는 틀렸지만 런타임에 정상적으로 돌아가는 코드
    • 런타임에 에러가 뜨지 않아 찾기가 힘들어진다.
    • 예시로 속도와 가속도는 서로 연산이 불가능한 단위인데 코드상에서 두 변수를 그냥 연산해도 동작은 한다.
      • 이상적인 상황은 단위가 맞지 않는 연산을 수행하는 코드가 있다면 컴파일타임에 오류를 발생시키는 것.


  • 현업에서는 TMP 활용은 그다지 많지 않다고 한다.
    • 복잡하고 버그 발생시 찾기가 힘들어서
  • 하지만 위의 단위예시 처럼 런타임에 찾아야 하는 오류를 컴파일 타임에 미리 다 잡아낼 수 있고
  • 런타임 시에 수행해야 하는 연산을 컴파일 타임을 옮길 수 있다.


  • 만약 TMP 사용할 일이 있다면 코딩을 도와주는 boost::MPL 라이브러리를 찾아보자.



참조


개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!

카테고리:

업데이트:

댓글남기기