ぼやきごと/2013-12-28/C++: array クラスを利用した多次元配列型を定義してみた のバックアップ(No.2)


C++: array クラスを利用した多次元配列型を定義してみた

Prev: [C++: array クラスの要素数指定を省略し、コンパイル時に要素数チェックする]

一応、前回の記事から続いています。

int values[2][3] という組み込みの多次元配列と同等の構造を std::array を使って定義すると、 std::array<std::array<int, 3>, 2> となります。
T values[A][B][C][D] ならば std::array<std::array<std::array<std::array<T, D>, C> B>, A> です。
要素数の順序が逆になってわかりにくいですし、そもそも長ったらしくてとても書きたくないですね。

そこで、上記のような多次元配列を multi_array<int, 2, 3>multi_array<T, A, B, C, D> といった書き方で定義できるようにしてみました。
VC++2013でもコンパイルできます。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 
 
 
-
-
|
|
|
|
!
|
|
-
-
!
!
|
-
|
|
|
!
|
|
-
-
!
!
|
-
|
|
|
|
!
|
|
!
#include <array>
 
namespace util
{
    /**
     * @brief 多次元配列型を提供する。
     * @tparam T        配列要素の型。
     * @tparam Size     ルートの要素数。
     * @tparam Sizes... 各次元の要素数。
     */
    template<typename T, std::size_t Size, std::size_t ...Sizes>
    struct multi_array_type
    {
        /// 多次元配列型。
        using type = std::array<typename multi_array_type<T, Sizes...>::type, Size>;
    };
 
    /**
     * @brief 1次元配列型を提供する。
     * @tparam T     配列要素の型。
     * @tparam Size  ルートの要素数。
     */
    template<typename T, std::size_t Size>
    struct multi_array_type<T, Size>
    {
        /// 1次元配列型。
        using type = std::array<T, Size>;
    };
 
    /**
     * @brief 多次元配列型。
     * @tparam T        配列要素の型。
     * @tparam Size     ルートの要素数。
     * @tparam Sizes... 各次元の要素数。
     */
    template<typename T, std::size_t Size, std::size_t ...Sizes>
    using multi_array = typename multi_array_type<T, Size, Sizes...>::type;
}

この multi_array は次のようなコードで初期化できます。

// リスト初期化をするには波括弧を2重にする必要がある
util::multi_array<int, 2, 3> a = {{ {{ 1, 2, 3 }}, {{ 4, 5, 6 }} }};

// 入れ子aggregate初期化構文
// 入れ子要素の波括弧を省略して書ける(一部だけ省略することはできない)
util::multi_array<int, 2, 3> b = { 1, 2, 3,  4, 5, 6 };

要素数を推論させて初期化するには、前回の記事でも使った make_array 関数を入れ子にして…と言いたいところなのですが、型の指定が面倒になります。
そこで、引数から要素数だけでなく型も推論する1次元配列作成関数を定義します。
Sproutというライブラリ*1make_common_array 関数の実装がとても参考になります。*2

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 
 
 
 
-
-
!
|
-
|
|
|
|
|
!
|
-
|
|
|
|
!
|
|
-
|
!
!
#include <array>
#include <type_traits>
#include <utility>
 
namespace util
{
    /// make_common_array 関数の戻り値の型となる1次元配列型を提供する。
    template<typename ...TArgs>
    struct common_array_type
    {
        using type =
            std::array<
                typename std::decay<
                    typename std::common_type<TArgs...>::type>::type,
                sizeof...(TArgs)>;
    };
 
    /**
     * @brief 引数から推論した型の1次元配列を作成する。
     * @tparam TArgs...     各引数の型。引数から推論される。
     * @param[in] args...   配列要素リスト。
     * @return 1次元配列。要素の型は std::common_type<Args...>::type となる。
     */
    template<typename ...TArgs>
    inline typename common_array_type<TArgs...>::type make_common_array(TArgs&&... args)
    {
        return typename common_array_type<TArgs...>::type{ std::forward<TArgs>(args)... };
    }
}

VC++2013でもコンパイルできるようにしてあります。
VC++対応が不要ならば constexpr 関数にするのもいいでしょう。

上記の make_common_array 関数を使うと、次のように型も要素数も自動推論させて1次元配列や多次元配列を初期化できます。

// auto は std::array<int, 4> になる。
auto d1 = util::make_common_array(1, 2, 3, 4);

// auto は util::multi_array<double, 2, 3> 相当になる。
auto d2 =
    util::make_common_array(
        util::make_common_array(1.2, 2.3, 3.4),
        util::make_common_array(4.5, 5.6, 6.7));

もっと実用性を高めるには、多次元配列に対応したユーティリティ関数を色々作った方がいいでしょうね。

Category: [C++][プログラミング] - 2013-12-28 00:28:46

*1 中3女子が作っている。
*2 …というかほぼ丸パクリです。