c++constructorc++20initializer

Workaround for data member initialization happening after base class construction


I would have liked to do something like this:

template<typename T>
class RasterView {
    T* data;
    unsigned stride;
    RasterView(T* data, unsigned stride)
        : data(data)
        , stride(stride)
    {}
public:
    T& operator()(int j, int i) { // hot path
        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?


Solution

  • 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;
    }