c++stliteratorlanguage-lawyerreverse-iterator

Why is reverse_iterator::base offset?


      +-- v.begin()           +-- v.end()
      |                       |
      v                       v
    +---+---+---+---+---+---+ - +
    | o | o | o | o | o | o | x |
    +---+---+---+---+---+---+ - +

+ - +---+---+---+---+---+---+
| x | o | o | o | o | o | o |
+ - +---+---+---+---+---+---+
  ^                       ^
  |                       |
  +-- v.rend()            +-- v.rbegin()

(ASCII copied, and edited, from this answer, which is what actually prompted me to ask the present question.)

I do see the advantage of having that &*rit == &*(rit.base() - 1), because this way I can take the rit.base() for any reverse iterator rit and I'll always get a valid iterator.

But at the same time

What if the design was that &*rit == &*rit.base()?

What I mean is that it seems to me that whether the design decision was that &*rit == &*(rit.base() - 1) (as it was) or that &*rit == &*rit.base(), we'd have had the same amount of convenience

and inconvenience

just in opposite situations.

So my question is: is there a definitive advantage in making the choice that has indeed been made? Or was it just a flipped coin?


I see that there's no valid iterator before the first element, but that's my point. When reverse iterators were introduced, what would have been wrong with making std::prev(v.begin()) a valid, non-dereferenceable iterator just like v.end()?


In hindsight, I do see one unquestionable advantage.

I didn't see the advantage of v.rbegin().base() == v.end()/v.rend().base() == v.begin() because why would I want to create v.end()/v.begin() from their reverse counterparts?

But if I have two reverse iterators rit1 and rit2 that define the range (rit1, rit2], then taking it1 = rit1.base() and it2 = rit2.base() allows to refer easily to the same range of elements in the opposite direction, [it1, it2).

Long story short: _I should have read §9.4.1 from The C++ Standarl Library - 2nd ed. first.


Solution

  • It is not a design decision, it is a necessity.

    A reverse iterator is not a magical thing that is somehow able to iterate a range in reverse. It is a facade that builds on top of things that are already present.

    When you have a collection with 6 entries (like in your picture), then all you have is exactly 7 usable iterator values, nothing more (6 are dereferencable, one is the end iterator value). That is all that the reverse iterator can build on.

    Since there is no one-before-the-beginning iterator (like the second picture makes one think), there is no other option for the reverse iterator than to map rbegin() to end() and rend() to begin(). That is, you have (rbegin() + i).base() == end() - i

    That, in turn, necessitates that the dereferencable iterators (the first 6 starting at rbegin()) must actually dereference iterator .base()-1. There is no other way to implement a reverse iterator.