Tag Dispatch and Iterator Categories
The standard header <iterator> provides the class template iterator_traits, which supplies information about iterators.
When Code Has to Choose by Type
Most code you write works with values: numbers, strings, and containers that hold them. Some code has to work with types instead, and it has to make its choice while the program is being compiled, before any values exist. This article starts from the idea of a name that stands for a type rather than a value, and then shows how the standard library applies the same technique to iterators.
The two ideas to keep in view are these. First, a type function takes a type as input and produces a type as output, at compile time. Second, tag dispatch uses the category of an iterator to choose among several implementations of the same work. The rest of the article explains how the first idea makes the second one possible, and why a routine such as sort() depends on both.
What do you think happens?
If a value-level function can be selected by the kind of value it receives, what do you think a compile-time type-level selection would have to be able to see before the program runs?
Reveal answer
It would have to be able to see the type itself, because the choice is made from types while compiling, before any values exist.
The source describes this kind of code as work that is done on types at compile time. The decision therefore has to be driven by type information, which is exactly what iterator_traits exposes for an iterator type.
Reading the Two Type-Naming Lines
The source opens with two short lines of C++. Each one defines a name that refers to a type. The names are Iterator_type and Iterator_category. Neither name holds a value. Each one is an alias for a type that the compiler works out from another type you supply.
Think of the trace as a sequence of states. Before compilation, you have only text. During compilation, the template parameter is filled in with a concrete type, the lookup is performed, and a named type is produced. Nothing about this process runs when the program executes. The program later uses the result, but the work of finding the type is already finished.
What iterator_traits Returns for an Iterator
The standard header <iterator> provides the class template iterator_traits, which supplies information about iterators. A class template is a pattern that the compiler instantiates for a specific type. When you write std::iterator_traits<Iter> with a concrete iterator type in place of Iter, you ask the library for the set of type-level facts it holds about that iterator.
The source names one of these facts directly: the iterator category, accessed as the member iterator_category. Other facts sit alongside it in the same traits class, and the diagram below shows the mapping. The important idea is that you give iterator_traits an iterator type as its key and receive a bundle of associated types back.
Consider a generic routine that must know what kind of iterator it has been handed. Instead of inspecting the iterator by hand, it asks iterator_traits for the category. The routine does not need to know how the iterator is implemented internally; it only needs the tag that the library reports for it.
Type Functions and the typename Keyword
A type function takes a type as input and produces a type as output, at compile time. The Iterator_type alias in the source is a type function: given a container type C, it names C's iterator type. The alias is written as a template alias, which means it is a pattern that is filled in once a concrete type is supplied.
- Iterator_type takes a container type C and names its iterator member
- Cat takes an iterator type It and names its
iterator_category - Vec is an ordinary alias for a concrete container type
- VecIt is produced by filling C with Vec
- VecCat is produced by filling It with VecIt
The typename keyword appears in both alias lines for the same reason. Because the expression depends on a template parameter, the compiler cannot know on its own whether the name that follows is a type or something else. Writing typename tells it that the name is a type. In the first line the dependent name is C::iterator, and in the second it is iterator_traits<It>::iterator_category.
Tracing a Type Lookup Step by Step
Now trace one concrete lookup through the states it passes through. Work from the alias Cat and a concrete iterator type, and watch which name is filled in and which member is read at each stage.
Producing an iterator category from an iterator type
Given template<class It> using Cat = typename std::iterator_traits<It>::iterator_category; and the concrete iterator type std::vector<int>::iterator, what type does Cat<std::vector<int>::iterator> name?
- 1Fill the template parameter
It is replaced by std::vector<int>::iterator. The alias is now a specific lookup rather than a pattern.
- 2Ask the traits class
The expression std::
iterator_traits<std::vector<int>::iterator> is evaluated. The compiler selects the traits for that iterator type and instantiates it. - 3Read the member
The member
iterator_categoryis read from that traits class. The typename keyword has told the compiler that this member is a type. - 4Name the result
The alias Cat<std::vector<int>::iterator> now names the category type stored in the traits. No values were compared or moved during this work.
Cat<std::vector<int>::iterator> names the iterator category type that iterator_traits reports for that iterator. This is a type, and it was produced entirely at compile time.
Iterator Categories as Tags
Tag dispatch is a technique that uses iterator categories to dispatch to different implementations. The category of an iterator acts as a tag, and the compiler uses that tag to choose a matching implementation at compile time. The source states that tag dispatch is supported by iterator_traits, which is why the category is read through iterator_traits rather than computed by hand.
The standard iterator categories form a hierarchy. The random access category is the most capable, and each stronger category also counts as every weaker category it extends. So a random access iterator can be matched by an overload written for a weaker category, and the compiler prefers the most specific match when several are available.
The hierarchy matters because overload matching works through it. A routine written for the weakest category can accept any iterator, while a routine written for the strongest category accepts only iterators that support its operations. The compiler's choice follows from the tag each iterator reports through iterator_traits.
How sort() Chooses an Implementation
The source explains that iterator_traits gives simple definitions of the type functions used in sort(). The generic sort() code does not check the iterator's kind by hand. It asks iterator_traits for the category, and that category decides which implementation the compiler uses. This is the connection between the two ideas in this article: iterator_traits supplies the type-level information, and tag dispatch uses it to choose behavior.
Mistakes With Type-Level Lookups
Common mistake Leaving out typename in a dependent alias
What it looks liketemplate<class It> using Cat = std::iterator_traits<It>::iterator_category;Why it failsThe lookup depends on the template parameter It, so the compiler cannot tell on its own that
iterator_categorynames a type. The name is not accepted as a type without the keyword.Do this insteadtemplate<class It> using Cat = typename std::iterator_traits<It>::iterator_category;Common mistake Misspelling the member name after the traits lookup
What it looks liketemplate<class It> using Cat = typename std::iterator_traits<It>::iterator_catagory;Why it failsThe lookup reaches the traits class correctly, but the nested member name does not exist, so the failure is at the last name in the chain rather than at the template argument.
Do this insteadUse the exact member name iterator_category after the :: in the traits lookup.
Common mistake Passing a container where an iterator is required
What it looks likeusing Cat = typename std::iterator_traits<std::vector<int>>::iterator_category;Why it failsiterator_traitsis keyed by an iterator type. A container type is not the iterator the library was asked to describe, so the lookup does not give the category you expect.Do this insteadSupply the iterator type, such as std::vector<int>::iterator, as the argument to iterator_traits.
Common mistake Assuming the category is checked at run time, when it is selected while the program is compiled
When a type-level lookup fails, the compiler's error usually points into the alias expression. To locate the failing name, check the chain in order: the template argument you supplied, the traits class named after it, and the nested member name at the end. The first of those three that does not exist or is spelled differently is the one to fix.
Practice Locating the Failing Name
Read this alias and identify which name in the chain is wrong: template<class It> using Cat = typename std::iterator_traits<It>::iterator_catgory; Name the failing part and write the corrected line.
Hints
- Split the expression into three parts: the template parameter It, the traits class std::
iterator_traits<It>, and the nested member after the ::. - Compare the final member name letter by letter with
iterator_category. - The typename keyword is already present, so the problem is not there.
Write a type alias named Iter_category_of that takes a template parameter named It and names the iterator category of that iterator type. Use typename and std::iterator_traits. Then explain, in one sentence, why this alias does not run any code when the program executes.
Hints
- Start from the pattern template<class It> using Cat = typename std::
iterator_traits<It>::iterator_category; and rename the alias. - Think about when the compiler evaluates a type alias, not when a function is called.
Key Takeaways
- A type function takes a type as input and produces a type as output at compile time, and a template alias with typename can name the type it produces.
- The class template
iterator_traits, provided by the standard header <iterator>, supplies type-level information about an iterator, including its iterator category. - Tag dispatch uses the iterator category as a tag so that one generic routine can select a matching implementation at compile time.
sort()relies oniterator_traitsfor the type functions it uses, so the category is read from the traits rather than checked by hand.- When a type-level lookup fails, check the template argument, the traits class, and the nested member name in that order to find the faulty name.