Concepts / Iterator Categories and Their Operations

Iterator Categories and Their Operations

The range sort() requires random-access iterators, a capability that depends on the iterator a container provides.

  • Programming

A Sort That Asks More of Its Input

Suppose you have a collection of values and you want them in order. A natural design is a sort function that receives the container itself and does all the work. The standard library's range version of sort() is shaped differently. It does not take a container. It takes a pair of iterators that together define a sequence: one marks where the sequence begins, the other marks where it ends, and sort() works on everything between them.

That shape looks harmless, but it carries a hidden requirement. The iterators passed to sort() are not allowed to be just any iterators. They must offer random access to the sequence. Whether a given call is valid therefore depends on what kind of iterator the container provides, not only on whether the container can be looped over. This article follows that requirement from the call site down into the iterator itself.

What do you think happens?

Can a forward_list, which you can walk from front to back with a loop, be passed to the range version of sort()?

  • Yes, because it can be traversed from front to back

  • No, because its iterators do not offer random access

Reveal answer
Answer:

No, because its iterators do not offer random access

A forward_list offers forward iterators, which let a program move from front to back. Traversal alone is not enough for sort(), which needs random access. The rest of this article explains why the requirement is on the iterator rather than on the container's name or size.

Tracing Positions in Two Layouts

The reason random access matters becomes clearer when you picture how the two kinds of container store their elements. In a random-access container, the elements sit at numbered positions, and a program can jump straight to position i. In a forward_list, each element holds a link to the element after it and nothing else useful for navigation. To reach position i, you must start at the beginning and follow links one at a time.

position 0value 10first elementvalue 10position 1value 20second elementvalue 20position 2value 30third elementvalue 30link to nextlink to next
What changes when a program wants the element at position 2? In the random-access row, the position is named directly. In the forward_list row, the program must follow the link from the first element through the second before it can reach the third.

Read the diagram as a contrast in access, not a ranking of quality. The top row can be reached by naming a position. The bottom row can only be reached by walking. The links in the forward_list also point only forward, because each element knows about the element after it and not about the one before it. That is why stepping back is not a reasonable operation for this container.

Random Access and the Precondition on sort()

sort() moves elements around in a non-sequential way. It does not simply pass once from front to back, swapping neighbours as it goes. Because it reorders elements across the whole sequence, it needs to reach any position directly. Random access is the capability that provides this: it lets a program reach any position in the sequence without stepping through each element before it.

Definition

A precondition is a requirement that must hold before a function can be called validly. For the range version of sort(), the precondition is that the iterators passed in must offer random access to the sequence they delimit.

Random access is therefore a property of how a container's iterators behave. A container can hold a great many elements, be walked from start to finish, and still fail this precondition if its iterators cannot jump to arbitrary positions.

How iterator_traits Lets One sort() Serve Many Containers

If a sort function simply accepted any pair of iterators, it would fail on a forward_list at the point where it tried to use a capability that iterator does not have. A better design checks the kind of iterator before it commits to a strategy. The standard library provides a mechanism named iterator_traits for exactly this purpose. It allows a program to check which kind of iterator a container supports.

The source uses this mechanism to improve the range sort() function from an earlier section, so that it accepts either a vector or a forward_list. The goal is a single function that can be called on both containers, with the kind of iterator determining what the function is able to do. The example is written in C++, the language the source uses for the standard library. Its value here is the calling pattern: one function name, two different containers, and no separate sorting function for each.

sort(begin, end)calledCheck iteratorcategory withiterator_traitsRandom accesssupported?Sort the rangeMismatch at the callyesno
Which step inspects the iterator category, and what happens when the category does not offer random access? The category is checked first, and the decision determines whether the range is sorted or the call is rejected.

The diagram shows the order of decisions. The category is looked up before the sorting work begins, so the function's behaviour follows from the kind of iterator it was given. The same function name therefore covers both containers, while the iterator determines which path is open to it.

Locating a Mismatch at the Call

Consider a call such as sort(lst), where lst is a forward_list. When you read this call, the question to ask is not whether the container holds numbers or can be walked from front to back. The question is whether its iterators meet the requirement of the function being called. The sequence below follows that question through the call.

