はじめに
C++には std::vector
という動的配列を扱うクラスが用意されている.
このクラスはよく利用され,時には std::vector
の std::vector
,すなわち多次元の std::vecotr
が用いられることがある.
しかし,この多次元の std::vector
は
std::vector<std::vector<int>> vct2d;
のように長い型宣言を伴うのと,初期サイズを指定する場合,
std::vector<std::vector<int>> vct2d(5, std::vector<int>(10));
のような記述となり,なかなかに煩雑である.
この記事では,上記のような多次元 std::vector
をある程度楽に扱えるようにすることについて記述する.
多次元の std::vector
を生成する関数
まずは実装を紹介する.
#include <vector> #include <type_traits> #include <utility> template< typename T, typename U > static inline std::vector<U> makeNdVector(T&& n, U&& val) noexcept { static_assert(std::is_integral<T>::value, "[makeNdVector] 1st argument must be an integer type"); return std::vector<U>(std::forward<T>(n), std::forward<U>(val)); } #if __cplusplus >= 201402L template< typename T, typename... Args > static inline decltype(auto) makeNdVector(T&& n, Args&&... args) noexcept { static_assert(std::is_integral<T>::value, "[makeNdVector] 1st argument must be an integer type"); return std::vector<decltype(makeNdVector(std::forward<Args>(args)...))>(std::forward<T>(n), makeNdVector(std::forward<Args>(args)...)); } #else template< typename T, typename... Args > static inline auto makeNdVector(T&& n, Args&&... args) noexcept -> decltype(std::vector<decltype(makeNdVector(std::forward<Args>(args)...))>(std::forward<T>(n), makeNdVector(std::forward<Args>(args)...))) { static_assert(std::is_integral<T>::value, "[makeNdVector] 1st argument must be an integer type"); return std::vector<decltype(makeNdVector(std::forward<Args>(args)...))>(std::forward<T>(n), makeNdVector(std::forward<Args>(args)...)); } #endif #include <iostream> int main() { // 10x5 の 全要素が 1 の 2次元vectorを生成 auto vct2d = makeNdVector(5, 10, 1); for (std::size_t i = 0; i < 5; i++) { for (std::size_t j = 0; j < 10; j++) { std::cout << vct2d[i][j] << " "; } std::cout << "\n"; } return 0; }
C++11でもコンパイル可能なように,返り値の型を懇切丁寧に記述したものも用意した.
makeNdVector()
関数は,std::vector
のコンストラクタと同様に扱えるように,一番最後の要素が全要素の初期値とする値にし,他は各次元における要素数とした.
要素数の順は多次元の組み込み配列と同じになるようにしている.
例えば,int arr[10][20][30]
に対応するのは makeNdVector(10, 20, 30, 0);
である.
多次元 std::vector
の型の簡略化
多次元 std::vector
を生成する関数の戻り値を auto
で受け取る分には問題ないが,他の関数に渡すとなったときに void f(const std::vector<std::vector>& vct)
と書かなくてはならない問題が残っている.
そこで,型を簡略に記述できるようにしてみる.
#include <type_traits> #include <vector> template< typename T, std::size_t N, typename std::enable_if<(N > 0), std::nullptr_t>::type = nullptr > struct ndvector_impl { using type = std::vector<typename ndvector_impl<T, N - 1>::type>; }; // struct ndvector_impl template<typename T> struct ndvector_impl<T, 1> { using type = std::vector<T>; }; // struct ndvector_impl template< typename T, std::size_t N > using NdVector = typename ndvector_impl<T, N>::type; int main() { static_assert(std::is_same<NdVector<int, 1>, std::vector<int>>::value, "NG"); static_assert(std::is_same<NdVector<double, 2>, std::vector<std::vector<double>>>::value, "NG"); static_assert(std::is_same<NdVector<int, 4>, std::vector<std::vector<std::vector<std::vector<int>>>>>::value, "NG"); return 0; }
上記のコードに示す通り,例えば std::vector<std::vector<int>>
を NdVector<int, 2>
と書けるようになる.
これで関数の引数として型を記述するのも楽になる.
#include <vector> #include <type_traits> #include <utility> template< typename T, typename U > static inline std::vector<U> makeNdVector(T&& n, U&& val) noexcept { static_assert(std::is_integral<T>::value, "[makeNdVector] The 1st argument must be an integer"); return std::vector<U>(std::forward<T>(n), std::forward<U>(val)); } template< typename T, typename... Args > static inline decltype(auto) makeNdVector(T&& n, Args&&... args) noexcept { static_assert(std::is_integral<T>::value, "[makeNdVector] The 1st argument must be an integer"); return std::vector<decltype(makeNdVector(std::forward<Args>(args)...))>(std::forward<T>(n), makeNdVector(std::forward<Args>(args)...)); } template< typename T, std::size_t N, typename std::enable_if<(N > 0), std::nullptr_t>::type = nullptr > struct ndvector_impl { using type = std::vector<typename ndvector_impl<T, N - 1>::type>; }; // struct ndvector_impl template<typename T> struct ndvector_impl<T, 1> { using type = std::vector<T>; }; // struct ndvector_impl template< typename T, std::size_t N > using NdVector = typename ndvector_impl<T, N>::type; #include <iostream> static inline void showAllElement(const NdVector<int, 2>& vct2d) noexcept { for (const auto& row : vct2d) { for (const auto& col : row) { std::cout << col << " "; } std::cout << "\n"; } } int main() { auto vct2d = makeNdVector(5, 10, 1); showAllElement(vct2d); return 0; }
(どうでもいいけど,何故かメタ関数はスネークケース,インスタンス作ったりするクラスはパスカルケースで書きたい気持ちがあるんだよなぁ)
まとめ
多次元 std::vector
を簡単に生成する関数と,型の記述を簡単にするエイリアステンプレートの実装を紹介した.
なお,この記事で紹介した方法では std::vector
の第2テンプレート引数(メモリアロケータ)を指定できるようにはなっていないが,各次元において別のメモリアロケータを指定する場合などを考えると複雑であるし,そもそもメモリアロケータを指定したい人がわざわざ要素アクセス速度の遅い多次元の std::vector
を使うとは思えないので省略した.
(多次元 std::vector
はいわゆるジャグ配列なので,全次元を通してメモリが連続に確保されるとはいえないので,キャッシュ効率の観点から不利なのは明らか)