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

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


26

I’m about to convert a lot of old C++ code to more modern C++.

There are many raw 2D arrays in that code like:

Foo bar[XSIZE][YSIZE];

And I’m about to replace these declarations with

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

This is a convenient way because the statements stay the same and the code is supposed to behave the same as with raw arrays with the additional benefit of being able to have out of bounds checks in debug builds.

But IMO the std::array<std::array<Foo, YSIZE>> is somewhat cumbersome and not easy to read, and with 3D arrays (although I have none) it would be even worse.

Right now I’m using this macro to make the declaration more readable:

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

But I feel this to be a macro hack, and I wonder if there is a cleaner, more C++ way to do something similar.

Share
Improve this question

3 Answers
3

Reset to default


46

You can use a type alias template:

#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;
}

You can also generalize it like so for any dimension:

#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; 
}

Share
Improve this answer


17

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

now

array_t<Foo[XSIZE][YSIZE]>

is

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

an alternative solutions is:

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

this uses the syntax:

array_t<Foo, XSIZE, YSIZE>

if you prefer it.

We can even combine the two, allowing either syntax.

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

and now

array_t< Foo[XSIZE], YSIZE >

works.

But be careful – the order is tricky!

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

To keep this the same we want

array_t<int, 3, 2>

to be

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

not

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

here are test cases to determine if you got the order right:

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]> > );

remove whichever have the wrong syntax for your chosen array_t.

Live example

Now, even this might be wrong. It feels incorrect that

array_t<int[3], 2>

doesn’t have sub-arrays of size 3, yet

array_t<int[3][2]>

feels like it should also be the same array, and the layout of int[3][2] should agree with array_t<int[3][2]> and agree with array_t<int, 3, 2>.

Also, array_t< array_t<int, 3>, 2> should be the same as array_t<int[3], 2>.

These requirements disagree with each other. I mean, all over the place they disagree.

Probably the simplest way to resolve this is to require only [][][] syntax, or don’t permit mixed [] and , syntax.

Having array_t<int[3][2]> with the same layout as int[3][2] is high value. Similarly, having array_t< int, 3, 2 > syntax is high value. Probably we want array_t<int, 3, 2> to mean the same as int[3][2]? Throw away this being equal to array_t< array_t<int, 3>, 2> – instead it equals array_t<array_t<int,2>,3>. Finally, block array_t<int[3], 2> syntax as confusing.

Then, split the array_t< T, 1,2,3,...> from array_t<T[1][2][3]...> templates to minimize confusion.

Share
Improve this answer

5

  • 2

    I like this solution. It's pretty clever, especially as it works for any dimension.

    – Jabberwocky

    yesterday


  • @Jabberwocky careful – my first pass got the dimension order wrong. Fixing now.

    – Yakk – Adam Nevraumont

    yesterday

  • Just noticed it myself, thanks.

    – Jabberwocky

    yesterday

  • @Jabberwocky Didn't catch it until I added "unit tests". 😉

    – Yakk – Adam Nevraumont

    yesterday

  • Testing code. Not just for n00bs.

    – user4581301

    yesterday


2

In C++23 you can use std::mdspan to get a multi-dimensional view of a single-dimensional array. There is a proposal for a std::mdarray, but that will not be in C++ until at least C++26.

An example of how std::mdspan can be used:

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';
}       

Share
Improve this answer

1

  • 1

    Looks like no compiler has a full mdspan implementation yet.

    – Aykhan Hagverdili

    21 hours ago



Not the answer you're looking for? Browse other questions tagged

or ask your own question.

Leave a Reply

Your email address will not be published. Required fields are marked *