日記的「駄目」プログラミング Yet Another Hacking Life

std::tupleと構造体の相互変換(任意の型でのget<i>の実現)

08.23.2011 · Posted in C++, C++11, プログラミング

メタプログラミングの練習をかねて。

既存の構造体とstd::tupleを非侵襲的に相互変換するスマートな方法を考えてみる。ちなみにboostは使わない派。

とりあえずコード。

#include <iostream>
#include <tuple>
#include <typeinfo>

// 構造体をget<>に対応させるための準備

template<typename T0, typename H0, typename ...Args0>
struct type_array
{
	template<unsigned i, typename T, typename S>
	struct zero_type
	{
		typedef S type;
	};

	template<typename T, typename S>
	struct zero_type<0, T, S>
	{
		typedef T type;
	};

	template<unsigned i, typename... Args1>
	struct get_type_sub;

	template<unsigned i>
	struct get_type_sub<i>{};

	template<unsigned i, typename T1, typename... Args1>
	struct get_type_sub<i, T1, Args1...>
	{
		typedef typename zero_type< !(i == 0), T1,
			typename get_type_sub< (i == 0 ? 0 : i - 1), Args1...>::type >::type type;
	};

	template<typename T1>
	struct get_type_sub<0, T1>
	{
		typedef T1 type;
	};

	template<unsigned i>
	struct get_type
	{
		typedef typename get_type_sub<i, H0, Args0...>::type type;
	};
};

template<typename T, unsigned i>
struct get_type
{
	typedef typename T::template get_type<i>::type type;
};

template<typename... Args>
int size()
{
	return sizeof...(Args);
}

template<typename T> struct type_array_real;

template<unsigned i, typename T>
auto get(const T&) ->
	typename type_array_real<T>::template get_type<i>::type;

template<unsigned i,typename T, typename U, unsigned j = (i - 1) ? i - 1: 1>
inline T& copy_dummy(T& t, U& u)
{
	get<i-1>(t) = get<i-1>(u);
	return (i - 1) ? copy_dummy<j>(t, u) : t;
}

template<typename T, typename U>
inline T& copy(T& left, U& right)
{
	return copy_dummy<std::tuple_size<T>::value>(left, right);
}

//-----------------------------------------------------------

// 既存の構造体
struct MyStruct
{
	int valA;
	double valB;
	float valC;
};

// 既存の構造体をget<>に対応させるコード ここから ->
typedef type_array<MyStruct, int, double, float> MyStructTypes;

template<>
struct type_array_real<MyStruct> : public MyStructTypes
{
};

template<>
auto get<0>(const MyStruct& t) -> MyStructTypes::get_type<0>::type
{
	return t.valA;
}

template<>
auto get<1>(const MyStruct& t) -> MyStructTypes::get_type<1>::type
{
	return t.valB;
}

template<>
auto get<2>(const MyStruct& t) -> MyStructTypes::get_type<2>::type
{
	return t.valC;
}

// 既存の構造体をget<>に対応させるコード <- ここまで

//-----------------------------------------------------------

// 実験

int
main(int argc, char* argv[])
{
	using namespace std;

	MyStruct s = {3, 3.1415, 1.414};
	cout << get<0>(s) << endl;
	cout << get<1>(s) << endl;
	cout << get<2>(s) << endl;

	cout << "--" << endl;

	cout << typeid(type_array_real<MyStruct>::get_type<0>::type).name() << endl;
	cout << typeid(type_array_real<MyStruct>::get_type<1>::type).name() << endl;
	cout << typeid(type_array_real<MyStruct>::get_type<2>::type).name() << endl;

	cout << "--" << endl;

	tuple<int, double, float> tup;
	copy(tup, s);
	cout << get<0>(tup) << endl;
	cout << get<1>(tup) << endl;
	cout << get<2>(tup) << endl;

	return 0;
}

問題は、get<i>の戻り値を静的に多態させるために、整数を受け取って型を返す、型の配列を作らなければならなかったこと。それさえできればあとは構造体を受け取って任意の型を返すget<i>を特殊化すればいい。rightのget<i>をleftのget<i>に移す、汎用のcopyを作っておけばget<i>に対応した型はすべて移せる(メンバの数さえ合っていれば)。

moveについてはもうちょっと考える必要があし、constあたりも勉強が足りないとは思う。

テンプレートのパラメータと構造体の並びはカスタマーが決定できるのでそれなりに柔軟。get<i>(tuple<…>)がメンバでないのは、こういう互換を考えたからなんだろうか。

まぁ、実際のところ都度コピー関数を書いた方が手っ取り早いかもしれない。

メタプログラミングはまだはじめたばかりだけど、テクニック的にパズルのように考えるところがたくさんあって面白い。整数を一つだけ取る関数テンプレートの再帰の終了は特殊化で簡単に実現できるけど、整数と複数の型を取る場合、部分特殊化ができない縛りがあるので悩んだ。
あとC++0xになってずいぶん楽になってると思う(テンプレートパラメータのデフォルトパラメータとか)。

Leave a Reply