Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a function or constructor that accepts array of any rank

Tags:

c++

c++17

I have a wrapper struct around a vector and a shape, like this:

template <std::size_t N>
struct array_type {
    std::array<std::size_t, N> shape;
    std::vector<float> data;
};

I would like to be able to construct an array_type from any array float[N], float[N][M], etc.

Currently, I have a function for each rank, e.g.

template <std::size_t N>
array_type<1> _1d(const deel::formal::float_type (&values)[N]) {
    return {{N},
            std::vector<float_type>(
                reinterpret_cast<const float_type*>(values),
                reinterpret_cast<const float_type*>(values) + N)};
}

template <std::size_t N>
array_type<2> _2d(const deel::formal::float_type (&values)[N][M]) {
    return {{N, M},
            std::vector<float_type>(
                reinterpret_cast<const float_type*>(values),
                reinterpret_cast<const float_type*>(values) + N * M)};
}

I would like to write something like:

template <class Array>
array_type<std::rank_v<Array>> make_array(Array const&);

...but that does not work for initializer list:

auto arr1 = _1d({1, 2, 3}); // Ok
auto arr2 = make_array({1, 2, 3}); // Ko

Is there a way to have this make_array? Or (since I think it's not possible), to have at least something like make_array<1>({1, 2, 3}) where the rank is explicitly specified?

like image 941
Holt Avatar asked Nov 02 '20 12:11

Holt


Video Answer


1 Answers

array_type::shape can be generated with std::extent with a bit of template meta programming:

template<typename Array, std::size_t... I>
auto extents_impl(const Array& a, std::index_sequence<I...>)
{
    return std::array{std::extent_v<Array, I>...};
}

template<typename Array>
auto extents(const Array& a)
{
    return extents_impl(a, std::make_index_sequence<std::rank_v<Array>>());
}

With this, make_array can be written as:

template <class Array, std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_all_extents_t<Array>>, float>, int> = 0> // magic incantation :)
array_type<std::rank_v<Array>> make_array(Array const& a)
{
    array_type<std::rank_v<Array>> ret {
        .shape = extents(a),
        .data = std::vector<float>(sizeof a / sizeof(float)),
    };
    std::memcpy(ret.data.data(), &a, sizeof a);
    return ret;
}

I use memcpy to avoid potential pointer type aliasing violations and the technicality of iterating outside the bounds of sub array being UB according to strict interpretation of the standard.

...but that does not work for initializer list:

Add one overload:

template <std::size_t N>
array_type<1> make_array(float const (&a)[N])
{
    return make_array<float[N]>(a);
}

Alternatively, you can specify the array type in the call:

make_array<float[2][3]>({{1, 2, 3},{4, 5, 6}});

This requires no overloads.

like image 89
eerorika Avatar answered Oct 28 '22 16:10

eerorika