C++ Template Programming - Variadic Template[6]
이번 포스팅에서는 C++11 에서 추가된 가변인자 템플릿의 기본 문법과 활용에 대해서 알아보겠습니다.
Variadic template
- 가변 인자 템플릿(클래스, 함수)의 기본 모양
// 가변인자 클래스 템플릿
template<typename ... Types> class tuple
{
};
// 가변인자 함수 템플릿
template<typename ... Types> void foo(Types ... args) // args -> Parameter Pack
{
}
int main()
{
tuple<int> t1; // Types : int
tuple<int, int> t2; // Types : int, int
tuple<int, int, char> t3;
tuple<> t4;
foo(1);
foo(1, 3.4); // Types : int, double args : 1, 3.4 => parameter pack
foo();
}
Parameter pack
가변인자 템플릿에 전달되는 인자를 Parameter pack이라 함
void goo(int n, double d, const char* s)
{
}
// Parameter Pack
template<typename ... Types> void foo(Types ... args)
{
// args : Parameter Pack
cout << sizeof...(args) << endl; // 3
cout << sizeof...(Types) << endl; // 3
//goo(args); // error.
goo(args...); // args... : pack 안의 요소들을 , 를 사용해서 나열해 달라.
// goo( 1, 3.4, "aaa")
// args... : Pack Expansion
}
int main()
{
foo();
foo(1);
foo(1, 3.4, "aaa"); // Types : int, double, const char*
// args : 1, 3.4, "aaa"
}
Pack expansion
-
Parameter Pack을 사용하는 패턴 -> 패턴1, 패턴2, 패턴3 …
-
Pack Expansion은 함수 호출의 인자 또는 list 초기화를 사용한 표현식에만 사용 할 수 있음
#include <iostream>
using namespace std;
void goo(int a, int b, int c)
{
cout << a << ", " << b << ", " << c << endl;
}
int hoo(int a) { return -a; }
// Pack Expansion
template<typename ... Types> void foo(Types ... args) // args : 1,2,3
{
//int ar[] = { args... }; // 1, 2, 3
//int ar[] = { (++args)... }; // ++E1, ++E2, ++E3, ++1, ++2, ++3
//int ar[] = { hoo(args...) }; // hoo(1,2,3) error
int ar[] = { hoo(args)... }; // { hoo(1), hoo(2), hoo(3) }
goo(args...); // goo( 1, 2, 3)
goo(hoo(args)...); // goo( hoo(1), hoo(2), hoo(3))
goo(hoo(args...)); // goo( hoo( 1,2,3)) // error
for (int n : ar)
cout << n << endl;
}
int main()
{
foo(1, 2, 3);
}
- pair, tuple 관련 Pack Expansion 활용
#include <iostream>
#include <tuple>
using namespace std;
// Type Expansion
template<typename ... Types> void foo()
{
// Types : int, char
pair<Types...> p1; // pair<int, char> ok
tuple<Types...> t1; // tuple<int, char> ok
tuple<pair<Types...>> t2; // tuple<pair<int, char>>
//pair<tuple<Types...>> p2; // pair< tuple<int, char> > error
pair<tuple<Types>...> p3; // pair< tuple<int>, tuple<char>> ok
//tuple<pair<Types>...> t3; // tuple< pair<int>, pair<char>> error
tuple<pair<int, Types>...> t4; // tuple< pair<int, int>, pair<int, char>> ok..
}
int main()
{
foo<int, char>();
}
Paramter pack의 각 요소 접근 방법
-
Pack Exapnsion : 배열 또는 Tuple에 담아서 접근
-
using recursive : 첫 번째 인자를 이름 있는 변수로 받아 재귀표현으로 처리
void foo() {} // 재귀 종료 위해
template<typename T, typename ... Types>
void foo(T value, Types ... args)
{
count << value << endl;
foo(args...); // foo( 3.4, "AA")
// foo("AA")
// foo()
}
int main()
{
foo(1, 3.4, "AA"); // value : 1, args : 3.4, "AA"
}
-
fold expression 사용 ( C++ 17 )
-
이항 연산자를 사용해서 parameter pack 안에 있는 요소에 연산을 수행하는 문법
#include <iostream> using namespace std; template<typename ... Types> void foo(Types ... args) { int x[] = { args... }; // pack expansion 사용 int n = (args + ...); // fold expression // E1 op ( E2 op ( E3 op ( E4 op E5) ) ) // 1 + (2 + (3 + (4 + 5))) cout << n << endl; } int main() { foo(1, 2, 3, 4, 5); }
-
4가지 형태 존재 (args와 … 의 위치에 따라)
#include <iostream> using namespace std; template<typename ... Types> void foo(Types ... args) { int n1 = (args + ...); // (1 + (2 + (3 + (4 + 5)))) int n2 = (... + args); // ((((1 + 2) + 3) + 4) + 5) int n3 = (args + ... + 10); // (1 + (2 + (3 + (4 + (5 + 10))))) int n4 = (10 + ... + args); // (((( 10 + 1) + 2) + 3) + 4) + 5) cout << n1 << endl; cout << n2 << endl; cout << n3 << endl; cout << n4 << endl; } int main() { foo(1, 2, 3, 4, 5); }
-
활용 패턴
#include <iostream> #include <vector> using namespace std; vector<int> v; template<typename ... Types> void foo(Types ... args) { int n1 = (args + ...); // (1 + (2 + 3) ) (v.push_back(args), ...); // ( v.push_back(1), (v.push_back(2), v.push_back(3))) for (auto n : v) cout << n << endl; } int main() { foo(1, 2, 3); }
-
함수 리턴타입 구하기
#include <iostream>
using namespace std;
int f(int a, double b) { return 0; }
// 가변 인자 템플릿 기술을 사용해서 함수의 리턴 타입을 구하는 코드 입니다.
template<typename T> struct result
{
typedef T type;
};
template<typename R, typename ... Types> struct result<R(Types...)>
{
typedef R type;
};
template<typename T> void foo(T& a) // 여기서 T는 int(int, double) 타입입니다.
{
typename result<T>::type n;
cout << typeid(n).name() << endl; // int 가 나와야 합니다.
}
int main()
{
foo(f);
}
가변인자 템플릿과 상속을 활용한 Tuple 구현
#include <iostream>
using namespace std;
// xtuple
template<typename ... Types> struct xtuple
{
static constexpr int N = 0;
};
template<typename T, typename ... Types>
struct xtuple<T, Types...> : public xtuple<Types...>
{
T value;
xtuple() = default;
xtuple(const T& v, const Types& ... args) : value(v), xtuple<Types...>(args...) {}
static constexpr int N = xtuple<Types...>::N + 1;
};
// xtuple_element_type
template<int N, typename T> struct xtuple_element_type;
template<typename T, typename ... Types>
struct xtuple_element_type<0, xtuple<T, Types...>>
{
typedef T type;
typedef xtuple<T, Types...> tupleType;
};
template<int N, typename T, typename ... Types>
struct xtuple_element_type<N, xtuple<T, Types...>>
{
typedef typename xtuple_element_type<N - 1, xtuple<Types...>>::type type;
typedef typename xtuple_element_type<N - 1, xtuple<Types...>>::tupleType tupleType;
};
// get
template<int N, typename T> typename xtuple_element_type<N, T>::type& xget(T& tp)
{
return static_cast<typename xtuple_element_type<N, T>::tupleType&>(tp).value;
}
int main()
{
xtuple<int, double, char> t3(1, 3.4, 'A');
xget<0>(t3) = 10;
cout << xget<0>(t3) << endl; // 10
cout << xget<1>(t3) << endl; // 3.4
cout << xget<2>(t3) << endl; // 'A'
}
Tuple의 모든 요소 출력하기
- std tuple의 index 자리에는 변수를 사용 할 수 없으므로 loop 순회가 불가능
- std의 index_squence를 활용하면 모든 요소를 순회 하는 표현이 가능
#include <iostream>
#include <tuple>
using namespace std;
// template<size_t ... N> struct index_sequence {};
template<typename TP, size_t ... I >
void print_tuple_imp(const TP& tp, const index_sequence<I...>& ) // I : 0, 1, 2
{
int x[] = { get<I>(tp)... }; // get<0>(tp), get<1>(tp), get<2>(tp)
for (auto& n : x)
cout << n << ", ";
}
template<typename TP> void print_tuple(const TP& tp)
{
print_tuple_imp(tp, make_index_sequence<tuple_size<TP>::value>());
}
int main()
{
tuple<int, int, int> tp(1, 2, 3);
print_tuple(tp);
}