Container-specific Iterator Implementations
Iterator types differ because each iterator must hold the information needed to do its job for a particular container type.
Visiting Two Kinds of Collections
Suppose you have a collection of values and want to visit each one in turn. The thing doing the visiting has to keep track of where it currently is. Now suppose there are two different kinds of collection: one stores its elements in a contiguous run of slots, and the other stores them as a chain of linked pieces. Does the visiting thing need the same information in both cases? Working through that question is the best way into this topic, because the answer explains why iterators come in different types at all.
What do you think happens?
Does the visiting object need the same information for a contiguous run of slots as it does for a chain of linked pieces?
Reveal answer
No. The information an iterator needs depends on the container it serves, so the iterator for a contiguous container and the iterator for a linked container must hold different information.
An iterator has to hold whatever information it needs to do its job for one particular kind of container. Information that is enough for one container may be useless for another.
Tracking Position in a Vector
Start with the vector, a container whose elements sit in order and can be reached by position. A vector iterator has a simple option available to it: it can be an ordinary pointer to an element. A pointer is a reasonable way of referring to an element of a vector, so no extra machinery is needed. Holding a pointer is enough to know where you are and to step forward to the neighboring element.
The key idea is that a vector's elements are arranged so that the element after the current one is the neighboring slot. Because the iterator only needs to know which slot it is at, a single pointer carries all of the information the iterator needs to do its job for a vector.
Adding an Index for Range Checking
The vector iterator could also be designed differently. Instead of holding only a pointer, it could hold a pointer to the vector itself together with an index, which is a number giving the position within that vector. Both designs are valid for a vector, but they carry different information.
The second design is the one that allows range checking. Because the iterator knows the vector it belongs to and its position within that vector, it can judge whether a requested position actually falls within the vector. A bare pointer carries no such information about the vector around it, so the pointer-only design cannot do this check on its own.
Why List Elements Cannot Find Their Successor
Now turn to the list. A list element cannot be relied on to know where the next element is. Each element is simply a value in its own place, and the link to its neighbor is not something the element itself is guaranteed to carry for the iterator's purposes. Because of this, a pointer to an element is not enough to move from one list element to the next.
Building a List Iterator from a Link
The list iterator therefore must be something more complicated than a simple pointer to an element. One natural choice is a pointer to a link, meaning the iterator refers to the connecting piece of the list rather than to the bare element. From the link, the iterator can reach the element it refers to and also the link that leads onward. This is the reason the list iterator and the vector iterator end up with different types.
Why Iterator Types Depend on the Container
Taken together, these designs explain the type difference. Each iterator must hold whatever information it needs to do its job for one particular container. A vector iterator can get by with a pointer to an element, or with a pointer to the vector plus an index. A list iterator needs a pointer to a link. Because the held information differs, the iterator types differ as well, and iterator types can differ as much as the containers they serve.
Operations Every Iterator Shares
After seeing how varied the designs are, it may seem that every iterator is a different creature. The source emphasizes the opposite. What is common to all iterators is their semantics and the naming of their operations. Two operations do most of the work. Applying the increment operator, written ++, to any iterator yields an iterator that refers to the next element. Applying the dereference operator, which is written as an asterisk, yields the element to which the iterator refers.
This is why one idea can cover all of these types. Any object that obeys a few simple rules like these counts as an iterator, and that shared set of rules is what the concept of an iterator describes. The internal representation can vary widely, but the behavior seen from outside stays consistent. A reader who knows the operations can use an iterator without knowing how it was built.
Advancing a vector iterator and a list iterator
A vector holds the values A, B, C in order, and a list holds the same values in the same order. Each iterator currently refers to the element B. Apply the increment operator once to each and describe what each iterator now refers to.
- 1Identify the vector iterator's starting state
The vector iterator refers to the slot holding B. Whether it holds a pointer alone or a pointer plus an index, it knows where B sits.
- 2Apply ++ to the vector iterator
The result is an iterator that refers to the next element, which is C, because C sits in the neighboring slot.
- 3Identify the list iterator's starting state
The list iterator refers to the link associated with B. From that link it can reach B and the link that leads onward.
- 4Apply ++ to the list iterator
The result is an iterator that refers to the next link, and therefore to the element C. The step follows the chain rather than a neighboring slot.
- 5Compare the two results
Both increments produce an iterator that refers to the next element, which is the shared behavior. The mechanism that achieves it differs between the two containers.
After one increment, both iterators refer to the element C. The vector iterator reached it by moving to the next slot, and the list iterator reached it by following a link.
Naming Iterator Types Through the Container
A natural follow-up question is how a programmer refers to these types without memorizing their details. Users rarely need to know the specific type of a particular iterator. Instead, each container knows its own iterator types and makes them available under the conventional names iterator and const_iterator. The names are the same across containers, even though the types behind them are not.
- list<Entry> names the container holding Entry elements
- ::iterator is the conventional name the container provides for its iterator type
In that expression, list<Entry>::iterator is the general iterator type for a list of Entry elements. Writing the container name followed by the conventional name lets you refer to the right iterator type for that container. The practical payoff is that you rarely have to worry about how that type is defined, because the container supplies the name.
Mistakes to Avoid with Iterator Types
Common mistake Assuming a plain element pointer can always step to the next list element
What it looks likeHolding a pointer to element B in a list and expecting ++ to move to C
Why it failsList elements do not reliably know where the next element is, so a pointer to an element is not enough to move along a list.
Do this insteadUse a list iterator built around a link, which can reach the element and the link that leads onward.
Common mistake Concluding that all iterators must be the same type because they share operations
What it looks likeExpecting a vector iterator and a list iterator to be interchangeable because both support ++
Why it failsShared semantics do not mean shared internal representation. Each iterator holds the information its own container needs, so the types differ.
Do this insteadTreat the operation names as the common interface and the internal design as specific to the container.
Common mistake Assuming only one design is valid for a vector iterator
What it looks likeBelieving a vector iterator must be a bare pointer
Why it failsA vector iterator can be an ordinary pointer, or a pointer to the vector plus an index, and both designs are valid.
Do this insteadChoose the design that provides the capability needed, such as range checking through the vector-plus-index design.
Common mistake Writing the internal iterator type directly instead of using the container's conventional name
Practice Tracing Iterator Designs
A container stores its elements as a chain of linked pieces, and you want an iterator that can step from one element to the next. Explain why a pointer to the bare element is not enough, and describe what the iterator should refer to instead.
Hints
- Think about whether a list element is guaranteed to know where its neighbor is.
- Consider what a link can reach that a bare element cannot.
A vector iterator is built as a pointer to the vector plus an index. Explain what extra capability this design provides compared with a bare pointer, and name the capability.
Hints
- The index tells the iterator where it is within the vector.
- The capability concerns whether a requested position is valid.
Key Takeaways
- Iterator types differ because each iterator must hold the information needed to do its job for one particular container type.
- A vector iterator can be an ordinary pointer to an element, or a pointer to the vector plus an index that allows range checking.
- A list iterator must be more complicated than a pointer to an element, because list elements do not know where the next element is, so a pointer to a link is one option.
- All iterators share their semantics and operation names, including ++ for moving to the next element and the dereference operator for reaching the element.
- Each container makes its iterator types available under the conventional names iterator and
const_iterator, so users rarely need the specific underlying type.