Iterators recap
Sean Kelly
sean at f4.ca
Wed Dec 27 16:56:13 PST 2006
Oskar Linde wrote:
>
> Now to bidirectional iterators.
>
> Separating R, T, S as three different methods, here are two ways a
> bidirectional iterator could be implemented:
>
> The value-style:
>
> BidirectionalIterator1(T) {
> T* begin;
> T* end;
> T* current;
>
> bool hasValue() { return cursor != end && cursor != (begin-1); }
> void moveNext() { current++; }
> void movePrevious() { current--; }
>
> T value() { return *current; }
> T value(T v) { return *current = v; }
> }
>
> Silly usage example:
>
> auto i = someIterator;
> while(i.hasValue) {
> if (i.value > x)
> i.moveNext;
> else
> i.movePrevious;
> }
>
> I like how succinct this turns out, but it has the disadvantage of
> internally needing two invalid values, one at the start and one at the
> end.
Not necessarily. The dummy node could be shared by both. This is one
reason why circular linked-list implementations are so common. Things
do admittedly get a bit weird for binary trees, however.
> Considering pointers internally, this means a pointer pointing
> before the start of an array, which is very bad.
In C++, while a bidirectional iterator may be able to traverse in either
direction, it still generally has some natural association as a forward
or reverse iterator. That is, when the iterator is first returned from
the sequence it was likely either initialized pointing to the first or
last element. If it's the former then it's associated with forward
iteration, if it's the latter then reverse iteration. For example, the
C++ vector iterator is bidirectional (actually, random), but iterators
returned by the begin() method are effectively forward iterators while
iterators returned by the rbegin() method are effectively reverse
iterators. Perhaps this is the correct approach to take here as well?
> The other disadvantage
> is hasValue that has to check for both of these two cases.
True enough. But range-checking is range-checking. If it's possible
for an iterator to go out of range and it's possible to check for this
condition then one should do so, at least in debug builds.
> One alternative is the Java style version. Where the pointer/cursor lies
> conceptually between the elements instead of at an element, giving:
>
> BidirectionalIterator2(T) {
> T* begin;
> T* end;
> T* cursor;
>
> bool hasNext() { return cursor != end; }
> bool hasPrevious() { return cursor != begin; }
> void moveNext() { cursor++; }
> void movePrevious() { cursor--; }
>
> T next() { return *cursor; }
> T next(T v) { return *cursor = v; }
>
> T previous() { return *(cursor-1); }
> T previous(T v) { return *(cursor-1); }
> }
>
>
> auto i = someIterator;
> while(i.hasNext) {
> if (i.next > 3)
> i.moveNext;
> else
> i.movePrevious;
> }
>
> The disadvantage is requiring two additional methods (hasPrevious and
> previous), but the advantage is that it doesn't need more than 1 illegal
> value (pointing 1 past the end, which is well supported).
The other disadvantage is that this returns to the original problem
where it's impossible to determine whether an iterator points to a valid
location without moving.
> Apart from different method names, are there other variants?
See above. The C++ approach seems to make more sense to me, though I'm
still mulling it over. There is a definite appeal to having iterators
that are truly direction-agnostic, but as you've demonstrated above it
does result in slightly weird implementation requirements. Since D is
like C++ in that the one-past-the-end location is valid but
one-past-the-beginning is not, it seems reasonable to preserve that
behavior here, even if a more Java-like iterator design is used.
Sean
More information about the Digitalmars-d-learn
mailing list