Question

I'm working on a image-class which makes it possible to work with images with different pixel layouts (RGB, RGBA, Gray, Bayer, ...). To access a pixel it's possible to call image.at<PixelType>(x,y) which returns an "Accessor". The concrete Accessor-implementation is dependent on the template argument. But now i've ran into some issues regarding const correctness.

Here is a very dumbed down implementation which hopefully makes it apparent:

template<bool constAccessor>
class Accessor {
public:
    typedef typename boost::mpl::if_c<constAccessor, const int, int>::type DataType;

    Accessor(DataType& data) 
    :data(data) {
    }

    Accessor(Accessor<false>& other) 
    : data(other.data) {
    }

    DataType& data;
};


class Image {
public:
    Accessor<false> at(unsigned int x, unsigned int y) {
        return Accessor<false>(data);
    }
    Accessor<true> at(unsigned int x, unsigned int y) const {
        return Accessor<true>(data);
    }

private:
    int data;
};

int main() {
    Image img;
    const Image& cimg = img;

    // get accessor which is non-const
    Accessor<false> a1 = img.at(0, 0); 

    // get a accessor which is const...
    Accessor<true> a2 = a1;
    // ... modifying a value results in an error
    a2.data = 42;

    // try to convert a accessor which is const to a non-const version
    // ... results in an error
    Accessor<false> a3 = a2;

    return 0;
}

As you can see there is a non-const and a const implementation of the at method. Depending on the constness the template argument of the accessor is set to true or false. But now i've got two different types (const and non-const) per accessor/pixel type which makes it necessary to write a conversion constructor because otherwise the test-cases shown in the main() function wouldn't work.

Now the question is: Is there a better way to achieve this? It feels kind of bad to use a template argument as const indicator. It would be much nicer to just use Accessor and const Accessor. On the other hand this is similar to what the std-library does with ::iterator and ::const_iterator. Anyone has some experience with such situations?

Was it helpful?

Solution

You can (over-)generalise your parameter from having the two boolean states to any value type instead:

template<typename Value>
class Accessor {
public:    
    Accessor(Value& data) 
        : data(data)
    {}

    template<typename T, EnableIf<std::is_convertible<T&, Value&>>...>
    Accessor(Accessor<T> const& other)
        : data(other.data)
    {}

    Value& data;
};

Obviously this isn’t any different than what you have, except under another guise: instead of Accessor<false> and Accessor<true>, you have Accessor<DataType> and Accessor<DataType const>.

The benefit is familiarity: all of e.g. std::unique_ptr<T>, std::shared_ptr<T>, std::reference_wrapper<T> (and even T*) behave the same way. In particular, this famialiarity should hopefully extend to compiler errors about a lack of conversion from Accessor<DataType const> to Accessor<DataType>, much like you can’t convert from int const* to int*.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top