简体   繁体   中英

const-correctness of data-accessor types - better solution?

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?

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 eg 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* .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM