이제 템플릿 이야기의 마지막으로 템플릿 리스트 기법과 C++11의 Variadic template을 잠시 이야기 하겠다. 사실 템플릿 리스트 기법과 Variadic template이 필요한 거의 모든 경우는 이미 구현되어 있다. 그래서 지금 이야기 하는 것을 굳이 이해할 필요는 없고, 이미 구현된 것들의 사용방법만 숙지하여도 충분하다.
첫번째로 템플릿 리스트를 이야기 해보자.
일반적으로 리스트는 이미 만들어진 데이터를 보관하기 위한 용도로 사용된다. 템플릿 리스트는 비슷한 의미로 객체 형을 보관할 수 있다. 앞서 이야기한 function, bind는 C++11이전에는 템플릿 리스트를 사용하여 구현한다.
template <class T, class U>
struct type_list
{
typedef T head;
typedef U tail;
};
struct null_type
{
};
단순한 템플릿이다. 두 가지 타입을 입력 받아 각각의 타입을 head, tail이라는 이름으로 재선언 한다. null_type은 아무 것도 없는 빈 클래스다. 리스트의 끝을 표현하기 위해 사용했다.
typedef type_list<int, type_list<short, type_list<double, null_type>>> type_list_sample;
위의 예시처럼, type_list를 재귀적으로 사용 가능하다. 위의 형은 컴파일 시점에 해석되어지기 때문에 런타임에는 아무런 영향도 미치지 않는다. 형은 형일 뿐이다. 그럼 위의 type_list_sample 따위의 형은 어떻게 사용할까?
template<class TLIST> struct type_group;
template<class T>
struct type_group<type_list<T, null_type>>
{
T value;
};
template<class T, class TLIST>
struct type_group<type_list<T, TLIST>> : public type_group<TLIST>
{
T value;
};
type_group<type_list_sample> instance;
위 구문은 type_list_sample의 실제 객체를 만드는 예제다. 템플릿의 재귀적인 전개를 이용하여 반복적으로 상속하고 최종적으로 원하는 객체를 만들어 냈다.
그런데 객체에 값을 대입하거나, 객체의 값을 읽는 행위는 어떻게 할까? 그것을 위해 몇가지 템플릿을 추가로 정의한다.
template <class TYPE_LIST, class T>
struct index_of;
template <class T>
struct index_of<null_type, T>
{
enum {index = -1};
};
template <class T, class TAIL>
struct index_of<type_list<T, TAIL>, T>
{
enum {index=0};
};
template <class HEAD, class TAIL, class T>
struct index_of<type_list<HEAD, TAIL>, T>
{
private: enum {temp = index_of<TAIL, T>::index};
public: enum {index = temp == -1? -1:1+temp};
};
조금은 복잡하지만 끊기있게 해석하기 바란다.
index_of의 일반형을 선언했지만, 정의는 없다. 아래의 명시적으로 정의된 형이 모든 형을 커버하기 때문에 굳이 구체적으로 정의할 필요는 없다.
index_of 템플릿은 아래 처럼 사용한다.
const int index_position = index_of<type_list_sample, int> ::index;
위의 예제로 type_list의 활용 방법을 보자. type_list_sample은 앞서 정의한 것 처럼, int, short, double을 그 멤버로 가지는 리스트다.
index_of의 템플릿 파라미터가 type_list와 int이기 때문에 3개의 명시적 정의 중, 2번째가 선택된다.
type_list_sample가
type_list<int, type_list<short, type_list<double, null_type>>> 임으로 index_of<
type_list_sample, int> 형태는 아래 2개와 비교하면, 명확할 것이다.
struct index_of<null_type, T>
struct index_of<type_list<T, TAIL>, T>
struct index_of<type_list<HEAD, TAIL>, T>
그러므로 index_position은 0이다.
index_of<type_list_sample, char> ::index 은 어떨까?
1. 첫 번째, 두 번째는 만족하지 않기 때문에 자동적으로 세 번째가 선택될 것이다.
HEAD = int
TAIL = type_list<short, type_list<double, null_type>>
T = char
2. index_of<type_list<short, type_list<double, null_type>, char
>::index 역시 세 번째다
HEAD = short
TAIL = type_list<double, null_type>
T = char
3. index_of<type_list<double, null_type>, char>::index는 세 번째다
4. index_of<null_type, char>은 첫 번째다.
결국 index_position은 -1이된다.
한번 더 해보자
const int index_position = index_of<type_list_sample, double> ::index;
은 위의 3번이 두 번째가 선택된다.
index_of<type_list<double, null_type>, double>::index
그러므로 index_position = 2이다.
비슷하게 다음 템플릿도 정의할 수 있다.
template <class TYPELIST, int index>
struct type_at;
template <class HEADER, class TAIL>
struct type_at<type_list<H, T>, 0>
{
typedef H type;
};
template <class HEADER, class TAIL, int i>
struct type_at<type_list<HEADER, TAIL>, i>
{
typedef typename type_at<TAIL, i-1>::type type;
};
사용 예는 다음과 같다.
typedef type_at<type_list_sample, 2>::type second_type;
간단한 사용 예제를 보자.
template<class TLIST> struct type_group;
template<class T>
struct type_group<type_list<T, null_type>>
{
T value;
template<int N> void* base_ptr()
{
return (void*)&value;
}
template<int N> T& get()
{
return value;
}
};
template<class T, class TLIST>
struct type_group<type_list<T, TLIST>> : public type_group<TLIST>
{
T value;
template<int N> void* base_ptr()
{
return type_group<TLIST>::base_ptr<N-1>();
}
template<> void* base_ptr<0>()
{
return (void*)&value;
}
template<int N> inline typename type_at<type_list<T, TLIST>, N>::type& get()
{
unsigned char* p = base_ptr<N>();
return *(type_at<type_list<T, TLIST>, N>::type*)p;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
type_group<type_list<int, type_list<short, type_list<double, null_type>>>> group_instance;
group_instance.get<0>() = 1;
group_instance.get<1>() = 2;
group_instance.get<2>() = 100.0;
}
위의 예제는 std::tuple을 흉내내어, 심플하게 구현한 것이다.
이런 type_list는, Variadic template이 없다면 앞서 이야기한 std::function이나 std::bind 등을 구현하기 위해서 필수적으로 사용되어야 한다.
두번째로, Variadic template을 이야기하자.
Variadic template은 템플릿 파라미터 숫자를 정하지 않은 상태로 받아 들이 수 있다. type_list의 확장 버전인 셈이다. 여기서는 소개하지 않겠지만, C++11의 Rvalue-reference와 결합하면 Perfect-forwarding이 가능해져 더욱 강력하다.
위의 예제들을 그대로 만들어 보겠다.
template<class ...> struct type_check;
template <class T, class TLIST> struct index_of2
{
enum{ value = -1 };
};
template <class T, class ...TLIST> struct index_of2<T, type_check<T, TLIST...>>
{
enum { value = 0 };
};
template <class T, class U, class ...TLIST> struct index_of2<T, type_check<U, TLIST...>>
{
enum { value = index_of2<U, type_check<TLIST...>>::value == -1 ? -1 : 1 + index_of2<U, type_check<TLIST...>>::value };
};
template <class type_check, int N> struct type_at2;
template <class T, class ...TLIST>
struct type_at2<type_check<T, TLIST...>, 0>
{
typedef T type;
};
template <class T, class ...TLIST, int N>
struct type_at2<type_check<T, TLIST...>, N>
{
typedef typename type_at2<type_check<TLIST...>, N-1>::type type;
};
type_check만 제대로 이해하면 type_list와 똑같다.
type_check은 N개의 임의의 템플릿 파라미터에서 가장 앞의 템플릿 파라미터를 분리 할 수 있게 해준다. 즉, type_check<...>와 type_check<T, ...>은 같은 형이기 때문에 T에 대하여 원하는 검사를 수행 할 수 있다.
사용 방법은 다음과 같다.
template<class ...T>
struct test
{
const int n = index_of2<int, type_check<T...>>::value;
const int total_size = sizeof...(T);
typedef typename type_at2<type_check<T...>, 2>::type type;
};
int _tmain(int argc, _TCHAR* argv[])
{
test<int, short, long> t;
test<int, short, long>::type d = 100.0;
printf("%d, %d", t.n, t.total_size);
return 0;
}
이해하지 않아도 괜찮다. 앞서 언급했지만 응용해서 만들 수 있는 거의 대부분이 이미 STL에 구현되어 있다. 사용 방법만 익히면 된다. type_check은 std::tuple 로 이미 선언되어 있고, type_at2는 std::tuple_element로 구현되어 있다. 다만 왠 일인지 index_of2는 구현되어 있지 않다. (혹은 내가 모르는 것일 수도 있다.)
sizeof...(T) 는 전체 템플릿 파라미터의 갯수를 구할 수 있는 신규 문법이다.
Variadic template은 vs2013이후 기본 지원하나, vs2012는
http://www.microsoft.com/en-us/download/details.aspx?id=35515 에서 다운 받은 후 추가 설치해야 하고 vs2010 이하는 지원하지 않는다.