3

After seeing the benefits of std::array I was trying to create a class that supports multiple dimensions.

My initial experiments used nested std::array. I chose not to use this method partly for the ugly way to write the type.

ie: std::array<std::array<std::array<...>, >, >

The new class is mostly working except for initialization. I haven't decided whether its best to use inheritance or containment. The choice may depend on whether I can get initialization working.

How can I get either of the last two lines of this to compile:

// multi-dimensional array based on std::array

#include <array>

template <class T, int s, int... r>
class arraynd_a : public std::array<arraynd_a<T, r...>, s>
{
public:
};

template <class T, int s>
class arraynd_a<T, s> : public std::array<T, s>
{
public:
};

template <class T, int s, int... r>
class arraynd_b
{
public:
    std::array<arraynd_b<T, r...>, s> arr;
};

template <class T, int s>
class arraynd_b<T, s>
{
public:
    std::array<T, s> arr;
};

void test()
{
    constexpr std::array<std::array<int, 2>, 3> a1 = { { { 0, 1 }, { 1, 0 }, { 2, 4 } } };
    /*constexpr*/ arraynd_a<int, 3, 2> a2a;
    /*constexpr*/ arraynd_b<int, 3, 2> a2b;
#if 0
    /*constexpr*/ arraynd_a<int, 3, 2> a3a =  { { { 0, 1 }, { 1, 0 }, { 2, 4 } } };
#endif
#if 0
    /*constexpr*/ arraynd_b<int, 3, 2> a3b =  { { { 0, 1 }, { 1, 0 }, { 2, 4 } } };
#endif
}
RadAd
  • 194
  • 9
  • If your worried about the type of nested array looking ugly, consider using a typedef or a using declaration. – Tom Sep 13 '18 at 08:03
  • Accessing array as: `a2b.arr = {...}` does the job, but i can't figure out how to do this with aggregate initialization – Krzysztof Lewko Sep 13 '18 at 08:13
  • I suggest, that you have a look at a library, such as eigen or amadrillo. You don't have to reinvent it. http://eigen.tuxfamily.org/dox-devel/unsupported/group__CXX11__Tensor__Module.html – schorsch312 Sep 13 '18 at 12:10

2 Answers2

1

If you are doing the member class way, you have to wrape the array content one more time with {} (you also do not initialize an array with std::array<int, 2> arr = 1, 2;, or?):

template <class T, std::size_t DIM, std::size_t... ARGS>
struct arr_mult_dim
{
    std::array<arr_mult_dim<T, ARGS...>, DIM> arr_;
};
template <class T, int DIM>
struct arr_mult_dim<T, DIM>
{
    std::array<T, DIM> arr_;
};
template <class T, std::size_t... DIMS>
using arr_mult_dim_t = arr_mult_dim<T, DIMS...>;

And use it like that:

arr_mult_dim_t<int, 2> arr_1 = { { 0, 1 } };

But a more pretty way to do it would be if you generate the needed type with a nested using declaration:

template <class T, std::size_t DIM, std::size_t... ARGS>
struct arr_mult_dim
{
    using type = std::array<typename arr_mult_dim<T, ARGS...>::type, DIM>;
};
template <class T, std::size_t DIM>
struct arr_mult_dim<T, DIM>
{
    using type = std::array<T, DIM>;
};
template <class T, std::size_t... DIMS>
using arr_mult_dim_t = typename arr_mult_dim<T, DIMS...>::type;

The usage would be:

arr_mult_dim_t<int, 2> arr_1 = { 0, 1 };
arr_mult_dim_t<int, 2, 2> arr_2 = { { { 0, 1 }, {0, 1} } };
arr_mult_dim_t<int, 2, 2, 2> arr_3 =
{
    {
        {
            {
                {0, 1 },
                { 0, 1 }
            }
        },
        {
            {
                { 0, 1 },
                { 0, 1 }
            }
        }
    }
};

Now you don't need to use the extra {}.

EDIT: I did some research. I don't know why your inheritation solution isn't working. The class which inherites from a class with aggregate propierties should also have aggregate propiertes. The standard says:

The elements of an aggregate are: ...
- for a class, the direct base classes in declaration order followed by the direct non-static data members
in declaration order.

Looks like this isn't implemented yet. In older standards there is a clausel which is explicitly prohibiting aggregate classes to have base classes: Why can I not brace initialize a struct derived from another struct?

JulianW
  • 897
  • 9
  • 23
  • 1
    `std::array`'s size parameter is a `std::size_t` not an `int`, consider using that instead – Caleth Sep 13 '18 at 11:29
  • Thanks, So in the form of my original question the answer is more braces: arraynd_b a3b = { { { { { { 0, 1 } } }, { { { 1, 0 } } }, { { { 2, 4 } } } } } }; – RadAd Sep 13 '18 at 12:36
  • So, the type of arr_mult_dim_t arr_3 actually is std::array, 2>, 2>, 2>, its just defined using the nested using declaration. This does look better regarding the braces. – RadAd Sep 13 '18 at 12:39
  • @RadAd yes, exactly – JulianW Sep 13 '18 at 14:03
0

I was able to keep my original design and initialize it from a native multidimensional array:

#include <array>

template <class T, size_t s, size_t...r>
struct arraynd
{
    typedef arraynd<T, r...> inner_type;
    typedef typename inner_type::native_type native_type[s];

    static constexpr std::array<inner_type, s> to_arraynd(const native_type& init)
    {
        return impl_to_arraynd(init, std::make_index_sequence<s> {});
    }

    template <std::size_t... I>
    static constexpr std::array<inner_type, s> impl_to_arraynd(const native_type& init, std::index_sequence<I...>)
    {
        return { inner_type(init[I])... };
    }

    constexpr arraynd()
    {
    }

    constexpr arraynd(const native_type& init)
        : inner(to_arraynd(init))
    {

    }

    std::array<inner_type, s>   inner;
};

template <class T, size_t s>
struct arraynd<T, s>
{
    typedef T inner_type;
    typedef T native_type[s];

    static constexpr std::array<inner_type, s> to_arraynd(const native_type& init)
    {
        return impl_to_arraynd(init, std::make_index_sequence<s> {});
    }

    template <std::size_t... I>
    static constexpr std::array<inner_type, s> impl_to_arraynd(const native_type& init, std::index_sequence<I...>)
    {
        return { inner_type(init[I])... };
    }

    constexpr arraynd()
    {
    }

    constexpr arraynd(const native_type& init)
        : inner(to_arraynd(init))
    {

    }

    std::array<inner_type, s>   inner;
};

int main()
{
    constexpr int a2native[2][3] = { { 1, 2, 3 }, { 1, 2, 3 } };
    constexpr std::array<std::array<int, 3>, 2>   a2std = { { { 1, 2, 3 }, { 1, 2, 3 } } };
    constexpr arraynd<int, 2, 3> a2({ { 1, 2, 3 }, { 1, 2, 3 } });

    return 0;
}
RadAd
  • 194
  • 9