はじめに
前回の記事では,多次元の std::vector
について書いた.
今回は多次元の std::array
について書こうと思う.
まず,std::array
は組み込み配列と同等の機能を提供するクラスである(というより,組み込み配列のラッパークラスである).
使用方法としては std::array<int, 4> arr
のように,第1テンプレート引数に要素型,第2テンプレート引数に要素数を渡す.
std::array<int, 4> arr
は int arr[4]
に相当する宣言となる.
しかし,std::array
を多次元にする場合を考えると,
std::array<std::array<std::array<int, 30>, 20>, 10> arr;
と宣言が長い宣言が必要になる.
また,上記の3次元の std::array
と同等の組み込み配列は int arr[10][20][30]
であり,要素数の順が逆になっている.
この記事では,これらの問題を解決することについて書く.
実装
以下のコードがこの記事に書きたいことの全てである.
#include <algorithm> #include <array> #include <iostream> #include <iterator> #include <type_traits> #include <utility> struct is_range_impl { template<typename T> static auto check(T&& obj) -> decltype(std::begin(obj), std::end(obj), std::true_type{}); template<typename T> static auto check(...) -> std::false_type; }; // struct is_range_impl template<typename T> class is_range : public decltype(is_range_impl::check<T>(std::declval<T>())) {}; // class is_range template< typename R, typename T, typename std::enable_if< is_range<R>::value && !is_range<typename std::iterator_traits<decltype(std::begin(std::declval<R>()))>::value_type>::value, std::nullptr_t >::type = nullptr > static inline void fill(R&& range, T&& value) noexcept { std::fill(std::begin(range), std::end(range), std::forward<T>(value)); } template< typename R, typename T, typename std::enable_if< is_range<R>::value && is_range<typename std::iterator_traits<decltype(std::begin(std::declval<R>()))>::value_type>::value, std::nullptr_t >::type = nullptr > static inline void fill(R&& range, T&& value) noexcept { for (auto&& e : range) { fill(std::forward<decltype(e)>(e), std::forward<T>(value)); } } template< typename T, std::size_t kN, std::size_t... kSizes > struct ndarray_impl { using type = std::array<typename ndarray_impl<T, kSizes...>::type, kN>; }; // struct ndarray_impl template< typename T, std::size_t kN > struct ndarray_impl<T, kN> { using type = std::array<T, kN>; }; // struct ndarray_impl template<typename T, std::size_t kN, std::size_t... kSizes> using NdArray = typename ndarray_impl<T, kN, kSizes...>::type; int main() { NdArray<int, 4> arr1; fill(arr1, 0); for (const auto& e : arr1) { std::cout << e << " "; } std::cout << "\n\n"; NdArray<int, 4, 4> arr2; fill(arr2, -1); for (const auto& e1 : arr2) { for (const auto& e2 : e1) { std::cout << e2 << " "; } std::cout << "\n"; } std::cout << "\n"; NdArray<int, 4, 4, 4> arr3; fill(arr3, 114514); for (const auto& e1 : arr3) { for (const auto& e2 : e1) { for (const auto& e3 : e2) { std::cout << e3 << " "; } std::cout << "\n"; } std::cout << "\n"; } std::cout << "\n"; return 0; }
std::array
はテンプレート引数に要素数が必要なので,前回の記事の NdVector
とは異なり, NdArray<int, 10, 20, 30>
とする必要がある.
NdVector<int, 10, 20, 30> arr;
は int arr[10][20][30];
と同等であり,組み込み配列と遜色無い使用感になっていると思う.
なお,組み込み配列と同様に std::array
生成時に要素は初期化されないので,ローカル変数で使う分には初期化が必要となる.
std::array
は組み込み配列のみをメンバー変数に持つラッパークラスであり,全次元を通して領域が連続していることを利用して,
NdAarray<int, 3, 4, 5> arr; std::fill( reinterpret_cast<int*>(arr.data()), reinterpret_cast<int*>(arr.data() + arr.size()), 0);
のようにしてしまうのもアリだが,お行儀が悪いので,ちゃんとRange-based forでループを行って初期値を入れる関数 fill()
を用意した.
なお,この fill()
は他の多次元のものに対しても利用可能であるので,例えば多次元の組み込み配列にも適用可能である.
int arr[3][4][5][6]; fill(arr, 0);
当然,前回の記事の NdVector
にも適用可能である.
(各関数,クラスの実装は前回の記事を参照)
auto nv = makeNdVector(3, 4, 5, -1); std::cout << "nv =\n["; for (const auto& e1 : arr3) { std::cout << " [\n"; for (const auto& e2 : e1) { std::cout << " ["; for (const auto& e3 : e2) { std::cout << e3 << ", "; } std::cout << "],\n"; } std::cout << " ],\n"; } std::cout << "]\n"; fill(nv, 0); std::cout << "nv =\n["; for (const auto& e1 : arr3) { std::cout << " [\n"; for (const auto& e2 : e1) { std::cout << " ["; for (const auto& e3 : e2) { std::cout << e3 << ", "; } std::cout << "],\n"; } std::cout << " ],\n"; } std::cout << "]\n";
他にも多次元の std::list
にも fill()
を適用することが可能である.
別実装
いなむ神に以下のような実装を教えていただいた.
#include <array> template<typename T, std::size_t N, std::size_t... Extents> struct extents_expander : extents_expander<std::array<T, N>, Extents...> {}; // struct extents_expander template<typename T, std::size_t N> struct extents_expander<T, N> { using type = std::array<T, N>; }; // struct extents_expander template<typename T, std::size_t... Extents> struct ndarray_helper { using type = typename extents_expander<T, Extents...>::type; }; // struct ndarray_helper template<typename T, std::size_t N, std::size_t... Extents> struct ndarray_helper<T[N], Extents...> { using type = typename ndarray_helper<T, N, Extents...>::type; }; // struct ndarray_helper template<typename T> using NdArray = typename ndarray_helper<T>::type;
これは以下のように使用できる.
NdArray<int[10][20][30]> arr;
こちらの方が直感的であると感じた.
まとめ
多次元 std::array
の型の記述を簡単にするエイリアステンプレートの実装と,初期化関数を紹介した.
工夫すれば,多次元の std::array
は多次元の組み込み配列と同じぐらい容易に扱うことが可能になる.