How it works...

The standard library defines five categories of iterators:

  • Input iterators: These are the simplest category and guarantee validity only for single-pass sequential algorithms. After being incremented, the previous copies may become invalid.
  • Output iterators: These are basically input iterators that can be used to write to the pointed element.
  • Forward iterators: These can read (and write) data to the pointed element. They satisfy the requirements for input iterators and, in addition, must be default constructible and must support multi-pass scenarios without invalidating the previous copies.
  • Bidirectional iterators: These are forward iterators that, in addition, support decrementing, so they can move in both directions.
  • Random access iterators: These support access to any element in the container in constant time. They implement all the requirements for bidirectional iterators, and, in addition, support arithmetic operations + and -, compound assignments += and -=, comparisons with other iterators with <, <=, >, >=, and the offset dereference operator.

Forward, bidirectional, and random access iterators that also implement the requirements of output iterators are called mutable iterators.

In the previous section, we saw how to implement random access iterators, with a step-by-step walkthrough of the requirements of each category of iterators (as each iterator category includes the requirements of the previous category and adds new requirements). The iterator class template is common for both constant and mutable iterators, and we have defined two synonyms for it called iterator and constant_iterator.

After implementing the inner iterator class template, we also defined the begin() and end() member functions that return an iterator to the first and the one-past-last element in the array. These methods have overloads to return mutable or constant iterators, depending on whether the dummy_array class instance is mutable or constant.

With this implementation of the dummy_array class and its iterators, we can write the following samples. For more examples, check the source code that accompanies this book:

    dummy_array<int, 3> a;
a[0] = 10;
a[1] = 20;
a[2] = 30;

std::transform(a.begin(), a.end(), a.begin(),
[](int const e) {return e * 2; });

for (auto&& e : a) std::cout << e << std::endl;

auto lp = [](dummy_array<int, 3> const & ca)
{
for (auto const & e : ca)
std::cout << e << std::endl;
};

lp(a);

dummy_array<std::unique_ptr<Tag>, 3> ta;
ta[0] = std::make_unique<Tag>(1, "Tag 1");
ta[1] = std::make_unique<Tag>(2, "Tag 2");
ta[2] = std::make_unique<Tag>(3, "Tag 3");

for (auto it = ta.begin(); it != ta.end(); ++it)
std::cout << it->id << " " << it->name << std::endl;