alle_meije
alle_meije

Reputation: 2480

Does an xtensor adaptor of a std::vector store its own copy of the data?

I am trying to build an image class where the storage is provided by a std::vector (easier for interacting with other variables and files), and an xtensor adaptor provides multidimensional access (and, hopefully, later also loads of mathematical goodies).

The image object is initialised with a storage vector of size 1, a shape vector {1} and a strides vector of {1}. Then when I load a vector of points that is bigger, it should resize the storage vector, copy the values and set the new shape of the xtensor adaptor.

This is the code, I'm using G++ 14.2 on a Ubuntu machine:

// compile with
//      g++ -I../../xtensor-stack test-xtensor3.cpp -o test-xtensor3
// from directory test-xtensor
//
// this should be the directory structure from the parent dir:
// .
// ├── bis
// │   └── test-xtensor
// └── xtensor-stack
//     ├── xframe
//     ├── xsimd
//     ├── xsimd-algorithm
//     ├── xtensor
//     ├── xtensor-blas
//     ├── xtensor-fftw
//     ├── xtensor-io
//     ├── xtensor-python
//     ├── xtensor-r
//     ├── xtensor-signal
//     ├── xtensor-sparse
//     └── xtl


#include <vector>
#include <cstddef>
#include <xtensor/xio.hpp>
#include <xtensor/xarray.hpp>
#include <xtensor/xadapt.hpp>
#include <xtensor/xstrides.hpp>


template <class value_type>
class bisimage {                    // image with a data buffer and xtensor access
  
public:
  using self        =    bisimage<value_type>;      // always good to know your type
  using buffer_type = std::vector<value_type>;      // type of the underlying buffer of data points
  
  // Ensuring that strides and shape types match the expected types for xtensor
  using shape_type = std::vector<std::size_t>;      // std::size_t is typically used for shape
  using strides_type = std::vector<std::ptrdiff_t>; // std::ptrdiff_t                for strides
  
protected:
  buffer_type _datavec     = { 0 }; // data buffer
  shape_type   datashape   = { 1 }; // shape for initialisation
  strides_type datastrides = { 1 }; // strides for initialisation

public:
  using data_type = xt::xarray_adaptor<buffer_type, xt::layout_type::dynamic, shape_type, xt::xtensor_expression_tag>;
  data_type data;                   // this is what the data looks like to the outside world

  bisimage() 
    : data( _datavec, datashape, datastrides ) {}   // default constructor: 1 data point, shape and strides as expected

  void data_info () {
    std::cout << "Address of _datavec: " << static_cast<void*> ( _datavec.data() ) << "\n";
    std::cout << "Address of data: "     << static_cast<void*> (     data.data() ) << "\n";
  }

  void import_vector(const std::vector<value_type>& rhs) {
      
    data_info();
    
    auto
      newsize = rhs.size();
    if ( newsize != data.size() ) {
      std::cerr << "WARNING: data re-sized to a 1D array of size " << newsize << "\n";
      _datavec.resize ( newsize );
      shape_type
        datashape0   = shape_type   { newsize };
      strides_type
        datastrides0 = strides_type { 1 };
      data = data_type( _datavec, datashape0, datastrides0 );
    }    
    std::copy(rhs.begin(), rhs.end(), _datavec.begin());
    
    std::cout << "_datavec contents:\n";
    for ( auto i:_datavec ) std::cout << i << " ";
    std::cout << "\n";
    data_info();
    
  }
    
  friend std::ostream& operator<< ( std::ostream& sout, const bisimage& output) {
    sout << output.data;
    return sout;
  }
  
};

int main() {
  
  bisimage <int>
    image;                          //  - new bisimage  
  std::vector <int>
    values { 42, 43 };              //  - make a vector of values

  image.import_vector ( values );   //  - and put them in

  std::cout << "image contains" << image << "\n";
  
  return 0;
  
}

When I print the underlying storage vector straight after copying, the new values are in there. But when I print the contents of the data adaptor via the xtensor interface, the new values have not been transferred (even though data's size has been adjusted):

Address of _datavec: 0x5d57381ed2b0
Address of data: 0x5d57381ed310
WARNING: data re-sized to a 1D array of size 2
_datavec contents:
42 43 
Address of _datavec: 0x5d57381ed7c0
Address of data: 0x5d57381ed370
image contains{0, 0}

More specifically, the pointer to the storage in the xarray::adaptor object is not the address of _datavec. I don't understand why it isn't:

After resetting the adaptor to use the new resized and refilled data vector, it should point to the data vector's contents? It looks like the adaptor object has its own vector of data (called m_storage) independent of _datavec.

It is also possible to change the contents of the adaptor directly, I have tried that as well. It is even possible to get the new values in the storage of the newly resized xtensor adaptor. But then the opposite happens: _datavec's values are not updated!

I thought the whole idea of the adaptor was that it referenced the container it adapts? Am I doing something wrong?

EDIT: passing the image by reference now to the << operator.

Upvotes: 0

Views: 56

Answers (1)

alle_meije
alle_meije

Reputation: 2480

Found it! (must admit the search was ChatGPT assisted)

I was using

using data_type = xt::xarray_adaptor<buffer_type, xt::layout_type::dynamic, shape_type, xt::xtensor_expression_tag>;

but it needed to be

using data_type = xt::xarray_adaptor<buffer_type&, xt::layout_type::row_major, shape_type, xt::xtensor_expression_tag>;

(row_major vs. dynamic was just because it was easier -no strides required- but the necessary change was buffer_type& not buffer_type).

The reason that I specified this by hand was that I thought that was necessary in the class definition. The automatic decltype approach did not give the by-reference type that (I think) is more in line with how xt::array_adaptor is supposed to behave.

Glad that, as with many of these riddles, it turned out to be something relatively simple.

Upvotes: 0

Related Questions