+-- 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
v.rbegin().base()
; I have to remember subtracting 1 first, *(v.rbegin().base() - 1)
,v.rend().base() - 1
exactly because I can't dereference v.rend()
.What if the design was that &*rit == &*rit.base()
?
v.rend().base()
, true, but that simply corresponds to not being able to dereference v.rend().base() - 1
in the current design;v.end()
directly from a reverse iterator, not even the closest one, b.rbegin()
, but that simply corresponds to the - 1
we have to add to rit.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,- 1
is not generally needed in the alternative designand inconvenience
rit.base()
s in the actual design,+1
v.rbegin()
to get v.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()
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.