Concepts / Writing Generic Algorithms with Iterators

Writing Generic Algorithms with Iterators

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

  • Programming

Sorting a Range Instead of a Container

Suppose you want to sort everything held in a container. A natural design is a sort function that receives the container and does the work itself. The standard library's range version of sort() is shaped differently. It takes a pair of iterators that together define a sequence. The first iterator marks where the sequence begins, the second marks where it ends, and sort() works on everything between them.

This shape matters because it moves the question. The function no longer asks what kind of container it was given. It asks what its iterators can do. That single change is the idea this article builds on: a generic algorithm is written against a capability, and the iterator is what provides that capability.

Why Sorting Needs Direct Jumps

The important detail is what sort() demands of those iterators. They are not allowed to be just any iterators. They must offer random access to the sequence. Sorting moves elements around in a non-sequential way, so the algorithm must be able to reach any position in the sequence directly, rather than stepping through one element at a time.

Random access is the capability to reach any position in a sequence directly. In the source's terms, a random-access iterator supports operations such as it + n and it[i], which move to or read the element a given distance from where the iterator currently is. A function that depends on this capability has a precondition, and that precondition belongs to the iterator, not to the container's name or its size.

position 030position 110position 250position 320position 440
How does a random-access iterator reach element number k in one step, and how is that different from having to step through elements one at a time?

Read the cells above as the sequence that sort() must rearrange. Because the algorithm may need to touch position 3 right after position 0, it needs a way to address each cell by its index. A capability that lets the program index into the sequence is what makes the sort possible at all.

Predicting Access on a forward_list

What do you think happens?

A forward_list holds five values. Can a program jump straight to the third element in one step, without visiting the first two?

  • Yes, by indexing it directly

  • No, it must follow links from the beginning

Reveal answer
Answer:

No, it must follow links from the beginning

A forward_list is a singly-linked list. Reaching a given position means following links from the beginning, so a program cannot skip ahead to the third element. This is exactly why forward_list does not offer random access.

How Storage Shapes Access

The reason a forward_list cannot jump is rooted in how it is stored. The source describes forward_list as a singly-linked list. Each element knows only about the element after it, so the structure is a chain that can be followed in one direction. The two sub-topics below contrast this chain with a container that supports random access.

Checking the Iterator Kind with iterator_traits

If a sort function simply accepts any pair of iterators, it will fail on a forward_list at the point where it tries to use a capability the 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 source describes the calling pattern as one function name, two different containers, and no separate sorting function for each. It also states that the techniques needed to make that work are generally useful beyond this one case.

The sort function does not need to know the container's name. It asks iterator_traits which kind of iterator it has been given, and that answer decides which strategy the function can take. A vector's iterators provide random access, so the function can use the direct-jump strategy. A forward_list's iterators provide only forward traversal, so a function that checks first can avoid committing to a strategy that the iterator cannot support.

Tracing a Sort Call on Two Containers

Worked example

One sort name, two containers

The same range sort() is called on a vector and on a forward_list. Trace what the function needs and what each iterator can provide.

  1. 1
    Identify the range

    The call passes a pair of iterators marking the beginning and end of the sequence. The container itself is not the deciding argument.

  2. 2
    State the requirement

    sort() must be able to reach any position in the sequence directly, so its iterators must offer random access.

  3. 3
    Check the vector

    A vector supports random access through its contiguous elements, so its iterators satisfy the requirement and the sort can proceed.

  4. 4
    Check the forward_list

    A forward_list offers only forward iterators because it is singly-linked. Its iterators do not satisfy the random-access requirement.

  5. 5
    Locate the mismatch

    The failure is located at the sort() call, in the unmet requirement, not in the element values or in any loop around the call.

Result

The same function name serves both containers when it checks the iterator kind. Without that check, the forward_list call fails at the point where the function needs a capability the iterator lacks.

cpp
Notes on the code
  • The range is given as a pair of iterators, not as the container itself.
  • The vector call matches sort()'s requirement for random access.
  • The commented forward_list call shows where the mismatch would surface.

Where the Mismatch Surfaces

The flowchart below follows a sort() call on a forward_list. The question is at which step the failure happens. Read it as the order of checks the function must satisfy, not as a claim about any particular compiler's messages.

sort(first, last)calledrange from iteratorsIterator kind checkedvia iterator_traitsRandom accessavailable?required by sort()Sort proceedsdirect jumps allowedMismatch at the callforward iterators onlyyes (vector)no (forward_list)
What steps happen from the moment sort() is called on a forward_list until the mismatch is reported, and at which step does it fail?

The step that fails is the requirement check at the call, where sort() needs random access and the forward_list's forward iterators cannot provide it. A function that skips the check and simply begins working will fail later, at the point where it tries to use the missing capability. Checking first places the failure at the call, where the cause actually lies.

Mistakes Learners Make with Container Names

  • Common mistake

    Assuming that any container which can be looped over can be sorted by the same function

    What it looks like

    Looping over a forward_list with a for-statement works, so the learner expects sort() to accept it too.

    Why it fails

    Traversal and random access are different capabilities. A forward_list can be traversed but does not offer the random access sort() requires.

    Do this instead

    Ask whether the iterators meet the requirement of the function being called, not whether the container can be walked.

  • Common mistake

    Deciding suitability from the container's name or size

    Why it fails

    The source states that the precondition belongs to the iterator, not to the container's name or size.

    Do this instead

    Inspect the iterator kind the container provides.

  • Common mistake

    Blaming the element values or the surrounding loop when a sort call fails

    Why it fails

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

    Do this instead

    Look at the iterators passed to sort() first.

  • Common mistake

    Believing a forward_list is a broken or inferior container

    Why it fails

    The source says a container lacking random access is not unusable; it offers forward iterators that support traversal.

    Do this instead

    Use a forward_list for what it does well, and recognize that random-access algorithms need a different container or a different algorithm.

Designing Functions Around Iterator Capabilities

Designing against iterator capabilities makes one generic function usable across containers. The technique is to let the iterator kind determine what the function may do, rather than writing a separate sort for each container. The source notes that this approach is generally useful beyond the single sort example.

Practice: Matching Containers to Requirements

EASY

A function requires random access. For each container, say whether its iterators can satisfy that requirement: a vector, and a forward_list. Justify each answer by describing how a program would reach the third element.

Hints
  • Ask what the source says forward_list is structured as.
  • Think about whether a program can compute a position directly or must follow links.
  • A vector's elements are described in the source's terms as offering direct jumps through it + n and it[i].

Key Takeaways

  • The range sort() takes a pair of iterators and requires them to offer random access, so it can reach any position directly.
  • A forward_list is singly-linked, so reaching a position means following links from the beginning, and it offers only forward iterators.
  • A container without random access remains fully traversable, but a function that needs random access will reject it.
  • iterator_traits lets a program check which kind of iterator a container supports, so one sort() function can serve both a vector and a forward_list.
  • A mismatch between container and iterator kind is located at the sort() call, in the unmet requirement, not in the elements or the surrounding loop.

Related concepts

More concepts starting with W