c++boostboost-multi-array

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


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.


Solution

  • 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]