しるふぃずむ

どうもプログラマです。好きなものはC#とジンギスカンです。嫌いなものはJava。プログラムおもちろいね。

std::tuple同士の加算

いちいち要素ごとに足すのが面倒です.
要素ごとにそれぞれのoperator+に委譲する形で実装しようとしたんですが,
MSVCには未だにVariadic Templateがなくstd::tupleの実装がエミュレーションでしかないので
これまた面倒な事に.
Boost.PPを使って無理やり実装しました.
MSVC11(VS2012RC)

	//括弧外し用
#define UNPAREN(...)__VA_ARGS__
	//タプルの最大数の設定がよくわからない
#define TUPLE_SIZE BOOST_PP_ADD(_VARIADIC_MAX, 3)

	// typename Txを生成
#define TYPENAME(x, i, name) (typename name##i)
	// ↑をコンマ連結
#define TEMPLATE_LIST \
	BOOST_PP_LIST_REST ( \
	BOOST_PP_SEQ_TO_ARRAY( \
	BOOST_PP_REPEAT(TUPLE_SIZE, TYPENAME, T) \
	) \
	)
	// template宣言
#define TUPLE_TEMPLATE_DECLARE template <BOOST_PP_EXPAND(UNPAREN TEMPLATE_LIST)>

	// Tx
#define TEMPLATE_PARAM(x, i, name) (name##i)
	// ↑をコンマ連結
#define TYPELIST \
	BOOST_PP_LIST_REST ( \
	BOOST_PP_SEQ_TO_ARRAY( \
	BOOST_PP_REPEAT(TUPLE_SIZE, TEMPLATE_PARAM, T) \
	) \
	)
	// タプル型名
#define TUPLE_TYPE std::tuple<BOOST_PP_EXPAND(UNPAREN TYPELIST)>

	namespace detail
	{

		template<int size, typename T>
		/** 
		 * operator+=の実装部分 テンプレート関数にはパラメータを明示的に渡せないので 
		 * 関数オブジェクトを定義して特殊化する
		 */
		struct PlusEqImpl
		{
			T& operator()(T&, const T&);
		};

		// operator+=を呼ぶ
#define ELEM_PLUS_EQ(x, i, name) \
	std::get<i>(lhs) += std::get<i>(rhs);
		// タプルサイズに応じて特殊化する
#define PLUS_EQ_IMPL(x, i, name) \
		template<typename T> \
		struct PlusEqImpl<i, T> \
		{ \
			T& operator()(T& lhs, const T& rhs) \
			{ \
				BOOST_PP_REPEAT(i, ELEM_PLUS_EQ, ) \
				return lhs; \
			} \
		}; 

		BOOST_PP_REPEAT(BOOST_PP_ADD(TUPLE_SIZE, 1), PLUS_EQ_IMPL, )

#undef ELEM_PLUS_EQ
#undef PLUS_EQ_IMPL

	} // namespace detail

	TUPLE_TEMPLATE_DECLARE
		TUPLE_TYPE &
		/** 同じ型のタプルに対してoperator+=を定義 */
		operator+=(TUPLE_TYPE & lhs, const TUPLE_TYPE & rhs)
	{
		return detail::PlusEqImpl<std::tuple_size<TUPLE_TYPE>::value, TUPLE_TYPE>()(lhs, rhs);
	}
#undef OP_PLUTEQUAL

	TUPLE_TEMPLATE_DECLARE
		TUPLE_TYPE
		/** operator+=からoperator+を定義 */
		operator+(const TUPLE_TYPE & lhs, const TUPLE_TYPE & rhs)
	{
		return TUPLE_TYPE(lhs)+=rhs;
	}

#undef UNPAREN

#undef TYPENAME
#undef TEMPLATE_LIST 
#undef TUPLE_TEMPLATE_DECLARE 
#undef TEMPLATE_PARAM
#undef TYPELIST 
#undef TUPLE_TYPE 

直感的に扱えて満足.

auto x = std::make_tuple(1, 0.5, std::string("abc"));
auto y = std::make_tuple(2, 1.5, std::string("xyz"));

auto z = x + y;
Assert::AreEqual(std::get<0>(z), 3);
Assert::AreEqual(std::get<1>(z), 2.0, 0.001);
Assert::AreEqual(std::get<2>(z), std::string("abcxyz"));

タプルの型引数が違うとコンパイルエラーです.
多分それが望ましい動作…と思ったけれど,+=が使えるなら通ったほうがいいんですかね.
気が向いたらいじります.
しかし,Variadic Templateが使えればかなり楽に書けるんでしょうねえ.