+-- 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
- I cannot dereference
v.rbegin().base(); I have to remember subtracting 1 first,*(v.rbegin().base() - 1), - and I cannot dereference
v.rend().base() - 1exactly because I can't dereferencev.rend().
What if the design was that &*rit == &*rit.base()?
- We couldn't call
v.rend().base(), true, but that simply corresponds to not being able to dereferencev.rend().base() - 1in the current design; - We wouldn't be able to get
v.end()directly from a reverse iterator, not even the closest one,b.rbegin(), but that simply corresponds to the- 1we have to add torit.base()in the current design to get a forward iterator to the same element as the reverse.
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
rit.base()is always ok in the actual design,- 1is not generally needed in the alternative design
and inconvenience
- can't dereference all valid
rit.base()s in the actual design, - need to
+1v.rbegin()to getv.end()in the alternative design,
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.
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()toend()andrend()tobegin(). That is, you have(rbegin() + i).base() == end() - iThat, 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.