Adaptable iterators

Back to Programming techniques

A container data-structure in the C++ Standard Library often provides only two types of iterators: a mutable iterator iterator, and a non-mutable iterator const_iterator. This selection is not convenient for all uses. For example, while an iterator of std::map dereferences to a key-value pair, sometimes it is convenient to dereference to the key, and sometimes it is convenient to dereference to the value. In this section we present a technique for supporting various types of iterators to access the same part of a data-structure in different ways.

Problem

In general, a given part of a data-structure is desired to be accessed by an iterator with orthogonal compile-time properties, with the :th property having choices, for a total of possible iterator-types.

The varying types of iterators, for a given part, must interoperate; each of them must be equivalently usable for accessing the same part of a data-structure, except when explicitly disallowed by the data-structure (e.g. no mutation of the value through a const-iterator).

What is the best way to support such varying iterators?

Examples

For the RedBlack_Map, (constness, dereferencing), (const, non-const), and (key-value, key, value), for a total of 6 iterator types.

Solution

We solve the problem by using internal adaptation for our data-structures, and by using external adaptation for the other data-structures (such as the C++ Standard Library). The internal adaptation is to be used when the adaptation is meaningful only in that data-structure, or when the adaptation is expected to be common. The external adaptation is used for all the other use-cases.

Internal adaptation

Parametrize the iterator (range) with respect to the orthogonal properties, ensuring interoperability, and choose one parameter combination as the basic version. Use the basic-version in the interface of the data-structure, and provide functions in the iterator (range) to adapt the iterator one property at a time.

dataSet.begin().read().dereferenceData();
dataSet.range().read().dereferenceData();

For compatibility with the C++ Standard Library, also provide the c-prefix functions (e.g. cbegin()) for added constness (corresponding to an implicit .read()).

External adaptation

Create an iterator (range) adaptor which implements the changes by indirection to an underlying base-iterator (base-range).

secondIterator(dataSet.begin());
secondRange(dataSet.range());

Example

For iterators which dereference to std::pair, create an iterator adaptor which dereferences to iter->first or iter->second instead. The latter is provided by the Second_Iterator adaptor. This is a transition-adaptor while waiting for the C++14 support to be implemented in compilers.

In C++14, it is possible to create a generic iterator adaptor which takes in a generic lambda function to transform the dereferencing of the base iterator, with the resulting type deduced by decltype.

Using Boost’s iterator library, it is easy to define more general iterator adaptors, which adapt not only the dereferencing, but also the traversal.

Learn more