How to create a stack ND C-array from param pack of length N?

57 Views Asked by At

For the parameter pack size_t ...len, e.g. [len={3,5,2}, I wish to create

double x[3][5][2];

or an equivalent type that I can reference to it (cf Requirement below).

Question

Is there a short way of doing this?

Remarks

I would like to remain able to make heavy use of "dimensional abuse" x[i][j][k] == x[0][0][i*5*2+j*2+k] == (*(x+i))[j][k] etc.

What I tried

My current attempt is:

template<typename Tfloat, int...> struct ARRAY_TYPE;

template<typename Tfloat, int len, int ...lens>
struct ARRAY_TYPE<Tfloat,len,lens...>{
    using type = typename ARRAY_TYPE<Tfloat,lens...>::type[len];
};

template<typename Tfloat>
struct ARRAY_TYPE<Tfloat>{
    using type = Tfloat;
};

But I am clueless as to figure out whether this yields me actually the exact same constructed type. I am afraid it does not support "dimensional abuse". Is there a way to reinterpret an x of above type into a y of my type ARRAY_TYPE<double,len...> ? (That would at least enable me to test for differences in behaviour.)

Or is there maybe even an efficient triple-dot syntax for obtaining the sought type by virtue of a one-liner expression from len... (other than with my tedious ARRAY_TYPE implementation)?

Requirement

For y of my type, I need to be able to reference it from double[3][5][2]. I.e.:

double (&ref)[3][5][2] = y;
2

There are 2 best solutions below

2
Pepijn Kramer On

Do you mean something like this? Note I will not use "C" style arrays for this (the final std::array of arryas should be contiguous in memory still)

#include <array>
#include <type_traits>

namespace details
{
    template<typename type_t, std::size_t dim, std::size_t... dims>
    auto deduce_md_type()
    {
        if constexpr (sizeof...(dims) == 0ul)
        {
            return std::array<type_t,dim>{};
        }
        else
        {
            return std::array<decltype(deduce_md_type<type_t,dims...>()),dim>{};
        }
    }
}

template<typename type_t, std::size_t...dims>
using md_array_t = decltype(details::deduce_md_type<type_t,dims...>());

int main()
{
    md_array_t<int, 2, 3, 4> values{};
    static_assert(std::is_same_v<md_array_t<int, 2, 3, 4>, std::array<std::array<std::array<int,4>,3>,2>>);

    values[1][2][3] = 1;
}
2
cigien On

This can be done by constructing the multi-dimensional array type recursively.*

First, the primary template that creates an array type X of size i, where i is the first dimension. The type X is then recursively constructed from the remaining dimensions:

template<typename T, int i, int ... rest>
struct ARRAY_TYPE_IMPL {
    using Type = typename ARRAY_TYPE_IMPL<T, rest...>::Type[i];
};

With a base case for the last dimension that simply creates a 1-dimensional array:

template<typename T, int last>
struct ARRAY_TYPE_IMPL<T, last> {
    using Type = T[last];
};

Along with a helper alias to avoid the typename dance when using this utility:

template<typename T, int ...dims>
using ARRAY_TYPE = typename ARRAY_TYPE_IMPL<double, dims...>::Type;

Now you have a type that does what you want. e.g.:

ARRAY_TYPE<double, 3, 5, 2> y;
// check that y has the right type
static_assert(std::is_same_v<decltype(y), double[3][5][2]>);
// and even take a reference to that type
double(&ref)[3][5][2] = y;

Here's a demo.


* A function that returns the relevant type, as in Pepjin's answer, would be much nicer (since we could use if constexpr to handle the base case, thereby avoiding the partial specialization), but you can't return array types from a function.

You should also generally prefer std::array over C-style arrays when possible, but if you need to use the latter, this is one way to go about it.