I would have liked to do something like this:
template<typename T>
class RasterView {
P* data;
unsigned stride;
RasterView(T* data, unsigned stride)
: data(data)
, stride(stride)
{}
public:
T& operator()(int j, int i) {
return data[static_cast<unsigned long>(stride) * i j];
}
RasterView<T> subView(int j, int i) {
return RasterView<T>(data static_cast<unsigned long>(stride) * i j, stride);
}
// ...
}
template<typename T>
class Raster : public RasterView<T> {
std::vector<T> buffer;
public:
Raster(unsigned w, unsigned h, T defaultPixel)
: buffer(w*h, defaultPixel) // construct buffer first because we need...
, RasterView<T>(buffer.data(), w) // ... to pass this ptr to the base class constr
{}
// ...
}
but of course base class construction happens before member variable initialization, so the pointer buffer.data()
isn't available at the time the base class is being constructed. Does anyone have any advice or know of a trick I could use to work around this without adding too much complexity to the implementation?
CodePudding user response:
The straighforward solution is to move buffer
to a new base class, and inherit from it before RasterView
.
But I would rather turn RasterView
into a CRTP base class:
#include <cstddef>
#include <vector>
template <typename Derived, typename T>
class RasterView
{
public:
T &operator()(int j, int i)
{
auto buffer = static_cast<Derived *>(this)->data();
auto stride = static_cast<Derived *>(this)->stride();
return buffer[stride * i j];
}
};
template <typename T>
class Raster : public RasterView<Raster<T>, T>
{
std::vector<T> buffer;
std::size_t width;
public:
Raster(std::size_t w, std::size_t h, T value = {})
: buffer(w*h, value), width(w)
{}
T *data()
{
return buffer.data();
}
std::size_t stride() const
{
return width;
}
};
int main()
{
Raster<int> r(3,4,42);
r(1,2) = 3;
}
any advice for how to add back
RasterView::subView()
?
Like this:
#include <cstddef>
#include <vector>
template <typename T>
class RasterView;
template <typename Derived, typename T>
class RasterBase
{
public:
T &operator()(int j, int i)
{
auto buffer = static_cast<Derived *>(this)->data();
auto stride = static_cast<Derived *>(this)->stride();
return buffer[stride * i j];
}
RasterView<T> sub_view(int j, int i);
};
template <typename T>
class RasterView : public RasterBase<RasterView<T>, T>
{
T *ptr = nullptr;
std::size_t pixel_stride;
public:
RasterView(T *ptr, std::size_t pixel_stride) : ptr(ptr), pixel_stride(pixel_stride) {}
T *data()
{
return ptr;
}
std::size_t stride() const
{
return pixel_stride;
}
};
template <typename Derived, typename T>
RasterView<T> RasterBase<Derived, T>::sub_view(int j, int i)
{
auto buffer = static_cast<Derived *>(this)->data();
auto stride = static_cast<Derived *>(this)->stride();
return RasterView<T>(buffer stride * i j, stride);
}
template <typename T>
class Raster : public RasterBase<Raster<T>, T>
{
std::vector<T> buffer;
std::size_t width;
public:
Raster(std::size_t w, std::size_t h, T value = {})
: buffer(w*h, value), width(w)
{}
T *data()
{
return buffer.data();
}
std::size_t stride() const
{
return width;
}
};
int main()
{
Raster<int> r(3,4,42);
r(1,2) = 3;
RasterView<int> view = r.sub_view(1,1);
view(1,2) = 3;
}
CodePudding user response:
If you were to provide a default constructor to RasterView
(or simply adding default values here), than you could call call the constructor inside the Raster
constructor, effectively delaying the call:
template<typename T>
class RasterView {
public:
T* data;
unsigned stride;
RasterView(T* data = nullptr, unsigned stride = 0u) : data(data), stride(stride)
{
}
};
template<typename T>
class Raster : public RasterView<T> {
std::vector<T> buffer;
public:
Raster(unsigned w, unsigned h, T defaultPixel) : buffer(w* h, defaultPixel)
{
RasterView<T>::operator=(RasterView(buffer.data(), w));
}
};