Iterators and Container Traversal
A generic find_all() uses template parameters so that one definition works for strings, lists, and vectors, and it returns iterators so the caller can reach the matched positions.
Why One Search Must Serve Many Containers
Imagine you have written a routine that scans a container and collects the positions where a given value occurs. Once it works for a string of characters, the natural next step is to want the same routine for a list of doubles, a vector of strings, or any other sequence you might store. Rewriting the routine for every container type would duplicate the same loop again and again. This article follows one generic routine, find_all(), and shows how a single template can serve all of these containers, and what the template must say about the types involved in order to compile correctly.
What do you think happens?
When C is a template parameter, will the compiler always know on its own whether the name C::iterator refers to a type?
Reveal answer
No. The compiler cannot be relied on to infer that C::iterator is a type, so the programmer must mark it with typename.
In ordinary code a name usually has one obvious meaning. Inside a template, the name depends on what C turns out to be, and the compiler is not allowed to rely on guessing. The programmer has to say it.
Building the Generic find_all Routine
The routine find_all() takes a container and a value, walks through the container with an iterator, and returns a vector of the iterators that point at each matching element. Returning iterators rather than copies of the values is a deliberate design choice. The caller receives a handle to each match, so it can inspect or change the element in place.
Two template parameters do the work. C stands for whatever container type the caller passes in, and V stands for the type of the value being searched for. Because C is a parameter, the function accepts a string, a list, or a vector without any change to its body. The return type is a vector of iterators into that container, so the caller always receives positions expressed in the container's own iterator type.
- The template parameter C is the container type and V is the searched value type
- The return type names a vector of the container's iterator type
- The loop scans from
begin()up toend(), comparing each element with v - Each matching iterator is collected and returned to the caller
The code above is an illustrative sketch that follows the description in the source: one signature, a scan from begin() to end(), a comparison of each element with the search value, and a vector of matching iterators as the result. Notice that the body never mentions string, list, or vector. That is the point of the template.
Tracing find_all Across Container Types
The source tests the generic version on three containers: a string, a list of doubles, and a vector of strings. Each call uses the same template text, but the compiler produces a separate concrete version of find_all for each container type that is used. Each version has its own element type and its own iterator type, while the order of the steps stays the same.
| Call | Container type C | Value type V | Iterator type in the result |
|---|---|---|---|
| find_all on a string | std::string | char | string::iterator |
| find_all on a list of doubles | list<double> | double | list<double>::iterator |
| find_all on a vector of strings | vector<string> | string | vector<string>::iterator |
Three instantiations of one template. The loop text is identical; the types are not.
This is why the loop variable p in the test has a different concrete type in each call, even though the source text of the loop is the same in all three cases. The instantiation is decided by the container type the caller passes, and the caller never has to write three versions of the routine.
Where Matched Positions Come From
Because find_all() returns iterators rather than copies, a caller can write through them. The source extends the test with a loop over the results of find_all(vs, "green") that assigns a new string through each iterator. The matching elements of vs change in place, and the loop variable is only a handle to them. The diagram below uses a small container of our own, holding red, green, blue, and green, to show how each returned iterator maps to a specific position.
The contents of vs in the diagram are our own illustration; the source describes the mechanism, not these particular values. The key idea is that each returned iterator is tied to a position in the container. If a result contains the wrong positions, the iterator is not the place to look first, because iterators faithfully record where the routine found each match. The error comes from the routine itself, specifically from the comparison that decides a match or the range that decides which elements are scanned.
The Dependent Name Problem
A dependent name is a name whose meaning depends on a template parameter. In C::iterator, the name iterator lives inside C, and C is only known when the template is used. Many learners assume the compiler will look at the name, notice that C is a template parameter, and work out on its own whether C::iterator is a type or some value. It is a reasonable guess, because in ordinary code a name usually has one obvious meaning. In a template, however, the compiler is not allowed to rely on guessing for it.
The correction is that the programmer has to say it. The typename keyword is that marker. It tells the compiler that a dependent name refers to a type. Writing typename C::iterator is therefore a claim about meaning rather than decoration: it says the member named iterator inside C is a type to be used in a declaration. Without the marker, a declaration like the one below is not something the compiler can be trusted to read the way the author intended.
- The declared element type of the vector is an iterator type, so the name needs typename
- The loop variable p is also declared with the dependent iterator type
Hiding the Iterator Type with an Alias
Repeating typename C::iterator in every signature and body is noisy, and it exposes an implementation detail that callers do not need to think about. The source introduces a type alias template, Iterator<T>, to hide it. A type alias gives a shorter name to a type expression. Here the alias takes a container type T and produces that container's iterator type, and the generic function uses the alias in place of the long form.
- The alias takes a container type T and names T::iterator once
- The typename marker sits inside the alias, where the dependent name is written
- The signature now reads at a higher level of intent
template <class C, class V>
vector<typename C::iterator> find_all(C& c, V v);Every signature repeats the typename keyword and the member name iterator
template <class C, class V>
vector<Iterator<C>> find_all(C& c, V v);Every signature names Iterator<C>, and the knowledge of how to get the iterator lives in one place
The alias splits what each side needs to know. The caller passes a container and reads the result as a vector of iterators; the caller does not need to see the member name inside the container. The implementer writes the body and uses Iterator<C> wherever an iterator type is needed. The practical payoff is maintainability: if the way you obtain an iterator ever has to change, the change happens in the alias rather than in every function that uses it.
Worked Trace of a Green Match
Tracing find_all(vs, green) and Locating a Wrong Result
A vector vs holds red, green, blue, and green. A call find_all(vs, green) is expected to return iterators to positions 1 and 3. Trace what the routine does and say where a wrong result would come from.
- 1Fix the types
C is the vector of strings and V is the string type. The return type is vector<Iterator<C>>, which is a vector of vector<string>::iterator.
- 2Start the scan at begin()
The loop variable p is set to the first element, the one holding red. It is a vector<string>::iterator.
- 3Compare each element with the value
At position 0, red does not equal green, so nothing is collected. The scan advances with ++p.
- 4Collect the first match
At position 1, the element is green and equals the search value, so p is stored in the result vector.
- 5Continue until end()
Position 2 holds blue and is skipped. Position 3 holds green and is stored. The next advance reaches
end(), and the loop stops. - 6Return and inspect
The result holds two iterators, pointing at positions 1 and 3. If the caller writes through them, only those two cells change.
The result has two iterators, at positions 1 and 3. If a wrong set of positions appeared instead, check the comparison in the loop first, since it decides which elements are matches, and then the range from begin() to end(), since it decides which elements are scanned. A compile error on the return type, by contrast, points to a missing typename or a mistaken alias.
Mistakes With Dependent Names and Iterators
Common mistake Leaving out typename when a dependent name is used as a type
What it looks likevector<C::iterator> result;Why it failsC::iterator depends on the template parameter C, and the compiler cannot be relied on to read it as a type without the marker.
Do this insteadvector<typename C::iterator> result;Common mistake Assuming the compiler will infer the meaning from the name
Why it failsThe name's meaning depends on what C turns out to be, so the compiler is not allowed to settle the question by guessing.
Do this insteadMark every dependent name that is meant to be a type with typename.
Common mistake Repeating typename C::iterator in every signature and body
Why it failsThe repetition exposes an implementation detail that callers do not need, and it spreads the knowledge of how to get an iterator across the code.
Do this insteadRecord the detail once in a type alias such as Iterator<T> and use Iterator<C> in the signatures.
Common mistake Treating a wrong set of positions as a type error
Why it failsA wrong set of positions usually comes from the comparison or the scan range, not from the signature.
Do this insteadCheck the comparison between element and value, and the range from begin() to end(), before changing the return type.
Common mistake Expecting the result to be copies of the elements
Why it failsfind_allreturns iterators, so writing through a result changes the container itself.Do this insteadTreat each result element as a handle to a position in the original container.
Practice Tracing a Search
Given the generic template template <class C, class V> vector<typename C::iterator> find_all(C& c, V v), and a call find_all(s, x) where s is a std::string, explain what the element type of the returned vector is. Then say where you would look first if the returned positions were wrong, and where you would look first if the code failed to compile on the return type.
Hints
- The return type is a vector of the container's own iterator type, and C is the string type here
- A wrong set of positions points to the comparison or the scan range
- A compile error on the return type points to a missing typename or a mistaken alias
Wrapping Up the Traversal Pattern
- 1A generic
find_all()uses template parameters so one definition serves strings, lists, and vectors, and it returns iterators so the caller can reach each matched position. - 2A dependent name such as C::iterator must be marked with typename when it is meant to be a type, because the compiler cannot be relied on to infer that meaning.
- 3The alias template Iterator<T> hides the typename C::iterator detail, keeping signatures shorter and easier to maintain.
- 4Each container type produces its own concrete version of the function, so the same loop yields string, list, or vector iterators.
- 5When a generic search goes wrong, check the comparison and the begin-to-end range for wrong positions, and the typename marker or alias for compile errors on the return type.
Key Takeaways
- A generic
find_all()uses template parameters so one definition serves strings, lists, and vectors, and it returns iterators so the caller can reach each matched position. - A dependent name such as C::iterator must be marked with typename when it is meant to be a type, because the compiler cannot be relied on to infer that meaning.
- The alias template Iterator<T> hides the typename C::iterator detail, keeping signatures shorter and easier to maintain.
- Each container type produces its own concrete version of the function, so the same loop yields string, list, or vector iterators.
- When a generic search goes wrong, check the comparison and the begin-to-end range for wrong positions, and the typename marker or alias for compile errors on the return type.