Function returning a custom struct that includes a boost::multi_array

64 views Asked by At

I have this apparently simple code, but boost complains about a dimension mismatch:

#include <iostream>
#include <fstream>
#include "boost/multi_array.hpp"

struct t_struct {
   boost::multi_array<double, 2>  grid_mat;
       };

t_struct get_big_data(int dim_1, int dim_2) {
   boost::multi_array<double,2>  grid_mat(boost::extents[dim_1][dim_2]);

            for (int j=0;j<dim_2;++j){
                for (int i=0; i<dim_1;++i){ 
                grid_mat[i][j] = i + j; 
                }
        }
 
     t_struct all_data;
     all_data.grid_mat.resize(boost::extents[dim_1][dim_2]);
     all_data.grid_mat = grid_mat; 
   
     return all_data;

}


int main(int argc, char *argv[]) {

    t_struct all_data_2;
    int dim_1 = 320;
    int dim_2 = 6;
    all_data_2 = get_big_data(dim_1,dim_2);

    return 0;
} 

The runtime error I am receiving is:

/usr/include/boost/multi_array/multi_array_ref.hpp:483: boost::multi_array_ref<T, NumDims>& boost::multi_array_ref<T, NumDims>::operator=(const ConstMultiArray&) [with ConstMultiArray = boost::multi_array<double, 2>; T = double; long unsigned int NumDims = 2]: Assertion `std::equal(other.shape(),other.shape()+this->num_dimensions(), this->shape())' failed.

I can't figure out what I am doing wrong.

The code compiles and I was expecting it would simple to populate a boost::multi_array within a struct.

1

There are 1 answers

2
sehe On BEST ANSWER

You're copying the matrix several times. The first copy is fine:

all_data.grid_mat = grid_mat;

That is because you were aware that multi_array assignment operator requires the destination array to have the same shape, and you sized it accordingly.

However, the second copy happens in main:

t_struct all_data_2;
all_data_2 = get_big_data(320, 6);

Here, all_data_2 already was default constructed and hence does NOT have the required shape. You can simply elide the 2-phase init by using copy-initialization:

t_struct all_data_2 = get_big_data(320, 6);

This means that the compiler (in this case) can elide the assignment, instead using the auto-generated copy constructor. You can generalize this by "simply" returning an aggregate-constructed value as well:

Live On Coliru

#include "boost/multi_array.hpp"
#include <iostream>

struct t_struct {
    boost::multi_array<double, 2> grid_mat;
};

t_struct get_big_data(int n, int m) {
    boost::multi_array<double, 2> grid_mat(boost::extents[n][m]);

    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            grid_mat[i][j] = i + j;

    return {grid_mat};
}

int main() {
    t_struct all_data_2 = get_big_data(320, 6);
}

BONUS

Doing away with the surprises. Because they are just inviting bugs! You can specify the semantics you want in t_struct by defining your own set of special member functions:

Live On Coliru

#undef NDEBUG
#include "boost/multi_array.hpp"
#include <iostream>

using GridMat = boost::multi_array<double, 2>;
using Shape   = std::array<size_t, 2>;

struct t_struct {
    t_struct() = default;
    explicit t_struct(GridMat mat) : grid_mat_(std::move(mat)) {}
    t_struct(t_struct const& rhs) : grid_mat_(rhs.grid_mat_) {}

    Shape shape() const {
        auto shape = grid_mat_.shape();
        return {shape[0], shape[1]};
    }

    t_struct& operator=(t_struct const& rhs) {
        grid_mat_.resize(rhs.shape());
        grid_mat_ = rhs.grid_mat_;
        return *this;
    }

  private:
    GridMat grid_mat_;
};

t_struct get_big_data(int n, int m) {
    GridMat grid_mat(boost::extents[n][m]);

    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            grid_mat[i][j] = i + j;

    return t_struct(grid_mat);
}

#include <fmt/ranges.h>
int main() {
    t_struct clone;
    fmt::print("default-constructed shape: {}\n", clone.shape());

    clone = get_big_data(320, 6);
    fmt::print("assigned shape: {}\n", clone.shape());

    clone = get_big_data(6, 78);
    fmt::print("re-assigned shape: {}\n", clone.shape());
}

Printing

default-constructed shape: [0, 0]
assigned shape: [320, 6]
re-assigned shape: [6, 78]