컴파일 타임에 타입 선택하기

 

template<typename T>
struct datatype {
    using type = T;
};

using t = typename datatype<int>::type; // 컴파일 타임에 데이터 타입 선택
t myVar = 123;

 

템플릿 매개변수로 받은 타입을 저장하고 해당 타입 사용 시, 재정의해서 사용한다.

 

template<bool predicate, typename TrueType, typename FalseType>
struct IfElseDataType {};

template<typename TrueType, typename FalseType>
struct IfElseDataType<true, typename TrueType, typename FalseType> { // 템플릿 부분 특수화
    using type = TrueType;
};

template<typename TrueType, typename FalseType>
struct IfElseDataType<true, typename TrueType, typename FalseType> { // 템플릿 부분 특수화
    using type = FalseType;
}

IfElseDataType<SHRT_MAX = 2147583647, short, int>::type myVar; // false이므로 int 선택

 

조건식에 따라 데이터 타입을 선택할 수도 있다.

 

 

 

 

템플릿 메타프로그래밍으로 흐름 제어

 

조건에 따라 다음 작업 결정

void trueStatement() { std::cout << "true statement is run." << '\n'; }
void falseStatement() { std::cout << "true statement is run." << '\n'; }

if (2 + 3 == 5) trueStatement();
else falseStatement();

 

보통 조건을 처리할때는 if-else 구문을 사용해서 위와 같이 처리한다.

 

template<bool predicate>
class IfElse {};

template<>
class IfElse<true> { // 템플릿 특수화
public:
    static inline void func() { trueStatement(); }
};

template<>
class IfElse<false> { // 템플릿 특수화
public:
    static inline void func() { trueStatement(); }
};

IfElse<(2 + 3 == 5)>::func();

 

템플릿 특수화를 사용하면 템플릿 매개변수에 조건식을 넣어서 원하는 조건을 정확하게 처리할 수 있다.

 

 

구문 선택

일치하는 구문을 선택하는 switch문 역시 템플릿 특수화로 바꿔서 구현할 수 있다.

 

int Square(int a) { return a * a; }

template<int val> // switch-default
class SwitchTemplate {
public:
    static inline int func() { return Square(0); }
};

template<>
class SwitchTemplate<1> { // switch-case 1
public:
    static inline int func() { return Square(1); }
};

template<>
class SwitchTemplate<2> { // switch-case 2
public:
    static inline int func() { return Square(2); }
};


int output = SwitchTemplate<2>::func();

 

 

루프에 적용하기

재귀로 구현하고 루프를 빠져나올 조건이 될 특수화 템플릿이 하나 이상 반드시 존재해야한다.

 

void printNumber(int i) { std::cout << i << '\t'; }

template<int limit>
class DoWhile {
public:
    static inline void func() {
        PrintNumber(limit);
        DoWhile<run == true ? (limit - 1) : 0>::func();
    }
private:
    static const bool run = (limit - 1) != 0;
};

template<>
class DoWhile<0> { // 템플릿 특수화. 루프 탈출 조건
public:
    static inline void func() {} // 아무것도 하지 않는다
};


DoWhile<100>::func();

 

 

 

 

컴파일 타임에 코드 실행

 

컴파일 타임 상수 얻기

template<int num>
struct fibonacci {
    static const int value = 
        fibonacci<num - 1>::value + fibonacci<num - 2>::value; // 상수 계산
};

template<>
struct fibonacci<1> { // 템플릿 특수화
    static const int value = 1;
};

template<>
struct fibonacci<0> { // 템플릿 특수화
    static const int value = 0;
};


std::cout << fibonacci<25>::value << '\n';

 

enum이나 static은 컴파일 타임에 만들어지기 때문에 문제없이 상수 연산이 가능하다.

 

 

컴파일 타임에 클래스 생성

 

template<int lastNumber, int secondLastNumber>
class IsPrime {
public:
    static const bool primeNumber = (
        (lastNumber % secondLastNumber) &&
        IsPrime<lastNumber, secondLastNumber - 1>::primeNumber);
};

template<int number>
class IsPrime<number, 1> { // 템플릿 부분 특수화
public:
    static const bool primeNumber = 1;
};

template<int number>
class PrimeNumberPrinter {
public:
    void func() { // 객체로 만들어지기 때문에 static이 아니어도 된다
        printer.func(); // number부터 1까지 이동
        if (primeNumber) std::cout << number << '\t';
    }
private:
    PrimeNumberPrinter<number - 1> printer;
    static const bool primeNumber = IsPrime<number, number -1>::primeNumber;
};

template<>
class PrimeNumberPrinter<1> { // 템플릿 특수화
public:
    void func() {}
private:
    static const bool primeNumber = false;
};


PrimeNumberPrinter<500> printer;
printer.func();

 

클래스라고해서 상수와 다른건 없다. 결국 '컴파일 타임' 에 생성되어야 하는 것은 똑같기 때문이다.

 

타입이 다른 객체가 컴파일 타임에 500개 생성되기 때문에(=코드 500개 생성) 컴파일이 오래 걸린다.

디스어셈블리를 확인해보면 실제로 코드가 500개 생성되어 메모리에 잡히는 것을 확인할 수 있다.

덧붙여서 위와 같은 코드는 꼬리 재귀가 아니기 때문에 템플릿 매개변수의 값이 크다면 콜스택이 부족해진다. 최적화가 잘 된 코드는 아니고 예제에만 충실한 코드이다.

 

 

 

 

메타프로그래밍의 장점과 단점

 

장점

◾ 템플릿 메타프로그래밍은 불변성이 있어서 기존 타입을 수정할 수 없으므로 부작용도 없다.

◾ 메타프로그래밍으로 구현하면 가독성이 더 좋아진다.

◾ 코드 반복을 줄일 수 있다.

◾ 컴파일러에 따라 생성된 코드에 인라인을 적용시켜서 성능을 높일 수 있다.

 

단점

◾ 구문이 꽤 복잡해질 수 있다.

◾ 컴파일 시간에 코드를 실행하기 때문에 컴파일 시간이 더 오래 걸린다.

◾ 디버깅이 어렵다. 컴파일에 코드가 생성되므로 디버거가 런타임에 코드를 찾기가 어렵다.

+ Recent posts