Элегантно объявляя 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>
.
Объяснение:
std:rank
дает количество измерений типа массива. Таким образом, для int[4][5][6]
возвращается 3.
- Мы передаем это
make_index_sequence
, чтобы получить пакет индексов. (0, 1, 2
)
std::remove_all_extents
дает нам базовый тип массива; T[a][b]...[n]
→ T
(int
)
std::extent
дает нам степень данного измерения. Мы называем это для каждого индекса. (4, 5, 6
).
Передав их в наш ранее реализованный md_array_t
, мы получаем md_array_t<int, 4, 5, 6>
, который производит то, что мы хотим.