Константная оболочка для итератора
Изменить : добавлено разъяснение, почему я хочу это, и обновлен код, поскольку у меня еще нет ответов
У меня есть класс, подобный массиву С ++ 11, который (может быть) оберткой вокруг итератора с произвольным доступом. Доступ на основе индекса и .begin()/ .end()можно просто передать итератору, но есть небольшая сложность, когда объект const:
template<class Size, typename DataIterator>
class Storage : public Size {
DataIterator iterator;
public:
Storage(const DataIterator &iterator, const Size &size) : Size(size), iterator(iterator) {}
auto operator[](index_t i)
-> decltype(iterator[i]) {
return iterator[i];
}
auto operator[](index_t i) const
-> MakeConst<decltype(iterator[i])> {
return iterator[i];
}
DataIterator begin() {return iterator;}
ConstWrapper<DataIterator> begin() const {return iterator;}
DataIterator end() {return iterator + this->size();}
ConstWrapper<DataIterator> end() const {return iterator + this->size();}
};
Если мы просто вернемся DataIteratorиз constверсии .begin()и .end(), то люди, владеющие a, Storage const &смогут (неправильно!) Изменить массив с помощью этого итератора.
У контейнеров вроде std::vectorесть два отдельных итератора ( ::iteratorи ::const_iterator), но у нашего Storageкласса есть только один для чтения и записи, поэтому мы синтезируем один, используя ConstWrapper:
template <typename Iterator>
class ConstWrapper {
Iterator iterator;
using traits = std::iterator_traits<Iterator>;
public:
using difference_type = typename traits::difference_type;
using value_type = typename traits::value_type;
using pointer = ConstWrapper;
using reference = MakeConst<typename traits::reference>;
using iterator_category = typename traits::iterator_category;
ConstWrapper() {}
ConstWrapper(const Iterator &iterator) : iterator(iterator) {}
// The problematic cases:
auto operator[](index_t i) const
-> MakeConst<decltype(iterator[i])> {
return iterator[i];
}
auto operator*() const
-> MakeConst<decltype(*iterator)> {
return *iterator;
}
bool operator!= (const ConstWrapper& other) const {
return iterator != other.iterator;
}
/** All the other random-access iterator methods **/
};
// Specialisation to prevent infinite loops
template <typename Iterator>
class ConstWrapper<ConstWrapper<Iterator>> : ConstWrapper<Iterator> {
public:
using ConstWrapper<Iterator>::ConstWrapper;
};
Реализация пересылает все соответствующие методы и имеет специализацию, ConstWrapper<ConstWrapper<...>>так что она не может обернуть себя.
Ключевая часть MakeConst, которая превращает типы в правильный constвариант (в отличие от простого добавления const, которое не влияет на ссылки):
// Converts (T & -> T const &), and (T -> const T)
using MakeConst = typename std::conditional<
std::is_reference<T>::value,
typename std::remove_reference<T>::type const &,
const T
>::type;
Есть ли в этом смысл и нормально ли это? Есть ли что-то еще, что я мог бы сделать более читабельным / эффективным / и т. Д.?
Благодаря!
Ответы
Я не думаю, что ваша специализация - делать то, что вы думаете. https://godbolt.org/z/Gfh7sv
Вы не хотите , ConstWrapper<ConstWrapper<Iterator>>чтобы наследовать от ConstWrapper<Iterator> ; это означало бы, что CW<CW<I>>"это что-то вроде" CW<I>, что не соответствует действительности. Здесь вам не нужны отношения наследования. То , что вы хотите , это просто для специализаций , Storageчтобы не завернуть DataIterator сек , что случается, специализации ConstWrapperуже. Вы делаете это с помощью дополнительного уровня косвенного обращения к псевдонимам:
template<class T> struct maybe_constwrap { using type = ConstWrapper<T>; };
template<class U> struct maybe_constwrap<ConstWrapper<U>> { using type = ConstWrapper<U>; };
template<class Size, typename DataIterator>
class Storage : public Size {
public:
using iterator = DataIterator;
using const_iterator = typename maybe_constwrap<DataIterator>::type;
~~~
};
Теперь, если DataIteratorэто уже a ConstWrapper, тогда оба iteratorи const_iteratorбудут буквально одного типа ... что вам и нужно.
Вы , MakeConstкажется, основывается на сомнительной идее , что const _Bit_referenceне собирается быть назначаемыми к. Это, вероятно, изменится в C ++ 23. Видетьhttps://stackoverflow.com/questions/63412623/should-c20-stdrangessort-not-need-to-support-stdvectorbool для некоторого контекста.
Вы повторяете метапрограммирование в возвращаемых типах operator*и operator[]. Они должны просто вернуть тип, referenceкоторый вы уже вычислили выше.
reference operator*() const { return *it_; }
Между прочим, я меняю имя вашего элемента данных с iteratorна it_, потому что подозреваю, что с точки зрения концепций, иметь контейнерный тип класса с членом с именем, iteratorчлен которого (A) не является общедоступным, и (B ) не тип.
Вам, operator!=вероятно, следует использовать идиому скрытого друга:
friend bool operator==(ConstWrapper a, ConstWrapper b) { return a.it_ == b.it_; }
friend bool operator!=(ConstWrapper a, ConstWrapper b) { return a.it_ != b.it_; }
В C ++ 20 теоретически можно было опустить operator!=и предоставить operator==только. Я пока не знаю, хорошая ли это идея.
Передача ConstWrapperпо значению должна быть хорошей, потому что она содержит только итератор, а итераторы дешево копировать.