How std::reverse_iterator Works in C++
This article explains how the C++ Standard Template Library (STL)
implements std::reverse_iterator as an adaptor for
bidirectional iterators. It explores the off-by-one design pattern
required to map reverse ranges to standard ranges, details the internal
mechanics of dereferencing and incrementing, and clarifies how the
underlying base iterator interacts with operations like element
insertion and deletion.
In C++, std::reverse_iterator is an iterator adaptor
that inverts the direction of traversal for any underlying iterator
meeting at least the std::bidirectional_iterator
requirement. It allows algorithms designed for forward processing to
iterate backward through sequences without requiring specialized
backward algorithms.
The Off-by-One Relationship
The primary design challenge when reversing a range lies in the
asymmetry of standard iterator ranges: ranges are half-open, expressed
as [begin, end). The begin iterator points
directly to the first valid element, while end points one
past the final element.
If a reverse iterator simply stored an underlying iterator pointing
to the current element, rbegin() would have to wrap an
iterator pointing to the last valid element, and rend()
would have to point to one position before the first element. However,
in C++, decrementing an iterator past the beginning of a sequence (such
as decrementing container.begin()) yields undefined
behavior.
To solve this without undefined behavior,
std::reverse_iterator maintains the invariant that its
logical position corresponds to the element immediately
preceding the physical position of the underlying iterator.
rbegin()wrapsend().rend()wrapsbegin().
Positions: [ Element 0 ] [ Element 1 ] [ Element 2 ]
Iterators: ^ ^ ^ ^
begin() end()
rbegin().base()
Reverse access: *rend() *(rbegin()+1) *rbegin()
Dereferencing and Movement Operations
Because of this offset, the member operations of
std::reverse_iterator translate reverse operations into
inverted forward operations on the underlying iterator:
Dereferencing (
operator*): When dereferencing a reverse iterator, it does not access the element at the underlying iterator's current position. Instead, it accesses the element prior to it.reference operator*() const { Iterator temp = current; return *--temp; }When dereferencing
rbegin()(which wrapsend()), the temporary iterator is decremented to the last valid element, safely returning it.Increment (
operator++): Advancing a reverse iterator forward moves the traversal backward in the container. Therefore, prefix increment decrements the wrapped iterator:reverse_iterator& operator++() { --current; return *this; }Decrement (
operator--): Conversely, moving the reverse iterator backward advances the wrapped iterator:reverse_iterator& operator--() { ++current; return *this; }
The base() Member
Function
The underlying iterator can be extracted using the
.base() member function. Due to the shift mechanism,
rit.base() does not point to the element that
*rit evaluates to; it points to the element immediately
following it in forward order.
This distinction is critical when modifying containers:
Insertion (
insert): Callingcontainer.insert(rit.base(), value)places the new element at the position immediately beforerit.base(), which corresponds exactly to the position beforeritin the reverse sequence. No manual offset adjustment is needed.Erasure (
erase): Passingrit.base()directly tocontainer.erase()erases the element after the element pointed to by the reverse iterator. To erase the element currently referenced byrit, the base iterator must be decremented first:container.erase(std::next(rit).base()); // or container.erase(--rit.base());
By decoupling the physical position of the underlying iterator from
the logical element accessed, std::reverse_iterator
provides reverse navigation across standard ranges safely within the
bounds of defined iterator behavior.