Почему ranges::basic_istream_view::begin() не кэшируется?

Я обнаружил, что c++20 ranges::basic_istream_view немного отличается от range-v3.

Самое важное отличие состоит в том, что std::ranges::basic_istream_view не кэширует свой begin(), так что каждый begin() будет возвращать следующий итератор со значением, которое было прочитано (богболт):

auto words = std::istringstream{"today is yesterday's tomorrow"};
auto view = std::ranges::istream_view<std::string>(words);
std::cout << *view.begin() << "\n"; // today
std::cout << *view.begin() << "\n"; // is
std::cout << *view.begin() << "\n"; // yesterday's
std::cout << *view.begin() << "\n"; // tomorrow

Рассмотрим следующее (godbolt). Если я использую версию range-v3, все три std::ranges::find() будут найдены "is", но если я использую стандартную версию, "is" будет найден только при первом вызове.

auto words = std::istringstream{"today is yesterday's tomorrow"};
auto view = std::ranges::istream_view<std::string>(words);
std::cout << *std::ranges::find(view, "is") << "\n"; // is
std::cout << *std::ranges::find(view, "is") << "\n"; // tomorrow
std::cout << *std::ranges::find(view, "is") << "\n"; // tomorrow

Почему стандарт выбрал дизайн, отличный от range-v3? Есть ли потенциальный дефект, если begin() кэшируется?


person 康桓瑋    schedule 14.05.2021    source источник


Ответы (2)


В определении концепции range в [range.range] мы имеем :

template<class T>
  concept range =
    requires(T& t) {
      ranges::begin(t);   // sometimes equality-preserving (see below)
      ranges::end(t);
    };

где см. ниже части (выделено мной):

Учитывая выражение t такое, что decltype((t)) равно T&, T модели варьируются только в том случае, если

  • ...
  • если тип ranges​::​begin(t) моделей forward_­iterator, ranges​::​begin(t) сохраняет равенство.

[Примечание 1. Сохранение равенства ranges​::​begin и ranges​::​end позволяет передавать диапазон, чьи модели типов итераторов forward_­iterator, нескольким алгоритмам и выполнять несколько проходов по диапазону путем повторных вызовов ranges​::​begin и ranges​::​end. Поскольку ranges​::​begin не обязан сохранять равенство, когда тип возвращаемого значения не моделирует forward_­iterator, повторные вызовы могут не возвращать одинаковые значения или быть нечетко определенными. — примечание в конце]

Для прямых диапазонов вы можете многократно вызывать ranges::begin(r) и ожидать один и тот же ответ (это сохраняет равенство). Для некоторых диапазонов требуется кэширование.

Но для диапазонов только для ввода (например, istream_view) вы можете вызвать ranges::begin(r) ровно один раз, и нет никаких гарантий того, что произойдет при втором вызове.

Таким образом, разница между двумя реализациями не заметна для действительной программы, поскольку, вызывая begin несколько раз, вы уже нарушаете здесь предварительные условия.


Чтобы получить более конкретный ответ относительно istream_view, реализация range-v3 также не кэширует begin(). Это не та разница, что происходит. И действительно, вы все равно не можете кэшировать входные итераторы, так как они сразу же будут признаны недействительными.

Разница скорее когда мы читаем первое значение из потока:

  • в range-v3, это происходит в конструкторе istream_view.
  • в libstdc++, это происходит при вызове begin().

Последнее больше соответствует общей модели диапазонов, в которой создание адаптеров диапазона фактически не выполняет никакой работы.

person Barry    schedule 14.05.2021

входные итераторы таковы, что, как только вы разыменовали один из них, вам нужно немедленно увеличить его. То же самое и с итераторами вывода, такими как back_insert_iterator. Это то, что вы просто не должны делать. Если вам нужно кэшировать первое значение, кэшируйте его сами.

Причина, по которой итераторы ввода и вывода необходимо увеличивать после разыменования, заключается в том, что они предназначены для однократного прохода. Если вы прочитали что-то из потока, вы не можете прочитать это снова. Оператор * фактически читает из потока. Что делает ++? Ничего такого! То же самое касается back_insert_iterator.

person Armen Tsirunyan    schedule 14.05.2021