0

I remember having read that the motivation for [forward.iterators]/6 (requiring that, given two iterators a and b, a == b if and only if *a and *b are bound to the same object) was to support reverse_iterators. Am I remembering correctly?

cppreference.com notes that

For a reverse iterator r constructed from an iterator i, the relationship &*r == &*(i-1) is always true (as long as r is dereferenceable); thus a reverse iterator constructed from a one-past-the-end iterator dereferences to the last element in a sequence.

and also:

std::reverse_iterator does not work with iterators whose dereference returns a reference to a member of *this (so-called "stashing iterators"). An example of a stashing iterator is std::filesystem::path::iterator.

However, it is also stated that:

std::reverse_iterator is an iterator adaptor that reverses the direction of a given iterator, which must be at least a LegacyBidirectionalIterator or model bidirectional_iterator (since C++20).

which is exactly what the standard says.

If I’m not mistaken, C++20 bidirectional_iterators no longer require [forward.iterators]/6. For example, std::ranges::iota_view::iterator is a std::random_access_iterator but also a stashing iterator. Therefore, I don’t understand why std::bidirectional_iterator is a sufficient requirement for std::reverse_iterator to work if the above claim about stashing iterators is true.

Indeed, the following program displays the expected output:

#include <iostream>
#include <ranges>

int main()
{
  auto v = std::views::iota(0u, 10'000ul) | std::views::take(10);

  for (auto it = std::make_reverse_iterator(v.end());
       it != std::make_reverse_iterator(v.begin()); ++it)
    std::cout << *it << std::endl;
}

There isn’t really much point in using std::reverse_iterator with C++20 iterators, since we have std::ranges::reverse_view, but I’m curious to know whether [forward.iterators]/6 is currently necessary at all.

metalfox
  • 6,301
  • 1
  • 21
  • 43
  • *"If I’m not mistaken, C++20 bidirectional_iterators no longer require [forward.iterators]/6."* - The link you mentioned says the opposite: both the `bidirectional_­iterator` and the *Cpp17BidirectionalIterator* extend forward iterator concepts. – Holt Jul 20 '21 at 09:32
  • Why is `iota_view::iterator` a stashing iterator? – 康桓瑋 Jul 20 '21 at 09:39
  • @Holt See the note at the end of this page: https://en.cppreference.com/w/cpp/iterator/forward_iterator – metalfox Jul 20 '21 at 09:40
  • 1
    @康桓瑋 because it doesn't return a reference to an element in the range but a copy of an integer stored in the iterator itself. – metalfox Jul 20 '21 at 09:43
  • @metalfox. "*std::reverse_iterator does not work with iterators whose dereference returns a reference to a member of *this (so-called "stashing iterators").*" But what `iota_view::iterator` returns is *not* a reference but a prvalue, so it is not satisfied the definition of stashing iterators from the beginning, is it? Or is there something I missed? – 康桓瑋 Jul 20 '21 at 09:58
  • @康桓瑋 It needs not to be a reference. Stashing iterators cache the thing being iterated within themselves. – metalfox Jul 20 '21 at 10:24
  • Is "whose dereference returns a reference" not clear enough to you? I'm happy to change cppreference if you think it suggest that the dereference can return a copy. – cpplearner Jul 20 '21 at 10:42
  • @cpplearner That's clear. It was *"the relationship `&*r == &*(i-1)` is always `true`"* what confused me. – metalfox Jul 20 '21 at 12:11

1 Answers1

0

The note about stashing iterators is not about l-value vs r-value return from *, but instead about the multi-pass guarantee.

std::forward_iterator still has the semantic requirement "((void)[](auto x){ ++x; }(i), *i) is equivalent to *i", which is met by decltype(v.begin()), but is not (required to be) met by std::filesystem::path::(const_)iterator.

const_iterator a constant LegacyBidirectionalIterator with a value_type of path, except that for dereferenceable iterators a and b of type path::iterator with a == b, there is no requirement that *a and *b are bound to the same object

N.b. std::reverse_view is (for some expressions) defined to return a ranges::subrange<reverse_iterator<...>>, so it is still relevant.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • So, is the statement *"the relationship `&*r == &*(i-1)` is always true"* out of date? I believe there was similar wording in the standard but I couldn't find it in the current draft. – metalfox Jul 20 '21 at 10:16
  • 1
    @metalfox in the sense that `*i` (and thus `*r`) are permitted to be rvalues, so that expression might be ill-formed, yes – Caleth Jul 20 '21 at 10:21