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)
| Container | Iterator Category |
|---|---|
| vector, deque, array | Random Access |
| list, map, set | Bidirectional |
| forward_list, unordered_* | Forward |
| istream_iterator | Input |
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
- Iterators decouple algorithms from containers.
- Know which category your container provides — it determines which algorithms work.
- Use
std::advance,std::next,std::prevfor generic code. - C++20 ranges simplify the pattern further.