Type Aliases in C++
The return type is a vector of iterators into that container.
One Search Routine for Many Containers
Suppose 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, you will quickly want the same routine for a list of doubles, a vector of strings, or any other sequence. Rewriting the loop for every container type would duplicate the same logic again and again. The goal of this article is to see 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.
The routine in question is find_all(). It 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 an important design choice. The caller receives a handle to each match, so it can inspect or change the element in place.
Two Template Parameters at Work
Two template parameters do the work in the generic version. 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. The caller therefore always receives positions expressed in the container's own iterator type. The iterator type is not a fixed type that every container shares. It is a member that each container defines for itself, and that is where the difficulty begins.
- C and V are the two template parameters: the container type and the searched-value type
- The return type names the container's iterator type inside a vector
- The loop variable p is declared with the same nested iterator type
- The comparison and
push_backcollect positions, not copies of the matched values
The Guess the Compiler Cannot Safely Make
Before reading the generic version, pause on a belief many learners bring to templates. The belief is that the compiler will look at a name such as C::iterator, notice that C is a template parameter, and figure out on its own whether the name refers to a type or to some value. It is a reasonable guess, because in ordinary code a name usually has one obvious meaning. In a template, however, the name depends on what C turns out to be, and the compiler is not allowed to rely on guessing for it.
What do you think happens?
Inside a template, if C::iterator is written without any marker, can the compiler be relied on to treat it as a type?
Reveal answer
No, the programmer must mark the name with typename when it is meant to be a type
A dependent name is one whose meaning depends on a template parameter. The compiler may read such a name as a value unless the programmer marks it as a type, and the typename keyword is that marker.
The correction is that the programmer has to say it. Without the typename marker, a declaration like vector<typename C::iterator> is not something the compiler can be trusted to read the way the author intended. The flowchart above shows the decision point: the marker is what turns a dependent name into one read as a type.
What the typename Keyword Declares
The typename keyword tells the compiler that a dependent name refers to a type. The source puts the reason plainly: C's iterator is supposed to be a type, not a value of some type such as the integer 7. Writing typename C::iterator is therefore a claim about meaning, not a decoration. It says that the member named iterator inside C is a type to be used in a declaration.
Hiding the Iterator Behind 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 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 Iterator is a template that takes a container type T
- typename appears once, inside the alias, where the dependent name is defined
- The signature and loop now read Iterator<C> instead of the long nested form
The difference between the two approaches is one of where the detail lives. With the bare form, every function that needs an iterator must repeat the typename and the member name. With the alias, that knowledge is recorded once, and the function signatures state their intent at a higher level. The source notes that this improves readability and maintainability, which is the practical payoff: if the way you obtain an iterator ever has to change, the change happens in one place.
| Aspect | Bare form: typename C::iterator | Alias form: Iterator<C> |
|---|---|---|
| Where the typename detail lives | Repeated in every signature and body | Recorded once, inside the alias |
| What a caller reads | The implementation detail of the container | A shorter name for the container's iterator |
| Effect of changing how an iterator is obtained | Edit every place it is written | Edit the alias definition |
Two ways to name the same iterator type
Tracing One Call Across Containers
The same loop text runs whether the container holds characters, doubles, or strings, because the template generates a separate version of the function for each container type that is used. What changes from one call to the next is the type of the elements being compared and the type of the iterators being collected, not the order of the steps. The pipeline below follows one call through its decision points.
Locating where a wrong result begins
A call to find_all() returns positions that look wrong for one of three containers: a std::string, a std::list of doubles, and a std::vector of strings. Which of the three decision points should you check first, and why?
- 1Check the signature
Confirm the iterator type the signature promises. A compile error on the return type usually points at a missing typename or a mistaken alias, so a compile error here is a different symptom from a wrong set of positions.
- 2Check the scan range
Confirm that the loop runs from
begin()toend()for the container in question. A wrong set of positions can come from a range that does not cover what you expected. - 3Check the comparison
Confirm that the element compared with the search value is the one you think it is. A wrong set of positions usually comes from the comparison or the range.
- 4Trace one container at a time
Run the trace separately for the string, the list, and the vector. The container where the result diverges shows which of the three points changed.
The unexpected result is traced to the decision point where the behavior for that single container diverges, rather than being guessed at across all three.
Returned Iterators Let the Caller Write
Because the function 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. This is the reason the result is a vector of iterators: the caller gets access to the container itself.
An iterator returned by find_all() acts as a position inside the container. The caller can move from that position to neighboring elements by stepping the iterator, which is why the return type names the container's own iterator type rather than a plain value.
Mistakes When Reading Generic Search Code
Common mistake Assuming the compiler will decide whether a dependent name is a type
What it looks likeWriting vector<C::iterator> inside a template and expecting it to be read as a type.
Why it failsA dependent name's meaning depends on the template parameter, and the compiler may read it as a value unless the programmer marks it.
Do this insteadWrite vector<typename C::iterator> when the name is meant to be a type.
Common mistake Dropping typename from the alias definition
What it looks liketemplate <typename T> using Iterator = T::iterator;Why it failsThe alias is still a dependent name, so the type claim must be marked explicitly.
Do this insteadWrite template <typename T> using Iterator = typename T::iterator;Common mistake Treating the returned iterators as copies of the elements
What it looks likeAssuming that changing the loop variable in the caller leaves the container unchanged.
Why it failsThe function returns iterators, which are handles into the container, so writing through them changes the matched elements.
Do this insteadRemember that each iterator points at a position in the original container.
Common mistake Blaming the comparison when the compile error is about the return type
Checking a Trace on Your Own
Take the generic find_all() signature with Iterator<C> as the return type. Suppose a caller passes a std::vector of strings and the function returns an unexpected set of positions, while the return type compiles without error. Which of the three decision points would you check first, and what evidence would tell you the cause is in that point?
Hints
- A clean compile suggests the signature and alias are not the first place to look.
- Compare the elements that were matched against the value that was searched for.
- Ask whether the scan covered the full range from
begin()toend().
Key Takeaways
- A generic
find_all()uses template parameters, usually C for the container and V for the searched value, so one definition works for strings, lists, and vectors. - 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
find_all, and the returned iterators let the caller reach and change matched elements in place. - When a generic search goes wrong, check the iterator type the signature promises, the scan range, and the comparison, one container at a time.