Думаю, я знаю ответ на этот вопрос, но был бы признателен за проверку здравомыслия.
Применяется ли аннулирование итератора к std::back_insert_iterators?
#include <cassert>
#include <iterator>
#include <vector>
int main() {
auto v = std::vector<int>{ 0, 1, 2 };
auto iter = std::back_inserter(v);
*iter++ = 3;
v.clear(); // invalidates iterators, but
*iter++ = 4; // back_insert_iterator is special?
assert(v.size() == 1 && v[0] == 4);
return 0;
}
Этот код работает для меня, потому что реализация std::back_insert_iterator от моего поставщика не содержит итератора (или указателя или ссылки). Он просто вызывает метод push_back контейнера.
Но требует ли стандарт такой реализации? Может ли back_insert_iterator другого поставщика содержать и поддерживать итератор «один за конец», который можно использовать при вызове метода вставки контейнера? Кажется, это соответствует требованиям. Эта разница, конечно, состоит в том, что оно может быть признано недействительным.
Я знаю, что cppreference.com не является авторитетным, но он более доступен, чем стандартный.
[Метод очистки вектора] [i]nпроверяет любые... итераторы, ссылающиеся на содержащиеся элементы. Любые итераторы, прошедшие мимо конца, также становятся недействительными. [cppreference.com, курсив добавлен]
std::back_insert_iterator может быть дочерним элементом итератора прошедшего конца.
@NathanOliver: это =
, который вызывает push_back
, *
тоже не работает.
@Jarod42 Jarod42 Ага, да, *
для обычных итераторов ввода.
«std::back_insert_iterator может быть дочерним элементом итератора, прошедшего конец». -- Не согласно cppreference на странице std::end (курсив добавлен): «Возвращает c.end()
, который обычно представляет собой итератор, следующий за концом последовательности, представленной c
». Во фразе используется слово «конец», как, вероятно, использовал бы человек до изучения итераторов; он рассматривает «конец» как последний элемент, а не как результат вызова end()
. Фразу было бы точнее сказать «элемент, прошедший за конец», но эта формулировка непопулярна.
Может ли back_insert_iterator
другого поставщика содержать и поддерживать итератор «один за концом», который можно использовать с вызовом метода вставки контейнера? Кажется, это соответствует требованиям. - Как бы это работало, если бы back_inserter
вызывали дважды подряд? В этом отношении std::inserter
более интересен — вы передаете ему итератор.
@Evg: Схема *it++ = value;
. Простая реализация рассматривает приращение как неактивное. Реализация, которая «удерживает и поддерживает» итератор, фактически будет выполнять приращение.
namespace std {
template<class Container>
class back_insert_iterator {
protected:
Container* container;
public:
using iterator_category = output_iterator_tag;
using value_type = void;
using difference_type = ptrdiff_t;
using pointer = void;
using reference = void;
using container_type = Container;
constexpr explicit back_insert_iterator(Container& x);
constexpr back_insert_iterator& operator=(const typename Container::value_type& value);
constexpr back_insert_iterator& operator=(typename Container::value_type&& value);
constexpr back_insert_iterator& operator*();
constexpr back_insert_iterator& operator++();
constexpr back_insert_iterator operator++(int);
};
}
constexpr explicit back_insert_iterator(Container& x);
1 Эффекты: инициализирует
container
с помощьюaddressof(x)
.
constexpr back_insert_iterator& operator=(const typename Container::value_type& value);
2 эффекта: Как будто автор:
container->push_back(value);
3 возврата:
*this
.
constexpr back_insert_iterator& operator=(typename Container::value_type&& value);
4 эффекта: Как будто автор:
container->push_back(std::move(value));
5 Возвратов:
*this
.[...]
Как мы видим, итератор получает указатель на контейнер, а затем push_back
вызывается непосредственно в контейнере, поэтому никаких шансов на UB нет.
итератор обратного вставки не ссылается ни на какие элементы вектора, он просто ссылается на вектор.
*
вызываетpush_back
и++
не работает: en.cppreference.com/w/cpp/iterator/back_insert_iterator