C++ Template Programming - Template 특수화[4]
이번 포스팅에서는 template specialization, partial specialization, template meta programming에 대해서 알아보겠습니다. 다양한 템플릿 기술의 근본이 되는 아주 중요한 개념입니다.
Specialization 개념
template 구현시 특정 타입에 대해서 다르게 처리되도록 특수화를 할 수 있습니다.
#include <iostream>
using namespace std;
// template specialization
// primary template
template<typename T> class Stack
{
public:
void push(T v) { cout << "T" << endl; }
};
// partial specialization
template<typename T> class Stack<T*>
{
public:
void push(T* v) { cout << "T*" << endl; }
};
// specialization
template<> class Stack<char*>
{
public:
void push(char* v) { cout << "char*" << endl; }
};
int main()
{
Stack<int> s1; s1.push(0);
Stack<int*> s2; s2.push(0);
Stack<char*> s3; s3.push(0);
}
특수화 예제
- 메인 템플릿의 템플릿 인자가 2개라면, 사용자는 반드시 템플릿 인자 2개를 전달 해야 한다. (디폴트 인자가 없을 경우)
- 부분 특수화 버전을 만들 때 템플릿 인자의 개수는 메인 템플릿의 인자 개수와 다를 수 있다.
#include <iostream>
using namespace std;
template<typename T, typename U> struct Test
{
static void foo() { cout << "T, U" << endl; }
};
template<typename T, typename U> struct Test<T*, U>
{
static void foo() { cout << "T*, U" << endl; }
};
template<typename T, typename U> struct Test<T*, U*>
{
static void foo() { cout << "T*, U*" << endl; }
};
// 핵심 : 부분 특수화 시에 템플릿 인자의 갯수는 변할수 있다
template<typename T> struct Test<T, T>
{
static void foo() { cout << "T, T" << endl; }
};
template<typename U> struct Test<int, U>
{
static void foo() { cout << "int, U" << endl; }
};
// int, int : 특수화
template<> struct Test<int, int>
{
static void foo() { cout << "int, int" << endl; }
};
template<> struct Test<int, short>
{
static void foo() { cout << "int, short" << endl; }
};
int main()
{
Test<int, double>::foo(); // T, U
Test<int*, double>::foo(); // T*, U
Test<int*, double*>::foo(); // T*, U*
Test<int, int>::foo(); // T, T => int, int
Test<int, char>::foo(); // int, U
Test<int, short>::foo(); // int, short
}
Specialization 주의사항
- 부분 특수화의 디폴트 값은 표기하지 않음
// partial specialization 과 default parameter
template<typename T, int N = 10> class Stack
{
T buf[N];
};
// 부분 특수화 - 디폴값은 표기 하지 않는다. 표기하지 않아도 primary 값이 적용된다.
template<typename T, int N> class Stack<T*, N>
{
T buf[N];
};
int main()
{
Stack<int> s1; // N == 10
Stack<int*> s2; // N == 10
}
- 클래스의 특정 멤버 함수 하나만 특수화 할 수 있지만, 부분 특수화 할 수는 없음
#include <iostream>
using namespace std;
// 하나의 멤버 함수만 특수화 하기
// primary template
template<typename T> class Stack
{
public:
void pop() {}
void push(T a);
};
template<typename T> void Stack<T>::push(T a)
{
cout << "T" << endl;
};
// 특정 멤버함수만 특수화 하는 코드
template<> void Stack<char*>::push(char* a)
{
cout << "char*" << endl;
};
// 특정 멤버함수만 부분 특수화을 할수는 없다.
// 부분 특수화는 클래스 전체를 변경해야 한다.
template<> void Stack<T*>::push(char* a)
{
cout << "char*" << endl;
};
int main()
{
Stack<int> s1; s1.push(0);
Stack<int*> s1; s2.push(0);
Stack<char*> s1; s3.push(0);
}
특수화 활용
- IfThenElse 기법 (컴파일 타임에 조건에 따라 type 선택 가능)
- C++ 표준 <type_traits>의 conditional을 구현하는 방법
#include <iostream>
using namespace std;
// bool 기반의 type selection 기술.
template<bool, typename T, typename U> struct IfThenElse
{
typedef T type;
};
template<typename T, typename U> struct IfThenElse<false, T, U>
{
typedef U type;
};
template<int N> struct Bit
{
// using data_type = unsigned int;
using data_type = typename IfThenElse< (N <= 8), char, unsigned int>::type;
data_type data;
};
int main()
{
Bit<8> b1; // 8bit를 관리하기 위한 객체
Bit<32> b2; // 32bit를 관리하기 위한 객체
cout << sizeof(b1) << endl;
cout << sizeof(b2) << endl;
}
- couple template (stl pair와 유사)
- 템플릿의 인자로 자기 자신의 타인을 전달
- 부분 특수화를 만들 때 템플릿 인자의 개수
- N을 표현하는 방법
#include <iostream>
using namespace std;
template<typename T> void printN(const T& a) { cout << T::N << endl; }
// 임의의 타입 2개를 보관하는 구조체
template<typename T, typename U> struct couple
{
T v1;
U v2;
static constexpr int N = 2;
};
// 2번째 인자가 recursive일때를 위한 부분전문화
template<typename A, typename B, typename C> struct couple<A, couple<B, C>>
{
A v1;
couple<B, C> v2;
static constexpr int N = couple<B, C>::N + 1; // 핵심!
};
template<typename A, typename B, typename C> struct couple<couple<A, B>, C>
{
couple<A, B> v1;
C v2;
static constexpr int N = couple<A, B>::N + 1; // 핵심!
};
template<typename A, typename B, typename C, typename D> struct couple<couple<A, B>, couple<C, D>>
{
couple<A, B> v1;
couple<C, D> v2;
static constexpr int N = couple<A, B>::N + couple<C, D>::N; // 핵심!
};
int main()
{
couple<couple<int, int>, couple<int, int>> d4;
printN(d4); // 4나와야 합니다.
}
- tuple using couple (couple을 활용한 stl 표준의 tuple 구현)
- 무한하게 타입을 넣을 수 있을까? -> C++11 Variadic template
template<typename T, typename U> struct couple
{
T v1;
U v2;
enum { N = 2 };
};
// couple의 선형화 기술
struct Null {}; // empty class(struct), sizeof(Null) : 1
// 아무 멤버도 없지만 분명한 타입이다.
// 1. 함수 오버로딩이나
// 2. 템플릿 인자로 주로 활용
template<typename P1 = Null,
typename P2 = Null,
typename P3 = Null,
typename P4 = Null,
typename P5 = Null> class xtuple : public couple<P1, xtuple<P2, P3, P4, P5, Null>>
{
};
// 2개를 저장하는 xtuple을 위한 부분 전문화
template<typename P1, typename P2>
class xtuple<P1, P2, Null, Null, Null> : public couple<P1, P2>
{
};
int main()
{
// couple<short, long>
// couple<char, xxtuple<s, l, Null, Null, Null>>
// couple<double, xtuple<c, s, l, Null, Null>>
// couple<int, xtuple<d, c, s, l, Null>>
xtuple<int, double, char, short, long> t5; // 상속 받은후 추가한것이 없으므로
// 부모와 모양이 같다. 부모만 알면 이 객체의 모양을
// 알수 있다.
xtuple<int, int, int> t3;
}
template meta programming
컴파일 타임에 연산을 수행하도록 프로그래밍 하는 방법
- constexpr
#include <iostream>
using namespace std;
template<int N> struct check {};
// constexpr 함수 - C++11, 컴파일 타임에 수행이 가능하면 컴파일 타임에 수행, 런타임에 가능하면 런타임에 수행함
constexpr int add(int a, int b)
{
return a + b;
}
int main()
{
int n = add(1, 2);
check< add(1, 2) > c; // ok..
int n1 = 1, n2 = 2;
int c = add(n1, n2); // ok
//check< add(n1, n2) > c; // error
}
- factorial
#include <iostream>
using namespace std;
// template meta programming
template<int N> struct factorial
{
//int value = 10;
//enum { value = N * factorial<N-1>::value };
static constexpr int value = N * factorial<N - 1>::value;
};
// 재귀의 종료를 위해 특수화 문법 사용
template<> struct factorial<1>
{
//enum { value = 1 };
static constexpr int value = 1;
};
int main()
{
int n = factorial<5>::value; // 5 * 4 * 3 * 2 * 1 => 120
// 5 * f<4>::v
// 4 * f<3>::v
// 3 * f<2>::v
// 2 * f<1>::v
// 1
cout << n << endl;
}