Concepts / Using const_iterator for Read-Only Traversal

Using const_iterator for Read-Only Traversal

An iterator is defined by what its operations mean, not by its underlying type. If an object honors the shared operation names and their shared meanings, it is an iterator.

  • Programming

Pointing at One Record at a Time

Suppose you are writing a loop that walks through a collection of records. You need a way to point at one record, move on to the next one, and read the record you are pointing at. The language provides a tool for exactly this job, called an iterator. Once you start using iterators, a natural first question is which specific iterator type you should write down. For most day-to-day work, the answer is that you do not need to memorize it. What you need to understand is how iterators behave, and where to find the name when you do need it.

What do you think happens?

An iterator starts out referring to Ada in a list of three names: Ada, Grace, Alan. You apply the increment operator once, then the dereference operator. What value do you get back?

  • Ada

  • Grace

  • Alan

Reveal answer
Answer:

Grace

Applying the increment operator moves the iterator so that it refers to the next element, Grace. Applying the dereference operator then yields the element the iterator currently refers to, which is Grace. Dereferencing does not move the iterator; only increment does.

An Iterator Is a Role, Not a Type

Think of an iterator less as a particular kind of thing and more as a role that many different objects can play. What makes an object an iterator is that it responds to the same operations in the same way. The operation names are consistent across all iterators, and their meanings are shared. Whatever the underlying object looks like, if it honors these shared meanings, it counts as an iterator.

This is why the definition of an iterator is about semantics and naming rather than about one fixed type. Any object that obeys the rules is an iterator. The practical payoff is that code written against the shared operations works with every object that plays the role, and you can reason about a loop by knowing what each operation does rather than by knowing the exact type of the thing being used.

Key point

An iterator is defined by what its operations mean, not by its underlying type. If an object honors the shared operation names and their shared meanings, it is an iterator.

Two Operations: Move Forward and Read

The two core operations pull in different directions. The increment operator changes which element the iterator points to. Applying it to an iterator yields an iterator that refers to the next element. The dereference operator does not move anything. Applying it gives you the element to which the iterator currently refers.

Keeping these two roles separate makes any loop easier to follow. Each step has two parts: one moves the pointer, and the other looks at what the pointer now refers to. If you ever lose track of a loop, ask two questions separately: where does the iterator point now, and what element does dereferencing it give me?

Where a Container Keeps Its Iterator Names

If iterators are defined by shared behavior, you might expect every program to name a separate iterator type for every container it uses. The source describes a convenience instead. Each container knows the iterator types that belong to it, and it makes those types available under two conventional names: iterator and const_iterator. The container owns and advertises the type, so you look the name up through the container rather than remembering a separate name for each collection.

list<Entry>the containeriteratorconventional nameconst_iteratorconventional namepublishespublishes
A list of Entry values publishes its iterator types under the conventional names iterator and const_iterator, so you find them through the container.

Walking Ada, Grace, and Alan

Imagine a list holding three names in order: Ada, Grace, and Alan. Picture an iterator that starts out referring to Ada. Each increment moves it forward by one element, and each dereference reads the element it currently refers to. The trace below follows the iterator through the list.

Adastarting positionGraceafter one incrementAlanafter two increments++++
Each increment advances the iterator by one element, and dereferencing at each position yields the name the iterator refers to at that moment.
Worked example

Tracing the iterator through three names

A list holds Ada, Grace, and Alan in that order. The iterator starts at Ada. Apply increment, dereference, increment, and dereference in sequence. What does each dereference yield?

  1. 1
    Start

    The iterator refers to Ada, the first element.

  2. 2
    Increment

    The increment operator yields an iterator that refers to the next element, which is Grace.

  3. 3
    Dereference

    The dereference operator yields the element the iterator currently refers to, which is Grace. The iterator itself does not move.

  4. 4
    Increment again

    Another increment moves the iterator to the next element, which is Alan.

  5. 5
    Dereference again

    Dereferencing now yields Alan. The name you get always matches the element the iterator currently refers to.

Result

The two dereferences yield Grace and then Alan. Each increment advances the reference by exactly one element.

Iterator and const_iterator Side by Side

Both names belong to the same container and both are iterators, so both respond to increment and dereference in the shared way described above. The difference lies in the intent the name signals. The name iterator is the container's general iterator type. The name const_iterator signals a read-only traversal, the kind of loop where you look at each element without changing it.

Aspectiteratorconst_iterator
Published byThe container, under the conventional name iteratorThe container, under the conventional name const_iterator
Responds to incrementYes, it moves to the next elementYes, it moves to the next element
Responds to dereferenceYes, it yields the element it refers toYes, it yields the element it refers to
Intended useGeneral iterator type of the containerRead-only traversal of the container

Both names share the same shared operations; the name signals the intended use.

Mistakes Beginners Make with Iterators

  • Common mistake

    Assuming you must memorize a separate iterator type for every container

    Why it fails

    Each container publishes its own iterator types under the conventional names iterator and const_iterator, so you can look the name up through the container.

    Do this instead

    Find the name through the container, for example list<Entry>::iterator, instead of memorizing a new name for each collection.

  • Common mistake

    Expecting dereference to move the iterator forward

    Why it fails

    The dereference operator does not move anything. It only yields the element the iterator currently refers to.

    Do this instead

    Use increment to move to the next element, and dereference to read the element you are pointing at.

  • Common mistake

    Reading list<Entry>::iterator as a single unrelated word

    Why it fails

    The double colon is the scope operator. It means the name iterator is looked up inside list<Entry>.

    Do this instead

    Read the qualified name from left to right: the container first, then the name it publishes.

  • Common mistake

    Treating a type as valid only because it has the right name, when the real test is whether it honors the shared operations

Predicting Positions Before You Run

EASY

A list holds Ada, Grace, and Alan in that order. An iterator starts at Grace. Apply increment, then dereference, then increment, then dereference. Write down what each dereference yields before checking.

Hints
  • Track only where the iterator points. Increment moves it, and dereference only reads.
  • After the first increment the iterator refers to Alan, the last element.
  • The second dereference yields whatever element the iterator refers to at that point.
MEDIUM

Explain in one or two sentences why a qualified name such as list<Entry>::iterator is useful even though you rarely need to know how that iterator type is defined internally.

Hints
  • Think about what the scope operator tells you to look inside.
  • Consider what the name tells you about which container the iterator belongs to.

Key Takeaways from Iterator Roles

  1. 1
    An iterator is defined by its semantics and the naming of its operations, not by one fixed type. Any object that obeys the rules is an iterator.
  2. 2
    Increment moves the iterator to the next element. Dereference does not move it, and yields the element it currently refers to.
  3. 3
    Each container publishes its iterator types under the conventional names iterator and const_iterator, so you find them through the container.
  4. 4
    A qualified name such as list<Entry>::iterator identifies the general iterator type of that container, and the scope operator tells you to look inside the container for it.

At a glance

  • An iterator is defined by shared operation names and their meanings, not by one fixed type.
  • Increment moves an iterator to the next element; dereference reads the element it currently refers to without moving.
  • Containers publish iterator and const_iterator under conventional names, and qualified names such as list<Entry>::iterator identify them.
  • Use const_iterator as the signal for read-only traversal, and check your language's rules for exact guarantees.

Related concepts

More concepts starting with U