Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is ranges::basic_istream_view::begin() not cached?

I found that c++20 ranges::basic_istream_view is slightly different from the range-v3 version.

The most important difference is that the std::ranges::basic_istream_view does not cache its begin(), so that each begin()s will return the next iterator with the value that has been read (godbolt):

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

Consider the following (godbolt), If I use the range-v3 version, all the three std::ranges::find()s will found "is", but if I use the std version, "is" will be found only in the first call.

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

Why did the standard choose a different design from range-v3? Is there any potential defect if begin() is cached?

like image 886
康桓瑋 Avatar asked Jan 25 '23 09:01

康桓瑋


2 Answers

In the definition of the range concept, in [range.range], we have:

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

where the "see below" parts are (emphasis mine):

Given an expression t such that decltype((t)) is T&, T models range only if

  • ...
  • if the type of ranges​::​begin(t) models forward_­iterator, ranges​::​begin(t) is equality-preserving.

[Note 1: Equality preservation of both ranges​::​begin and ranges​::​end enables passing a range whose iterator type models forward_­iterator to multiple algorithms and making multiple passes over the range by repeated calls to ranges​::​begin and ranges​::​end. Since ranges​::​begin is not required to be equality-preserving when the return type does not model forward_­iterator, it is possible for repeated calls to not return equal values or to not be well-defined. — end note]

For forward ranges, you can call ranges::begin(r) repeatedly and expect the same answer (it is equality-preserving). For some ranges, that requires caching.

But for input-only ranges (like istream_view), you can call ranges::begin(r) exactly one time, and there are no guarantees for what happens on the second invocation.

So the difference between the two implementations isn't observable by a valid program, since by calling begin multiple times you're already violating the preconditions here.


For a more specific answer as to istream_view, range-v3's implementation does cache not begin() either. That's not the difference that's happening. And indeed, you cannot cache input iterators anyway, since they would immediately be invalidated.

The difference is rather when we read the first value from the stream:

  • in range-v3, that happens in the constructor of istream_view.
  • in libstdc++, that happens in the call to begin().

The latter is more consistent with the general Ranges model where constructing range adaptors does not actually do any work.

like image 143
Barry Avatar answered Jan 26 '23 23:01

Barry


input iterators are such that as soon as you have dereferenced one, you need to instantly increment it. The same is with output iterators, such as back_insert_iterator. This is something that you are just not supposed to be doing. If you need the first value cached, cache it yourself.

The reason input and output iterators need to be incremented after dereferencing is that they are by design single pass.If you have read something from a stream, you cannot read it again. Operator * actually reads from the stream. What does ++ do? Nothing! Same goes for back_insert_iterator

like image 39
Armen Tsirunyan Avatar answered Jan 26 '23 21:01

Armen Tsirunyan