最終更新日時:

テンプレートによる多次元配列の表現

はじめに

 多次元配列をテンプレートによりあらわす方法を考える。
 例えば、

int temp[2][3][4]
C++

は静的に宣言される多次元配列であるが、配列の添え字を可変長テンプレートによりあらわすことができれば便利である。

 環境はVisual Studio 2017によるC++14である。

実装

可変長テンプレートによる配列の表現

 安直な発想として、以下のようにすることで実装可能であると考えられる。

namespace iml {
	// インデックスのタプル
	template <class T, T... Indices>
	class index_tuple {};

	// 多次元配列の構築
	template <class T, class Indices>
	struct multi_array_impl;
	template <class T, size_t N>
	struct multi_array_impl<T, index_tuple<N>> {
		using type = T[N];
	};
	template <class T, size_t First, size_t... Indices>
	struct multi_array_impl<T, index_tuple<First, Indices...>> : multi_array_impl<T[First], index_tuple<Indices...>> {};
	template <class T, size_t First, size_t... Indices>
	struct multi_array : multi_array_impl<T, index_tuple<First, Indices...>> {};
	template <class T, size_t... Indices>
	using multi_array_t = typename multi_array<T, Indices...>::type;
}
C++

 しかし、これでは上手く動かない。
 なぜならば、

	typeid(multi_array_t<double, 5, 6, 7>).name();
C++

といったコードでどうなるか実行すると

	double [7][6][5]
C++

といった感じに添え字が逆順になる。言語仕様はよく知らないため何故かはわからない。スタックが関係しているのだろうか。
 実際、remove_extentを用いて

	typeid(remove_extent_t<multi_array_t<double, 5, 6, 7>>).name();
C++

を見れば配列の次数である5が除去されていることがわかる。

index_tupleによるシーケンスを逆順にする

 上記による議論により、index_tupleを逆順にするメタ関数を実装することにより目的を達成できると考えられる。そのためのコードは以下のようになる。

namespace iml {
	// type_tupleを逆順にする
	template <class, class>
	struct reverse_type_tuple_impl;
	template <class... Inv, class First, class... Types>
	struct reverse_type_tuple_impl<type_tuple<Inv...>, type_tuple<First, Types...>>
		: public reverse_type_tuple_impl<type_tuple<First, Inv...>, type_tuple<Types...>> {};
	template <class... Inv>
	struct reverse_type_tuple_impl<type_tuple<Inv...>, type_tuple<>> {
		using type = type_tuple<Inv...>;
	};
	template <class>
	struct reverse_type_tuple;
	template <class... Types>
	struct reverse_type_tuple<type_tuple<Types...>>
		:reverse_type_tuple_impl<type_tuple<>, type_tuple<Types...>> {};
	template <class T>
	using reverse_type_tuple_t = typename reverse_type_tuple<T>::type;
}
C++

 ただ単に2つのindex_tupleを用意して先頭から取り出したものを別のindex_tupleに先頭から入れるだけである。

完成

 reverse_type_tupleを導入することで完成する。

namespace iml {
	// 多次元配列の構築
	template <class, class, size_t>
	struct multi_array_impl;
	template <class T, size_t N, size_t Dim>
	struct multi_array_impl<T, index_tuple<size_t, N>, Dim> {
		using type = T[N];
		static constexpr size_t value = Dim * N;
	};
	template <class T, size_t First, size_t Second, size_t... Indices, size_t Dim>
	struct multi_array_impl<T, index_tuple<size_t, First, Second, Indices...>, Dim> : multi_array_impl<T[First], index_tuple<size_t, Second, Indices...>, Dim * First> {};
	template <class T, size_t... Indices>
	struct multi_array : multi_array_impl<T, reverse_index_tuple_t<index_tuple<size_t, Indices...>>, 1> {};
	template <class T, size_t... Indices>
	using multi_array_t = typename multi_array<T, Indices...>::type;
}
C++

 このようにして、テンプレートによる多次元配列を実装することができた。
 これを用いることで静的なテンソル型の設計や標準ライブラリにおけるstd::arrayの多次元化をすることが可能となる。