Элегантно объявляя 2 (или даже multi-) размерные std::arrays

Я использую двумерные массивы на основе std::array.

В основном вместо:

MyType myarray[X_SIZE][Y_SIZE];

У меня есть:

std::array<std::array<MyType, Y_SIZE>, X_SIZE> myarray;

Это прекрасно работает, но IMO декларация не очень читаема.

Есть ли способ объявить это с помощью некоторого умного механизма C++ шаблона, чтобы объявление могло выглядеть примерно так?

My2DArray<Mytype, X_SIZE, Y_SIZE> myarray;

Ответ 1

Если вам нужны только 2D-массивы, это довольно просто:

template <class T, std::size_t X, std::size_t Y>
using My2DArray = std::array<std::array<T, Y>, X>;

Если вам нужен универсальный механизм, не ограничивающийся двумерными массивами, это тоже можно сделать:

template <class T, std::size_t N, std::size_t... Ns>
struct AddArray {
    using type = std::array<typename AddArray<T, Ns...>::type, N>;
};

template <class T, std::size_t N>
struct AddArray<T, N> {
    using type = std::array<T, N>;
};

template <class T, std::size_t... N>
using MyNDArray = typename AddArray<T, N...>::type;

[Живой пример]

Ответ 2

Несколько элегантный способ реализовать эту операцию с помощью выражения сгиба:

// Some namespace to hide the poorly-constrained template function:
namespace array_making {
    template <std::size_t N>
    struct array_dim {};

    template <typename T, std::size_t N>
    constexpr auto operator%(array_dim<N>, T const&)
        -> std::array<T, N>;
}

template <typename T, std::size_t... Is>
using md_array_t = decltype(
    (array_making::array_dim<Is>{} % ... % std::declval<T>())
);

Проводник компилятора.

Тогда md_array_t<int, 1, 2, 3> - это array<array<array<int, 3>, 2>, 1>. Если вы предпочитаете противоположный порядок, измените параметры operator% и аргументы на выражение сгиба.


Обратите внимание, что это может привести к проблемам, если тип T имеет неограниченный operator% в связанном пространстве имен (пожалуйста, ограничьте своих операторов!). Мы можем снизить риск этого, выбрав маловероятные операторы, такие как .*, ->* или %=; или мы можем использовать оболочку array_type<T>. Ни одно из решений не позволяет полностью избежать проблемы неправильно ограниченных перегрузок операторов для T.

Ответ 3

Мы можем обернуть один из существующих MyNDArray/md_array_t ответов, чтобы получить альтернативный интерфейс:

template <typename Arr, std::size_t... Is>
constexpr auto make_array_impl(std::index_sequence<Is...>)
    -> md_array_t<std::remove_all_extents_t<Arr>,
        std::extent_v<Arr, Is>...>;

template <typename Arr>
using make_array = decltype(make_array_impl<Arr>(
    std::make_index_sequence<std::rank_v<Arr>>{}));

Проводник компилятора

Это позволяет нам написать make_array<int[4][5][6]>, что означает array<array<array<int, 6>, 5, 4>.


Объяснение:

  1. std:rank дает количество измерений типа массива. Таким образом, для int[4][5][6] возвращается 3.
  2. Мы передаем это make_index_sequence, чтобы получить пакет индексов. (0, 1, 2)
  3. std::remove_all_extents дает нам базовый тип массива; T[a][b]...[n]T (int)
  4. std::extent дает нам степень данного измерения. Мы называем это для каждого индекса. (4, 5, 6).

Передав их в наш ранее реализованный md_array_t, мы получаем md_array_t<int, 4, 5, 6>, который производит то, что мы хотим.