C/C++ Arena

Step 3 of 5

Iterator concepts and algorithms

Standard algorithms need to know what kind of iterator they're given. Can it go backwards? Jump ahead by 100 in one step? The answers decide which algorithms are allowed and how fast they are. The main categories:

Category Can do Examples
forward *, ++, ==, pass over the data many times std::forward_list, unordered_map
bidirectional forward, plus -- std::list, std::map
random access bidirectional, plus it + n, it[n], < in O(1) std::vector, pointers

C++20 checks these with concepts such as std::forward_iterator<It>. For your iterator to qualify as a forward iterator, it must declare a few member types and support a couple more operations:

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <iterator>

class Evens {                           // 0, 2, 4, ... below a limit
public:
    explicit Evens(int limit) : limit_(limit) {}

    class Iterator {
    public:
        using iterator_category = std::forward_iterator_tag;
        using value_type = int;
        using difference_type = std::ptrdiff_t;

        Iterator() = default;                          // required: default constructible
        explicit Iterator(int v) : v_(v) {}
        int operator*() const { return v_; }
        Iterator& operator++() { v_ += 2; return *this; }
        Iterator operator++(int) { Iterator old = *this; ++*this; return old; }   // it++
        bool operator==(const Iterator& o) const { return v_ == o.v_; }

    private:
        int v_ = 0;
    };

    Iterator begin() const { return Iterator(0); }
    Iterator end() const { return Iterator(limit_ % 2 == 0 ? limit_ : limit_ + 1); }

private:
    int limit_;
};

static_assert(std::forward_iterator<Evens::Iterator>);

int main() {
    Evens e(10);
    std::cout << std::count_if(e.begin(), e.end(), [](int x) { return x % 4 == 0; }) << "\n";
    std::cout << std::ranges::distance(e) << "\n";
    auto it = std::ranges::find(e, 6);
    std::cout << *it << " found\n";
}
3
5
6 found

How it works

Your task

Make Countdown::Iterator a proper forward iterator: add the three member types, a default constructor, and post-increment. Countdown(n) yields n, n-1, ..., 1.

Challenge 2

Make Lines::Iterator a proper input iterator that reads a stream line by line: all five member types (iterator_category = std::input_iterator_tag, value_type = std::string, difference_type = std::ptrdiff_t, pointer = const std::string* and reference = const std::string&; the older algorithms need all five), operator* returning the current line, ++ reading the next one, and ==. An iterator whose stream pointer is nullptr is the end; reading past the last line turns an iterator into the end.

Challenge 3

A bidirectional iterator can also go back with --. Write Span(lo, hi), a range of the ints lo to hi - 1, whose iterator satisfies std::bidirectional_iterator: the member types (std::bidirectional_iterator_tag), a default constructor, *, ==, and pre- and post- ++ and --. The tests walk it backwards with std::reverse_iterator.

Previous: Writing an iterator class Next: A lazy range