Search code examples
c++c++17generic-programming

Variadic template for multidimensional std::array


We can alias a multidimensional array like this:

template<typename T, size_t size1, size_t size2>
using myArray = std::array<std::array<T, size2>, size1>;

But this only allows us a predefined number of dimensions. Is there a way to turn this into a variadic template, so that we could write any of those:

myArray<int, 2, 2, 2> arr3d;
myArray<int, 2, 2, 2, 2> arr4d;

I've tried a few things but wasn't completely satisfied with any of them.

This:

template<typename T, size_t size>
using myArray<T, size> = std::array<T, size>;

template<typename T, size_t size, size_t... more>
using myArray = std::array<myArray<T, more...>, size>;

doesn't even compile because template specializations are apparently not allowed with alias templates.

This is currently my best solution but deletes all constructors of std::array which I would like to keep:

template<typename T, size_t size, size_t... more>
struct myArray : public std::array<myArray<T, more...>, size> {};

template<typename T, size_t size>
struct myArray<T, size> : public std::array<T, size>{};

And with this solution I would always have to write ".internal" in front of every array access:

template<typename T, size_t size, size_t... more>
struct myArr {
    std::array<myArr<T, more...>, size> internal;
};

template<typename T, size_t size>
struct myArr<T, size> {
    std::array<T, size> internal;
};

So could anyone think of a solution like the second but where I could keep the constructors? I can't.


Solution

  • I would suggest simply multiplying the dimensions together and having a single std::array. E.g.: std::array<int, D0 * D1 * D2 * D3>. You can then provide utility functions or a wrapper class to convert a multi-dimensional index into one-dimensional.

    Anyway...

    Here's a simple recursive solution that uses explicit template specialization:

    template <typename T, std::size_t... Ds>
    struct nested_array;
    
    template <typename T, std::size_t D>
    struct nested_array<T, D>
    {
        using type = std::array<T, D>;
    };
    
    template <typename T, std::size_t D, std::size_t... Ds>
    struct nested_array<T, D, Ds...>
    {
        using type = std::array<typename nested_array<T, Ds...>::type, D>;
    };
    
    static_assert(std::is_same_v<
        typename nested_array<int, 1, 2, 3>::type,
        std::array<std::array<std::array<int, 3>, 2>, 1>
    >);
    

    live example on wandbox


    Here's a solution based on variable template partial specialization:

    template <typename T>
    struct t { using type = T; };
    
    template <typename T>
    using unwrap = typename T::type;
    
    template <typename T, std::size_t... Ds>
    constexpr auto nested_array = t<void>{};
    
    template <typename T, std::size_t D>
    constexpr auto nested_array<T, D> = t<std::array<T, D>>{};
    
    template <typename T, std::size_t D, std::size_t... Ds>
    constexpr auto nested_array<T, D, Ds...> = 
        t<std::array<unwrap<decltype(nested_array<T, Ds...>)>, D>>{};
    
    static_assert(std::is_same_v<
        unwrap<decltype(nested_array<int, 1, 2, 3>)>,
        std::array<std::array<std::array<int, 3>, 2>, 1>
    >);
    

    live example on wandbox