← All Posts
C++ STL Series · Part 3 of 5

Iterators — The Glue of STL

Why Iterators Exist

Iterators are the abstraction that lets STL algorithms work with any container. Instead of writing sort_vector(), sort_list(), sort_deque(), you write one generic std::sort(begin, end) that works through iterators.

This design gives you M containers × N algorithms combinations with only M + N implementations, not M × N.

Iterator Categories

Each iterator supports a specific set of operations, arranged in a hierarchy:

▶ Iterator Traversal Animation

See how a forward iterator walks through a container element by element.

Input Iterator          →  read forward once
  ↓
Forward Iterator        →  read/write forward, multi-pass
  ↓
Bidirectional Iterator  →  + backward (--it)
  ↓
Random Access Iterator  →  + arithmetic (it + n, it[n], it1 - it2)
ContainerIterator Category
vector, deque, arrayRandom Access
list, map, setBidirectional
forward_list, unordered_*Forward
istream_iteratorInput
Why does it matter? std::sort requires Random Access iterators — so it works on vectors but not on lists. std::list provides its own .sort() member instead.

Common Iterator Operations

std::vector<int> v = {10, 20, 30, 40, 50};

auto it = v.begin();           // points to 10
std::advance(it, 2);           // now points to 30
auto dist = std::distance(v.begin(), it);  // 2

auto nxt = std::next(v.begin(), 3);  // points to 40
auto prv = std::prev(v.end());        // points to 50

Reverse Iterators

Every container with bidirectional iterators provides rbegin() and rend():

for (auto rit = v.rbegin(); rit != v.rend(); ++rit)
    std::cout << *rit << " ";  // 50 40 30 20 10

Writing a Custom Iterator

For a range of even numbers, you can create a lightweight forward iterator:

struct EvenRange {
    struct Iterator {
        int val;
        using iterator_category = std::forward_iterator_tag;
        using value_type = int;
        using difference_type = std::ptrdiff_t;
        using pointer = const int*;
        using reference = const int&;

        int operator*() const { return val; }
        Iterator& operator++() { val += 2; return *this; }
        Iterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }
        bool operator==(const Iterator& o) const { return val == o.val; }
        bool operator!=(const Iterator& o) const { return val != o.val; }
    };

    int start, stop;
    Iterator begin() const { return {start}; }
    Iterator end() const { return {stop}; }
};

// Usage: iterate even numbers 0..20
for (int x : EvenRange{0, 20})
    std::cout << x << " ";  // 0 2 4 6 ... 18

Iterator Adaptors

std::back_inserter

Turns push_back into an output iterator, letting algorithms append into a container:

std::vector<int> src = {1, 2, 3};
std::vector<int> dst;

std::copy(src.begin(), src.end(), std::back_inserter(dst));
// dst is now {1, 2, 3}

Stream Iterators

// Read whitespace-separated ints from stdin
std::vector<int> nums(
    std::istream_iterator<int>(std::cin),
    std::istream_iterator<int>()
);

C++20: Ranges and Sentinels

C++20 replaces iterator pairs with ranges and introduces sentinels (end markers that can have a different type than the begin iterator), making code cleaner:

#include <ranges>
#include <algorithm>

std::vector<int> v = {5, 3, 1, 4, 2};
std::ranges::sort(v);  // no need for begin/end

Summary