컴파일 타임에 타입 선택하기
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개 생성되어 메모리에 잡히는 것을 확인할 수 있다.
덧붙여서 위와 같은 코드는 꼬리 재귀가 아니기 때문에 템플릿 매개변수의 값이 크다면 콜스택이 부족해진다. 최적화가 잘 된 코드는 아니고 예제에만 충실한 코드이다.
메타프로그래밍의 장점과 단점
장점
◾ 템플릿 메타프로그래밍은 불변성이 있어서 기존 타입을 수정할 수 없으므로 부작용도 없다.
◾ 메타프로그래밍으로 구현하면 가독성이 더 좋아진다.
◾ 코드 반복을 줄일 수 있다.
◾ 컴파일러에 따라 생성된 코드에 인라인을 적용시켜서 성능을 높일 수 있다.
단점
◾ 구문이 꽤 복잡해질 수 있다.
◾ 컴파일 시간에 코드를 실행하기 때문에 컴파일 시간이 더 오래 걸린다.
◾ 디버깅이 어렵다. 컴파일에 코드가 생성되므로 디버거가 런타임에 코드를 찾기가 어렵다.
'도서 > 모던 C++로 배우는 함수형 프로그래밍' 카테고리의 다른 글
동시성을 이용한 병렬 실행 (0) | 2022.12.11 |
---|---|
메타프로그래밍으로 코드 최적화 (1) (0) | 2022.12.10 |
지연 평가로 실행 늦추기 (1) (0) | 2022.12.10 |
재귀 함수 호출 (2) (0) | 2022.12.09 |
재귀 함수 호출 (1) (0) | 2022.12.09 |