优雅地声明 2(甚至多)维 std::arrays

Declaring 2 (or even multi-) dimensional std::arrays elegantly

我正在使用基于 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;

如果您只需要二维数组,这非常简单:

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;

[Live example]

实现此操作的一种比较优雅的方法是使用折叠表达式:

// 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.

那么md_array_t<int, 1, 2, 3>就是array<array<array<int, 3>, 2>, 1>。如果您更喜欢相反的顺序,请反转 operator% 的参数和折叠表达式的参数。


请注意,如果类型 T 在关联的命名空间中具有不受约束的 operator%,这将 运行 出现问题(请限制您的运算符!)。我们可以通过选择不太可能的运算符来降低发生这种情况的风险,例如 .*->*%=;或者我们可以使用 array_type<T> 包装器。这两种解决方案都没有完全避免 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>>{}));

Compiler Explorer

这让我们可以写 make_array<int[4][5][6]> 来表示 array<array<array<int, 6>, 5, 4>


解释:

  1. std:rank给出数组类型的维数。因此,对于 int[4][5][6],它 returns 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>,它会产生我们想要的结果。