Каковы правила, регулирующие постоянство возвращаемого значения `operator*` для InputIterator и OutputIterator?

Я определяю итераторы для пользовательского контейнера. Итераторы реализуют концепции InputIterator и OutputIterator.

Какие типы следует использовать для iterator::reference и const_iterator::reference? И какие типы должен возвращать operator* для iterator, const iterator и const_iterator?

В случае iterator должны ли быть два определения operator*? следующим образом (T — тип содержащегося значения):

template<typename T>
class iterator {
public:
using reference = T&;
...
T& operator*() { return ...reference to the underlying storage... }
const T& operator*() const { return ...const reference to the underlying storage... }
...
};

Или только один:

T& operator*() const { return ...reference to the underlying storage... }

Иными словами, разрешает ли const iterator изменяемый доступ к базовому контейнеру?

Правильно ли я предполагаю, что для случая const_iterator верно следующее?

template<typename T>
class const_iterator {
public:
using reference = const T&;
...
const T& operator*() const { return ...reference to the underlying storage... }
...
};

Или определение const_iterator::reference должно быть T& без const?

В дополнение к полному ответу я был бы признателен за ссылку на авторитетный источник, где я могу найти аналогичную информацию в будущем.


person Ross Bencina    schedule 10.06.2017    source источник
comment
const iterator не будет предварительно увеличиваться, так как это const. Так что это победит цель   -  person Curious    schedule 10.06.2017
comment
@ Любопытно, я не уверен в этом. Например, итерационный цикл использует итератор, отличный от const, но передает его в качестве параметра функции, которая принимает const iterator&.   -  person Ross Bencina    schedule 10.06.2017
comment
Можете ли вы привести пример того, что вы имеете в виду? вы всегда можете преобразовать значение в его const аналог   -  person Curious    schedule 10.06.2017
comment
@ Любопытно, вопрос вполне ясен: если у вас есть const iterator i, какой тип *i*?   -  person Ross Bencina    schedule 10.06.2017
comment
Я не думаю, что понимаю, что вы имеете в виду, извините .. *i для обычного iterator, а не const_iterator обычно оценивается как неконстантная ссылка на базовый тип. Но вы это уже знали?   -  person Curious    schedule 10.06.2017
comment
@любопытно, есть и третий случай: когда i имеет тип const iterator. вопрос: какой тип *i в этом случае.   -  person Ross Bencina    schedule 10.06.2017
comment
Это вопрос, который вы хотели задать в вопросе выше? Если да, то я напишу ответ, чтобы объяснить   -  person Curious    schedule 10.06.2017
comment
@ Любопытно, что вопрос содержит несколько конкретных деталей, на которые необходимо ответить, все они сосредоточены вокруг вопроса об определении operator*, iterator::reference и const_iterator::reference. Если вы не чувствуете, что вопрос достаточно ясен, чтобы ответить на него, я думаю, будет лучше, если вы не будете пытаться.   -  person Ross Bencina    schedule 10.06.2017
comment
Хорошо, я напишу ответ   -  person Curious    schedule 10.06.2017
comment
Готово, посмотрите и не стесняйтесь публиковать дополнения, если что-то все еще неясно   -  person Curious    schedule 10.06.2017


Ответы (2)


Иными словами, разрешает ли константный итератор изменяемый доступ к базовому контейнеру?

Да. Итератор const не является итератором const; то, что есть const, есть само по себе, а не объект, на который оно указывает. (Это аналог указателя const, то есть T* const или const std::unique_ptr<T>.)

Или определение const_iterator::reference должно быть T& без const?

Он должен вернуть const T&. const_iterator является итератором для const; это означает, что то, на что он указывает, не должно быть разрешено изменять. (Это аналог указателя на const, то есть const T* или std::unique_ptr<const T>.)

person songyuanyao    schedule 10.06.2017
comment
демонстрация для объяснения - person songyuanyao; 10.06.2017

Какие типы следует использовать для iterator::reference и const_iterator::reference?

Если вы хотите узнать, какой тип следует присвоить своим типам итераторов (iterator::reference, iterator::pointer и т. д.), вы можете попробовать имитировать то, что делает std::vector, следующая программа после компиляции расскажет вам все, что вам нужно знать (демонстрация в реальном времени)

#include <vector>

template <typename...>
struct WhichType;

int main() {
    auto vec = std::vector<int>{};
    WhichType<typename decltype(vec.begin())::difference_type>{};
    WhichType<typename decltype(vec.begin())::value_type>{};
    WhichType<typename decltype(vec.begin())::pointer>{};
    WhichType<typename decltype(vec.begin())::reference>{};
    WhichType<typename decltype(vec.begin())::iterator_category>{};

    WhichType<typename decltype(vec.cbegin())::difference_type>{};
    WhichType<typename decltype(vec.cbegin())::value_type>{};
    WhichType<typename decltype(vec.cbegin())::pointer>{};
    WhichType<typename decltype(vec.cbegin())::reference>{};
    WhichType<typename decltype(vec.cbegin())::iterator_category>{};
}

какие типы должен возвращать оператор* для итератора, константного итератора и const_iterator?

operator* для обычных итераторов, отличных от const_, должна возвращать изменяемую ссылку на базовый тип, в этом нет двусмысленности, если семантика контейнера это допускает. Например, итераторы std::map возвращают постоянные ссылки на тип ключа, потому что ключ не должен изменяться в std::map (см. https://stackoverflow.com/a/32510343/5501675)

const iterator передает то же значение, что и T* const, const std::unique_ptr<int>, они представляют константные указатели на неконстантные данные. Таким образом, вы можете использовать указатель для изменения данных, на которые он указывает, но вы не можете изменить сам указатель. В этом случае итератор действует как указатель. Таким образом, вы не можете изменить итератор, но вы можете изменить то, на что он указывает (демонстрация в реальном времени)

Вследствие того, что итератор равен const, вы не можете вызвать operator++() (или оператор преинкремента), чтобы переместить итератор на следующую позицию. Вы не можете этого сделать, потому что метод не является константой. Из-за этого большая часть кода никогда не использует итераторы, которые сами по себе являются константами. Вам лучше использовать константную ссылку на базовый тип.

const_iterator определено для решения проблемы, когда итераторы возвращают константные ссылки на базовый тип. Таким образом, они возвращают константные ссылки на базовый тип. Демо

Что касается того, какие typedefs он должен иметь, первый пример кода при компиляции должен сказать вам, что

разрешает ли итератор const изменяемый доступ к базовому контейнеру?

Да, если есть контейнер. Например, std::vector<int> разрешает, тогда как std::map<int, int> не разрешает изменяемый доступ к своим типам ключей.

Или определение const_iterator::reference должно быть T& без const?

Посмотрите живую демонстрацию выше, когда вы скомпилируете код, вы увидите, что это const T&

person Curious    schedule 10.06.2017
comment
Четкий и прямой ответ на вопрос был бы лучше, чем следующая программа, когда она скомпилирована, расскажет вам все, что вам нужно знать. С другой стороны, пример кода дает частичный ответ на мой вопрос об авторитетных источниках, поэтому он имеет очевидную ценность. (Хотя я бы предпочел цитирование соответствующего стандарта.) - person Ross Bencina; 13.06.2017