Writing Generic Algorithms with Iterators
The range sort() requires random-access iterators, a capability that depends on the iterator a container provides.
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.
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?
Reveal 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
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.
- 1Identify 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.
- 2State the requirement
sort()must be able to reach any position in the sequence directly, so its iterators must offer random access. - 3Check the vector
A vector supports random access through its contiguous elements, so its iterators satisfy the requirement and the sort can proceed.
- 4Check the forward_list
A
forward_listoffers only forward iterators because it is singly-linked. Its iterators do not satisfy the random-access requirement. - 5Locate 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.
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.
- 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_listcall 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.
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 likeLooping over a forward_list with a for-statement works, so the learner expects sort() to accept it too.
Why it failsTraversal and random access are different capabilities. A
forward_listcan be traversed but does not offer the random accesssort()requires.Do this insteadAsk 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 failsThe source states that the precondition belongs to the iterator, not to the container's name or size.
Do this insteadInspect the iterator kind the container provides.
Common mistake Blaming the element values or the surrounding loop when a sort call fails
Why it failsA mismatch between container and iterator kind is located at the call, in the requirement.
Do this insteadLook at the iterators passed to sort() first.
Common mistake Believing a forward_list is a broken or inferior container
Why it failsThe source says a container lacking random access is not unusable; it offers forward iterators that support traversal.
Do this insteadUse 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
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_listis 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_listis 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_traitslets a program check which kind of iterator a container supports, so onesort()function can serve both a vector and aforward_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.