Я работаю над классом изображений, который позволяет работать с изображениями с разной компоновкой пикселей (RGB, RGBA, Gray, Bayer,...). Для доступа к пикселю можно вызвать image.at<PixelType>(x,y)
который возвращает "Accessor". Конкретная реализация Accessor зависит от аргумента шаблона. Но теперь я столкнулся с некоторыми проблемами относительно корректности const.
Вот очень неуклюжая реализация, которая, надеюсь, делает это очевидным:
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;
}
Как вы можете видеть, существует неконстантная и константная реализация метода at
. В зависимости от константы аргумент шаблона для accessor устанавливается в true
или false
. Но теперь у меня есть два разных типа (const и non-const) для типа accessor/pixel, из-за чего необходимо написать конструктор преобразования, потому что в противном случае тестовые примеры, показанные в функции main()
, не будут работать.
Теперь вопрос: есть ли лучший способ достичь этого? Мне кажется, что плохо использовать аргумент шаблона в качестве индикатора константы. Было бы гораздо приятнее использовать Accessor
и const Accessor
. С другой стороны, это похоже на то, что делает std-библиотека с ::iterator
и ::const_iterator
. У кого-нибудь есть опыт в таких ситуациях?
Вы можете (over-) обобщить ваш параметр от наличия двух булевых состояний для любого типа значения:
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;
};
Очевидно, это не отличается от того, что у вас есть, кроме как под другим видом: вместо Accessor<false>
и Accessor<true>
вас есть Accessor<DataType>
и Accessor<DataType const>
.
Преимущество - знакомство: все, например, std::unique_ptr<T>
, std::shared_ptr<T>
, std::reference_wrapper<T>
(и даже T*
) ведут себя одинаково. В частности, эта famialiarity, вероятно, должна распространяться на ошибки компилятора, связанные с отсутствием преобразования из Accessor<DataType const>
в Accessor<DataType>
, так же, как вы не можете преобразовать из int const*
в int*
.
iterator
иconst_iterator
, заставляя программиста думать о том, какой итератор использовать. К сожалению, программисты редко думают об этом и используют обычныйiterator
даже когдаconst_iterator
будет лучшим.