3
\$\begingroup\$

This is a follow-up question for A ones Function for Boost.MultiArray in C++. Thanks to G. Sliepen's answer, the mentioned method which can set various initial value is better in generic purpose usage. However, the previous function specified the input type which must be boost::detail::multi_array::extent_gen<NumDims>. The type issue happened in some cases, an example is as below. The object filled_multi_array1 is created first with filled_multi_array(boost::extents[3][4][2], 1.0). This works well. I want to create a filled_multi_array2 object which size is as same as filled_multi_array1 and the initial value is 2.0. This doesn't work in existed version of filled_multi_array function.

auto filled_multi_array1 = filled_multi_array(boost::extents[3][4][2], 1.0);
auto filled_multi_array2 = filled_multi_array<double, filled_multi_array1.dimensionality>(filled_multi_array1.shape(), 2.0);

for (decltype(filled_multi_array2)::index i = 0; i != 3; ++i)
    for (decltype(filled_multi_array2)::index j = 0; j != 4; ++j)
        for (decltype(filled_multi_array2)::index k = 0; k != 2; ++k)
            std::cout << filled_multi_array2[i][j][k] << std::endl;

Then, I found that the type returned from .shape() function is this one const boost::detail::multi_array::multi_array_base::size_type*. It seems that the another function overloading is needed to handle the above filled_multi_array2 case. The experimental code of the filled_multi_array function with the input type const boost::detail::multi_array::multi_array_base::size_type* is as follows.

template<class T, std::size_t NumDims>
auto filled_multi_array(const boost::detail::multi_array::multi_array_base::size_type* size, const T& value)
{
    boost::multi_array<T, NumDims> output(reinterpret_cast<boost::array<size_t, NumDims> const&>(*size));
    std::fill_n(output.data(), output.num_elements(), value);
    return output;
}

With this overloaded function, filled_multi_array2 object can be generated successfully and the output is like:

2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2

The full implementation of filled_multi_array template function:

template<class T, std::size_t NumDims>
auto filled_multi_array(boost::detail::multi_array::extent_gen<NumDims> size, const T& value)
{
    boost::multi_array<T, NumDims> output(size);
    std::fill_n(output.data(), output.num_elements(), value);
    return output;
}

template<class T, std::size_t NumDims>
auto filled_multi_array(const boost::detail::multi_array::multi_array_base::size_type* size, const T& value)
{
    boost::multi_array<T, NumDims> output(reinterpret_cast<boost::array<size_t, NumDims> const&>(*size));
    std::fill_n(output.data(), output.num_elements(), value);
    return output;
}

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    A ones Function for Boost.MultiArray in C++

  • What changes has been made in the code since last question?

    The another overloaded filled_multi_array function has been added here.

  • Why a new review is being asked for?

    I am trying to handle both cases, including the one with the specified shape directly and the other one with .shape() function (the shape is referenced from another Boost.MultiArray object). If there is any possible improvement, please let me know.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

It looks bad, but that's not your fault. boost::multi_array just has a terrible interface. In any case, you want to avoid passing the shape twice, because this can give problems:

auto large_array = filled_multi_array(boost::extents[1][2][3][4][5], 1.0);
auto small_array = filled_multi_array(boost::extents[1], 1.0);
auto another_array = filled_multi_array<double, large_array.dimensionality>(small_array.shape(), 2.0);

The above will compile without errors, but might crash at runtime, since small_array.shape() is a pointer to an array that doesn't have as many elements as expected. I don't know why the interface is so terrible, if they would have .shape() return a (reference to a) boost::array<size_t, NumDims> it would all be checkable at compile time and without needing reinterpret_casts.

So to prevent errors, you want to avoid having to pass two separate pieces of information. One possibility is just passing the array whose shape you want to copy as an argument:

template<class T, std::size_t NumDims>
auto filled_multi_array(const boost::multi_array<T, NumDims> &input, const T& value) {
    boost::multi_array<T, NumDims> output(reinterpret_cast<boost::array<size_t, NumDims> const&>(input.shape()));
    std::fill_n(output.data(), output.num_elements(), value);
    return output;
}

But that interface will create confusion: does the following line modify the input?

filled_multi_array(filled_multi_array1, 2.0);

Another possibility is to keep just the first filled_multi_array() variant, but create a helper function to create a extent_gen from a multi_array:

template<std::size_t NumDims>
auto get_extents(const boost::multi_array &input) {
    boost::detail::multi_array::extent_gen<NumDims> output;

    // Set the shape of output according to input.shape(),
    // left as an exercise to the reader.
    ...

    return output;
}

And then you can use it like so:

auto filled_multi_array1 = filled_multi_array(boost::extents[3][4][2], 1.0);
auto filled_multi_array2 = filled_multi_array(get_extents(filled_multi_array1), 2.0);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.