Reputation: 2480
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
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