Edited at

C++で多次元配列の表現

More than 1 year has passed since last update.


自己紹介

初めまして。趣味で数学とプログラムをやっていて、数値解析のライブラリを作っています。

機会があったので投稿をしてみようかと思いました。


概要

ライブラリの作成のコンセプトは標準ライブラリに頼らないC++の標準機能のみで数値解析をするといったものです。例外的にストリーム入出力とかは使ったりしますが・・・

現在はより一般的なテンソルを扱うことができるクラスを考えていたりるんですけど、静的に多次元配列のいい感じの扱い方が思いついたんでテンソル型構築の鍵になるかなーなんて思っていたりします。

趣味で教育とかを受けたわけじゃないんでプログラムの常識とか全く知らないんで変なところがあれば教えていただけると勉強になります。


テンプレートによる配列の扱い

とりあえず、直感的に多次元配列の型はこんな感じで取得できるかなーと思いました。

namespace iml {

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

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

これで試しに

multi_array<double, 5, 6, 7>::type;

といったコードでtypeがどうなるかを観察してみると

double [7][6][5]

とかという何か逆順になっていました。

ちょっとよくわからなかったので試しに

namespace iml {

//配列から次数の削除
template<class T>
struct remove_extent {
using type = T;
};
template<class T, size_t N>
struct remove_extent<T[N]> {
using type = T;
};
template<class T>
struct remove_extent<T[]> {
using type = T;
};
}

int main() {
double temp[5][6][7];
iml::remove_extent<decltype(temp)>::type;
return 0;
}

を見てみましたが、配列の次数の5が除去されていましたので、テンプレートによる配列は変数宣言との順と逆順になるみたいです。仕様についてはよくわかりませんが、スタックが関係したりしているんでしょうかね。


index_tupleを逆順にする

というわけでindex_tupleを逆順にしていこうかと思いますが、そこまで難しくありません。

namespace iml {

//index_tupleを逆順にする
template <class T, class S>
struct _Reverse_index_tuple;
template <size_t... Inv, size_t First, size_t... Indices>
struct _Reverse_index_tuple<index_tuple<Inv...>, index_tuple<First, Indices...>>
: public _Reverse_index_tuple<index_tuple<First, Inv...>, index_tuple<Indices...>> {};
template <size_t... Inv>
struct _Reverse_index_tuple<index_tuple<Inv...>, index_tuple<>> {
using type = index_tuple<Inv...>;
};
template <class T>
struct reverse_index_tuple;
template <size_t... Indices>
struct reverse_index_tuple<index_tuple<Indices...>> {
using type = typename _Reverse_index_tuple<index_tuple<>, index_tuple<Indices...>>::type;
};
}

ただ単に2つのindex_tupleを用意して先頭から取り出したものを別のindex_tupleに先頭から入れるだけですね。ちょうど∞を書く感覚に近いかと。


完成!

この逆順にするものを導入することで完成です。

//多次元配列の構築

template <class T, class Indices>
struct _Multi_array;
template <class T, size_t N>
struct _Multi_array<T, iml::index_tuple<N>> {
using type = T[N];
};
template <class T, size_t First, size_t... Indices>
struct _Multi_array<T, iml::index_tuple<First, Indices...>> : _Multi_array<T[First], iml::index_tuple<Indices...>> {};
template <class T, size_t First, size_t... Indices>
struct multi_array : _Multi_array<T,typename iml::reverse_index_tuple<iml::index_tuple<First, Indices...>>::type> {};

int main() {
//double temp[5][6][7];と同じ
multi_array<double, 5, 6, 7>::type temp;
return 0;
}

とまぁこんな感じで比較的簡単に実装できますが、突如頭に浮かんだので作ってみました。このクラスを用いることによる利点は、ただの多次元配列のためメモリが連続して分布していることです。多分これを使えばstd::arrayとかいい感じに拡張ができるのではないでしょうか。

ちなみにimlとかというのはnamespaceは自分のライブラリが構築されている名前空間です。

多分これでテンソル作成に一歩近づいた気がします。