Declaring 2 (or even multi-) dimensional std::arrays elegantly
If you want just 2D arrays, it's fairly straightforward:
template <class T, std::size_t X, std::size_t Y>
using My2DArray = std::array<std::array<T, Y>, X>;
If you want a generic mechanism not limited to 2D arrays, it can be done too:
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;
[Live example]
A somewhat elegant way to implement this operation is with a fold expression:
// 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>())
);
Compiler Explorer.
Then md_array_t<int, 1, 2, 3>
is array<array<array<int, 3>, 2>, 1>
. If you prefer the opposite order, reverse the parameters of the operator%
and the arguments to the fold expression.
Note that this will run into problems if the type T
has an unconstrained operator%
in an associated namespace (please constrain your operators!). We can reduce the risk of this happening by choosing unlikely operators such as .*
, ->*
, or %=
; or we can use an array_type<T>
wrapper. Neither solution completely avoids the problem of improperly constrained operator overloads for T
.
We can wrap one of the existing MyNDArray
/ md_array_t
answers to arrive at an alternative interface:
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>>{}));
Compiler Explorer
This allows us to write make_array<int[4][5][6]>
to mean array<array<array<int, 6>, 5, 4>
.
Explanation:
std:rank
gives the number of dimensions of an array type. Thus, forint[4][5][6]
, it returns 3.- We hand this to
make_index_sequence
to end up with a pack of indices. (0, 1, 2
) std::remove_all_extents
gives us the underlying type of the array;T[a][b]...[n]
->T
(int
)std::extent
gives us the extent of the given dimension. We call this for each index. (4, 5, 6
).
By passing these to our previously-implemented md_array_t
, we end up with md_array_t<int, 4, 5, 6>
, which produces what we want.