使用 std::array 声明 2D(甚至更高维度)数组的便捷方法

Convenient way to declare 2D (or even higher dimension) arrays with std::array

提问人:Jabberwocky 提问时间:8/8/2023 最后编辑:journpyJabberwocky 更新时间:8/28/2023 访问量:3058

问:

我即将将许多旧的 C++ 代码转换为更现代的 C++。

该代码中有许多原始的 2D 数组,例如:

Foo bar[XSIZE][YSIZE];

我将用

std::array<std::array<Foo, YSIZE>, XSIZE> bar;

这是一种方便的方法,因为语句保持不变,代码的行为应该与原始数组相同,并且能够在调试版本中进行越界检查。

但是 IMO 有点麻烦且不容易阅读,而使用 3D 阵列(尽管我没有)甚至会更糟。std::array<std::array<Foo, YSIZE>>

现在我正在使用这个宏来使声明更具可读性:

#define DECLARE_2D_ARRAY(type, x, y) std::array<std::array<type, y>, x>
...
DECLARE_2D_ARRAY(Foo, XSIZE, YSIZE) bar;

但我觉得这是一个宏黑客,我想知道是否有更干净、更 C++ 的方法来做类似的事情。

C++ 多维数组 stdarray

评论

2赞 Tasos Papastylianou 8/11/2023
与所需的答案无关,但是,YSIZE 和 XSIZE 不应该在这里颠倒吗?通常 Y 会反映行数,不是吗?事实上,这种混淆通常是我在提到维度时不命名事物 x 和 y 的充分理由......

答:

75赞 Aykhan Hagverdili 8/8/2023 #1

您可以使用类型别名模板

#include <array> 
#include <cstddef>

template <class T, std::size_t x, std::size_t y>
using Array2D = std::array<std::array<T, y>, x>;

int main() {
    Array2D<int, 5, 3> arr;
}

您也可以将其概括为任何维度:

#include <array>
#include <cstddef>

template <class T, std::size_t size, std::size_t... sizes>
struct ArrayHelper {
    using type = std::array<typename ArrayHelper<T, sizes...>::type, size>;
};

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

template <class T, std::size_t... sizes>
using Array = typename ArrayHelper<T, sizes...>::type;

int main() { 
    Array<int, 5, 3, 4, 3> arr; 
}

评论

0赞 Niels Holst 8/11/2023
哇!这很优雅。以前从未听说过类型别名模板构造。这。。。不过,符号仍然让我感到害怕。
24赞 Yakk - Adam Nevraumont 8/8/2023 #2
template<class A>
struct std_array_helper {
  using type=A;
};

template<class A>
using array_t = typename std_array_helper<A>::type;

template<class T, std::size_t N0>
struct std_array_helper<T[N0]> {
  using type=std::array<array_t<T>, N0>;
};

现在

array_t<Foo[XSIZE][YSIZE]>

std::array< std::array<Foo, XSIZE>, YSIZE>

另一种解决方案是:

template<class T, std::size_t...Sz>
struct array_helper {
  using type=T;
};

template<class T0, std::size_t...Ns>
using array_t = typename array_helper<T0, Ns...>::type;

template<class T, std::size_t N0, std::size_t...Ns>
struct array_helper<T, N0, Ns...>
{
  using type=std::array<array_t<T, Ns...>, N0>;
};

这使用以下语法:

array_t<Foo, XSIZE, YSIZE>

如果你喜欢它。

我们甚至可以将两者结合起来,允许任何一种语法。

template<class T, std::size_t...Sz>
struct array_helper {
  using type=T;
};
template<class T0, std::size_t...Ns>
using array_t = typename array_helper<T0, Ns...>::type;

template<class T, std::size_t N0, std::size_t...Ns>
  requires (!std::is_array_v<T>)
struct array_helper<T, N0, Ns...>
{
  using type = std::array<array_t<T, Ns...>, N0>;
};

template<class T, std::size_t N0, std::size_t...Ns>
struct array_helper<T[N0], Ns...>:
  array_helper<array_t<T, Ns...>, N0>
{};

现在

array_t< Foo[XSIZE], YSIZE >

工程。

但要小心 - 订单很棘手!

int[3][2] is an array of 3 elements of arrays of 2 elements.

为了保持我们想要的相同

array_t<int, 3, 2>

成为

std::array< std::array< int, 2 >, 3>

std::array< std::array< int, 3 >, 2>

以下是用于确定订单是否正确的测试用例:

static_assert( std::is_same_v< std::array<int, 3>, array_t<int, 3> > );
static_assert( std::is_same_v< std::array< std::array<int, 2>, 3>, array_t<int, 3, 2> > );
static_assert( std::is_same_v< std::array< std::array<int, 2>, 3>, array_t<int[3], 2> > );
static_assert( std::is_same_v< std::array< std::array<int, 2>, 3>, array_t<int[3][2]> > );

删除对您选择的语法错误的 .array_t

现场示例

现在,即使这样也可能是错误的。感觉不对

array_t<int[3], 2>

还没有大小为 3 的子数组

array_t<int[3][2]>

感觉也应该是同一个数组,布局应该和一致。int[3][2]array_t<int[3][2]>array_t<int, 3, 2>

此外,应与 相同。array_t< array_t<int, 3>, 2>array_t<int[3], 2>

这些要求彼此不一致。我的意思是,到处都是他们不同意。

解决此问题的最简单方法可能是只需要语法,或者不允许混合语法。[][][][],

具有与高价值相同的布局。同样,拥有语法也是高价值的。可能我们想和?扔掉这个等于 - 相反,它等于 .最后,块语法令人困惑。array_t<int[3][2]>int[3][2]array_t< int, 3, 2 >array_t<int, 3, 2>int[3][2]array_t< array_t<int, 3>, 2>array_t<array_t<int,2>,3>array_t<int[3], 2>

然后,拆分 from 模板以最大程度地减少混淆。array_t< T, 1,2,3,...>array_t<T[1][2][3]...>

评论

2赞 Jabberwocky 8/8/2023
我喜欢这个解决方案。它非常聪明,尤其是当它适用于任何维度时。
0赞 Yakk - Adam Nevraumont 8/8/2023
@Jabberwocky小心 - 我第一次通过的维度顺序是错误的。立即修复。
0赞 Jabberwocky 8/8/2023
我自己刚刚注意到了,谢谢。
0赞 Yakk - Adam Nevraumont 8/8/2023
@Jabberwocky 直到我添加了“单元测试”才发现它。;)
1赞 user4581301 8/9/2023
测试代码。不仅适用于 n00bs。
8赞 G. Sliepen 8/9/2023 #3

在 C++23 中,可以使用 std::mdspan 获取一维数组的多维视图。有一个关于std::mdarray的建议,但至少在C++26之前不会出现在C++中。

如何使用的示例:std::mdspan

std::array<Foo, XSIZE * YSIZE> bar_1d;
std::mdspan bar(bar_1d.data(), XSIZE, YSIZE);
…
for (std::size_t y = 0; y != YSIZE; ++y) {
    for (std::size_t x = 0; x != XSIZE; ++x) {
        std::cout << bar[x, y] << ' ';
    }
    std::cout << '\n';
}       

评论

2赞 Aykhan Hagverdili 8/9/2023
看起来还没有编译器有完整的 mdspan 实现。