C++

10. STL tuple

튀김족발 2023. 3. 27. 20:29

C++ Stl 중에 튜플이 있다. 사용하는데 편한데 어떻게 만들어져있는지 궁금할정도로 신기하다

 

void main()
{
 	std::tuple<float, int> t;
	std::get<0>(t) = 1.2f;
	std::get<1>(t) = 1;

	printf("%g\r\n", std::get<0>(t));
	printf("%i\r\n", std::get<1>(t));
}

출력

1.2
1

이 튜플을 비슷하게 만들어보자

더보기
template<int...> struct seq{};

template<int max, int... s> 
struct make_seq : make_seq<max - 1, max - 1, s...>
{
};

template<int... s> 
struct make_seq<0, s...>
{
	typedef seq<s...> type;
};

template<int max> 
using MakeSeq = typename make_seq<max>::type;

template<int x, typename Arg>
struct foo_storage
{
	Arg data;
};

template<typename Seq, typename... Args>
struct foo_helper{};

template<int s0, int... s, typename A0, typename... Args>
struct foo_helper<seq<s0, s...>, A0, Args...>
	: foo_storage<s0, A0>
	, foo_helper<seq<s...>, Args...>
	{};

template<typename... Args>
struct foo : foo_helper<MakeSeq<sizeof...(Args)>, Args...>
{
};

template<int N, typename T>
T& get(foo_storage<N, T>& f)
{
	return f.data;
}

위에는 전체 소스이고 하나씩 살펴보자

 

template<int...> 
struct seq{};

template<int max, int... s> 
struct make_seq : make_seq<max - 1, max - 1, s...>
{
};

template<int... s> 
struct make_seq<0, s...>
{
	typedef seq<s...> type;
};

template<int max> 
using MakeSeq = typename make_seq<max>::type;

여기에는 가변길이 템플릿을 사용한다.

make_seq는 어떤 max 값을 주면 그 max 값에 해당하는 int의 seq에 해당하는 type을 정의한다. (내가 적어도 뭔말인지 모르겠다 허헣)

이런 식으로 3을 넣으면 0~2까지의 seq를 정의한다.

따라서

이렇게 결과가 나온다.

 

template<int...>
struct seq{};

int형의 가변길이 템플릿을 받는 seq를 정의한다.

 

이 seq는

template<int... s> 
struct make_seq<0, s...>
{
	typedef seq<s...> type;
};

make_seq의 type을 정의한다.

 

struct make_seq : make_seq<max - 1, max - 1, s...>
{
};

그리고 make_seq는 상속 받는 make_seq보다 1 작은 make_seq를 상속받는다.

그리고 종료조건으로 

template<int... s> 
struct make_seq<0, s...>
{
	typedef seq<s...> type;
};

0일경우 마지막 베이스 클래스를 만든다.

 

헷갈린다.

 

이런 식으로 상속을 받게 된다.

따라서 

MakeSeq<2>의 타입은 seq<0,1>이 된다.

 

template<int x, typename Arg>
struct foo_storage
{
	Arg data;
};

여기서 foo_storage를 보면 int형에 대응하는 타입을 갖고 이 타입을 내부에서 타입에 해당하는 멤버변수 데이터를 선언한다. 각 인티저의 시퀀스마다 타입을 갖는다.

 

template<typename Seq, typename... Args>
struct foo_helper{};

가변길이 템플릿 Args를 갖는다. 그리고 특화버전을 작성한다.

template<int s0, int... s, typename A0, typename... Args>
struct foo_helper<seq<s0, s...>, A0, Args...>
	: foo_storage<s0, A0>
	, foo_helper<seq<s...>, Args...>
	{};


특화버전은 int형의 리스트가 n개 오고, 그리고 타입 리스트가 n개 옵니다. 따라서 int형의 리스트가 n개면 타입도 n개가 와야 한다.
이 작성의 의도는 0,1,2가 연속이 되고, 타입을 float, int, long을 작성 한다면 0을 float 1을 int 2를 long에 대응하도록 구성하려는 의도다.

foo_helper의 특화버전이 seq를 파라미터로 받는다면 

struct foo_helper<seq<s0, s...>

이 seq의 s0부터 n-1까지의 시퀀스가 foo_helper의 int형의 seq에 매핑이 되고,

, A0, Args...>

나머지 타입 인자가 타입에 매핑이 된다.

 

: foo_storage<s0, A0>

그리고 상속받기를 foo_helper의 첫번째 int형의 seq를 foo_storage의 첫번째에 넣고 첫번째 자료형을 첫번째 자료형에 넣는다. 이렇게 하면, 그 seq에 해당하는 데이터를 내부에서 선언하는 base클래스를 갖게 된다.

	, foo_helper<seq<s...>, Args...>
	{};

그 다음에 int형의 시퀀스를 계속 받기 위해 재귀적으로 상속을 받는다.

 

이렇게 하면

foo_helper<seq<0,1>,float,int> 를 선언한다면 이런 구조록 만들어진다.

 

template<typename... Args>
struct foo : foo_helper<MakeSeq<sizeof...(Args)>, Args...>
{
};

여기서 보면 MakeSeq<sizeof...(Args)>, Args...>에서 sizeof...(Args)는 가변길이 템플릿에서 템플릿에서 전달된 타입의 갯수를 구하는 연산자이다. 따라서 Args의 갯수를 의미한다.

위에는 궁극적으로는 MakeSeq에 sizeof에 Args를 전달하여 foo<seq<s0. s...> 부분을 자동적으로 생성하게 된다.

 

template<int N, typename T>
T& get(foo_storage<N, T>& f)
{
	return f.data;
}

마지막으로 위에 그림 구조로 만들어지니까 N과 T를 reference로 넘겨주면 접근 하게 된다.

 

void main()
{
	foo<float, int> t;
	get<0>(t) = 1.2f;
	get<1>(t) = 2;
	printf("%g\r\n", get<0>(t));	
	printf("%i\r\n", get<1>(t));	
}

이제 이것들을 실행시키면

 

출력이 된다....
마참내 std에서 제공하는 튜플과 비슷하게 작동하게 만들었다.