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

14

我正在使用基于std::array的二维数组。

基本上,与其使用:

MyType myarray[X_SIZE][Y_SIZE];

我有:

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

这个代码运行良好,但我认为声明部分不是很易读。

有没有一种使用巧妙的C++模板机制来声明它的方法,使得声明看起来像这样?

My2DArray<Mytype, X_SIZE, Y_SIZE> myarray;

2
我认为“现代”C++的代码优雅并不是/不曾是一个优先事项! - Adrian Mole
1
我期望它是 [Y][X](并且在答案的模板中是 std::array<std::array<T, X>, Y>),因为通常我认为 X 是基本维度,它被连续迭代。当然,如果您主要通过 Y 进行迭代,则您的示例也是最优的,因此这取决于用途。 - Ped7g
@Ped7g 不是Y,X而是X,Y是有意的,因为访问是这样完成的:myarray[x_index][y_index] - Jabberwocky
@Jabberwocky 这就是我所说的,如果例如 int map[4][3]; 那么内存布局为 [0][0], [0][1], [0][2], [1][0], [1][1], ...,也就是第二维形成整块连续的整数,因此你应该(在可能的情况下)在内部循环中迭代第二维,在外部循环中迭代第一维。在你的维度中,这意味着将 X 作为外部循环,将 Y 作为内部循环(我的意思是对于那些甚至可以进行这种区分的情况,例如矩阵加法,在乘法中并不那么重要,因为两个矩阵中的一个必须以次优的顺序遍历),等等。 - Ped7g
@Ped7g 这里不是关于优化的问题,而只关于代码的可读性。但你是对的。 - Jabberwocky
3个回答

22
如果你仅需要2D数组,那么这相当简单:
template <class T, std::size_t X, std::size_t Y>
using My2DArray = std::array<std::array<T, Y>, X>;

如果您想要一个不仅限于2D数组的通用机制,也可以实现:
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]


4
一种比较优美的实现方法是使用折叠表达式(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>())
);

编译器资源浏览器

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>>{}));

Compiler Explorer

这使得我们可以写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] -> Tint
  4. std::extent给出给定维度的大小。我们为每个索引调用这个函数(4, 5, 6)。

通过将这些传递给我们先前实现的md_array_t,我们最终得到了md_array_t<int, 4, 5, 6>,它产生了我们想要的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接