Callersort()forward_list iterator1. pass begin and end iterators2. require random access3. forward only, no random access4. mismatch surfaces at the call
At which step does the forward_list iterator fail to meet sort()'s requirement? The failure is located at the call, in the requirement, when sort() checks the iterator it was given.
Worked example

Tracing sort(lst) on a forward_list

A forward_list named lst holds the values 3, 1, 2. Trace what happens when the range version of sort() is called with its begin and end iterators.

  1. 1
    Step 1: the call

    The caller passes the iterators that mark the beginning and end of lst. The container's values, 3, 1, 2, are not examined yet.

  2. 2
    Step 2: the requirement

    sort() needs random access to the sequence, because it reorders elements across the whole range rather than walking once.

  3. 3
    Step 3: the category check

    The iterators are the forward kind, which can move from front to back but cannot jump to an arbitrary position. The requirement is not met.

  4. 4
    Step 4: where the error belongs

    The problem is located at the sort(lst) call, in the iterator's kind. The values 3, 1, 2 and any loop around the call are not the cause.

Result

The call is rejected because the forward_list iterators do not meet sort()'s random-access requirement, even though lst can be traversed from front to back.

Notice that the same values, the same loop structure, and the same container could be fine for a different algorithm that only needs forward traversal. The mismatch is between the container's iterator kind and the function's requirement, which is why it is found at the call and not inside the element values.

Mistakes Beginners Make with Iterator Requirements

  • Common mistake

    Assuming that a container you can loop over can always be sorted with the range version of sort()

    What it looks like

    A forward_list can be walked from front to back with a for-statement, so it seems it should work with sort(lst).

    Why it fails

    Traversal and random access are different capabilities. The range sort() requires random access, and a forward_list only offers forward iterators.

    Do this instead

    Check the iterator kind the function requires, not just whether the container can be traversed.

  • Common mistake

    Deciding whether a container supports sort() from its name or its size

    What it looks like

    Two containers of the same size are assumed to behave the same way with sort().

    Why it fails

    The source places the precondition on the iterator, not on the container's name or size.

    Do this instead

    Ask what kind of iterator the container provides and whether it offers random access.

  • Common mistake

    Looking for the cause of the failure in the element values or the surrounding loop

    Why it fails

    A mismatch between container and iterator kind is located at the call, in the requirement.

    Do this instead

    Start debugging at the sort() call and compare the iterators passed with the requirement the function states.

Practice Predicting Which Containers Fit

MEDIUM

A colleague says that because a forward_list can be walked with a for-statement, the range version of sort() must accept it too. Explain in your own words why this reasoning is incomplete, and name the capability the iterator would need to provide.

Hints
  • Separate traversal from random access in your explanation.
  • Think about what sort() must do to elements across the whole range.
  • Remember that the source places the precondition on the iterator, not the container's name or size.
EASY

Given a call sort(lst) where lst is a forward_list, state the exact point in the call where the mismatch would be identified, and say which part of the program that point belongs to.

Hints
  • The mismatch is located at the call, in the requirement.
  • It is not in the element values or in the loop around the call.

Key Takeaways on Iterator Kinds

  1. 1
    The range version of sort() takes a pair of iterators and requires that they offer random access to the sequence.
  2. 2
    A forward_list is singly-linked, so it offers forward iterators that traverse front to back but not random access.
  3. 3
    iterator_traits lets a program check which kind of iterator a container supports, so one sort() function can accept both a vector and a forward_list.
  4. 4
    A mismatch between container and iterator kind surfaces at the sort() call, in the requirement, not in the element values or the surrounding loop.

At a glance

  • The range version of sort() requires random-access iterators because it reorders elements across the whole sequence.
  • A forward_list offers forward iterators, which can traverse front to back, but its singly-linked structure gives no direct jump to arbitrary positions.
  • iterator_traits lets a program check the iterator kind, which allows one sort() function to serve both a vector and a forward_list.
  • The precondition belongs to the iterator, so a mismatch is located at the sort() call in the requirement, not in the element values or loop.

Related concepts

More concepts starting with I