Concepts / Tag Dispatch and Iterator Categories

Tag Dispatch and Iterator Categories

The standard header <iterator> provides the class template iterator_traits, which supplies information about iterators.

  • Programming

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
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.

Iteran iterator typeiterator_categorythe category tagvalue_typethe element typeraw pointer typea special casedifference_typethe distance typespecializationpointer handled directlymembermembermembermatches
For a given iterator type, iterator_traits gives back a set of associated types, including the iterator category. A raw pointer is handled through its own specialization rather than the generic case.
In the real world

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.

cpp
Notes on the code
  • 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.

template<class It>template parameterusing Cat =alias being definedtypenamename is a typestd::iterator_traits<It>the traits lookup::iterator_categoryresulting typethenthenthenmember
Reading the line left to right: the template parameter, the alias being defined, the typename marker, and the lookup that produces the resulting type.

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.

Worked example

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?

  1. 1
    Fill the template parameter

    It is replaced by std::vector<int>::iterator. The alias is now a specific lookup rather than a pattern.

  2. 2
    Ask 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.

  3. 3
    Read the member

    The member iterator_category is read from that traits class. The typename keyword has told the compiler that this member is a type.

  4. 4
    Name 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.

Result

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.

inputweakest tagforwardextends inputbidirectionalextends forwardrandom accessextends bidirectionalis ais ais a
Each arrow points from a stronger category to the weaker category it extends. A random access tag therefore also matches overloads written for bidirectional, forward, and input tags.

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.

sort() calledwith an iterator typeiterator_traits<Iter>type-level lookupiterator_categorytag type readmatching overloadbest category matchchosen implementationcompiled into the programItermembertagselects
A call to sort() passes an iterator type into iterator_traits. The category read from the traits selects one overload, and the compiler generates only that implementation.

Mistakes With Type-Level Lookups

  • Common mistake

    Leaving out typename in a dependent alias

    What it looks like
    template<class It> using Cat = std::iterator_traits<It>::iterator_category;
    Why it fails

    The lookup depends on the template parameter It, so the compiler cannot tell on its own that iterator_category names a type. The name is not accepted as a type without the keyword.

    Do this instead
    template<class It> using Cat = typename std::iterator_traits<It>::iterator_category;
  • Common mistake

    Misspelling the member name after the traits lookup

    What it looks like
    template<class It> using Cat = typename std::iterator_traits<It>::iterator_catagory;
    Why it fails

    The 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 instead

    Use 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 like
    using Cat = typename std::iterator_traits<std::vector<int>>::iterator_category;
    Why it fails

    iterator_traits is 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 instead

    Supply 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

Edge case

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

EASY

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.
MEDIUM

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 on iterator_traits for 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.

Related concepts

More concepts starting with T