Iterator Traits and Type Functions
The standard header <iterator> provides the class template iterator_traits, which supplies information about iterators.
Two Lines That Name Types
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 that choice while the program is being compiled, before any values exist. This article starts from two short lines of C++ that do exactly that kind of work, and then shows how the standard library applies the same technique to iterators.
Keep one question in mind as you read: if a generic function needs to behave differently depending on what kind of iterator it receives, how can it find out what kind that is without running any code first? The answer the standard library uses is the subject of the rest of this article. The two names that the source introduces, Iterator_type and Iterator_category, each stand for a type rather than a value, and tracing what each one refers to is the best way to see the whole mechanism.
What a Type Function Does
A type function takes a type as input and produces a type as output, and it does this at compile time. No values are computed while the program runs; the compiler works out the answer while it translates the code.
The Iterator_type alias from the source is a type function. Given a container type C, it names C's iterator type. You can think of it as a lookup table with one row: put in a container type, and the alias gives back the iterator type that container declares. Because the lookup happens in the compiler, the result can shape the code that is generated for that exact type.
A class template is the pattern the compiler uses to build a concrete class for a specific type. Writing std::iterator_traits<Iter> with a concrete iterator type in place of Iter asks the library for the information it holds about that iterator. The source describes this information as a set of type-level facts, and the one it names is the iterator category, accessed as the member iterator_category.
Reading Iterator_type Step by Step
The pattern from the source can be written in a generic form. The first line declares a template parameter C that stands for any container type. The second line defines a new name, Iterator_type, as an alias for C::iterator. The diagram below labels each part of that line so you can see what each piece contributes.
- template <typename C> declares C as a placeholder that the compiler fills in with a real type later
- using Iterator_type = ... defines a template alias, a new name for a type computed from C
- typename tells the compiler that C::iterator names a type
- C::iterator is the nested member path that is looked up inside whatever type C turns out to be
Read the alias from left to right in the same order the compiler does. The template line says the alias works for any C. The using line gives the new name. The typename keyword marks C::iterator as a type. The final part, iterator, is a member that C must provide. If you substitute a concrete container type for C, the compiler looks inside that container for its member named iterator and returns the type it finds there.
Why typename Appears in the Alias
The typename keyword appears in the middle of the Iterator_type line for a reason the source spells out. Because the expression depends on the template parameter C, the compiler cannot know on its own whether C::iterator names a type or something else, such as a value or a function. Writing typename tells it that the name is a type. The same keyword appears in the Iterator_category line for the same reason, where iterator_traits<Iter> depends on the template parameter Iter and the compiler needs to be told that iterator_category is a type.
What iterator_traits Returns for an Iterator
The standard header <iterator> provides the class template iterator_traits, which supplies information about iterators. The information comes out as member names. The member named in the source is iterator_category, and you read it by writing the name of the iterator type inside the angle brackets and then the member name after the scope operator. The diagram shows the lookup path: the iterator type goes into the template, and the member comes out.
The diagram also shows something the source does not spell out in detail: a raw pointer does not declare nested members of its own, yet iterator_traits still gives it the same set of type-level facts. This is why generic code can ask for iterator_category without first checking whether the iterator is a class or a pointer. The lookup is uniform across both.
Tracing a Type Function Through a Container
The example below is an original illustration that uses only the pattern shown in the source. It defines a small container type whose iterator member is a plain pointer. Follow the alias from the container type to the answer it produces.
- Int_box declares a nested name iterator that is an alias for int*
- Iterator_type<Int_box> substitutes Int_box for C
- The compiler resolves Int_box::iterator, which is int*
- p is therefore declared as an int* that starts out null
Resolving Iterator_type<Int_box>
What type does Iterator_type<Int_box> name?
- 1Start with the alias
Iterator_type is defined as typename C::iterator for any C.
- 2Substitute the container
Replace C with Int_box, giving typename Int_box::iterator.
- 3Look up the nested member
Int_box declares iterator as int*, so the lookup finds that member.
- 4Read the result
The alias names int*, and the declaration of p uses that type.
Iterator_type<Int_box> names int*.
Tag Dispatch Through Iterator Categories
Tag dispatch is a technique that uses iterator categories to choose among different implementations of the same work. One generic function can pick one of several versions based on the kind of iterator it receives. The category of the iterator acts as a tag, and the compiler selects the 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.
Read the diagram from top to bottom. The generic code never checks the iterator's kind by hand. It asks iterator_traits for the category, and the 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 that information to choose behavior.
Why sort() Depends on Type Functions
The source says that iterator_traits gives simple definitions of the type functions used in sort() and supports tag dispatch. In other words, sort() does not hard-code what kind of iterator it is working with. It reads the category through a type function and lets tag dispatch pick the version of the work that suits that category.
The plan for this article notes that sort() only compiles when the iterator category supports constant-time jumps, which is the random-access category. The reasoning follows from the dispatch step above: if the category does not offer that capability, the overload chosen for sort() cannot be built for that iterator type. Use the category tag, not your own guess about the iterator, to decide whether a given iterator can be sorted.
Mistakes in Type-Level Lookups
Common mistake Omitting typename before a dependent nested name
What it looks liketemplate <typename C> using Iterator_type = C::iterator;Why it failsC depends on the template parameter, so the compiler cannot tell whether C::iterator is a type. Without typename it does not treat the name as a type, and the alias fails to resolve.
Do this insteadtemplate <typename C> using Iterator_type = typename C::iterator;Common mistake Naming a member the container does not declare
What it looks likestruct Int_box { using iterator = int*; }; Iterator_type<Int_box>::value_type x;Why it failsThe lookup inside Int_box finds only the members it declares. A name it does not provide cannot be resolved, so the failing name is the one that is missing from the container.
Do this insteadCheck that the container declares the member the alias asks for, and name only that member.
Common mistake Assuming a raw pointer has no type-level information
Why it failsiterator_traitssupplies the same set of type-level facts for a raw pointer as it does for a class iterator, so generic code does not need to special-case pointers.Do this insteadRead the category through iterator_traits<It> for every iterator type, including pointers.
When a type lookup fails, the step to inspect is the nested name lookup, not the outer alias. Find the qualified name that the compiler could not resolve as a type, which is the name after the scope operator in the lookup path, and check whether typename appears in front of it and whether the container actually declares it.
Practice: Find the Failing Name
Consider this alias: template <typename C> using Iterator_category = std::iterator_traits<C>::iterator_category; Explain at which step the lookup could fail, and name the identifier that the compiler would report as the failing one if the keyword typename were removed.
Hints
- Ask whether the expression std::
iterator_traits<C> depends on the template parameter C. - Identify the last name in the path, the member after the scope operator, and decide whether it is meant to be a type.
- The source says that typename tells the compiler that the name is a type when the expression depends on a template parameter.
Key Takeaways
- A type function works on types at compile time, and a template alias such as Iterator_type = typename C::iterator names the type it produces.
- The class template
iterator_traits, provided by the header <iterator>, supplies type-level information about an iterator, including the memberiterator_category. - The typename keyword is required when a name depends on a template parameter, because it tells the compiler that the name is a type.
- Tag dispatch uses the iterator category, read through
iterator_traits, to select among implementations, andsort()relies on this mechanism.