Iterators
2 min read
Iterators
An iterator is an object that points to an element in a container and knows how to move to the next one. Every STL container provides iterators; every STL algorithm takes iterators. This separation makes it possible to run any algorithm on any container.
Basic iterator usage
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {10, 20, 30, 40, 50};
// Using iterators explicitly
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// Reverse iteration
for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n"; // 50 40 30 20 10
}
begin() returns an iterator to the first element. end() returns a past-the-end sentinel — one position beyond the last element. Dereferencing end() is undefined behavior.
Iterator categories
Iterators are classified by capability:
| Category | Operations | Example containers |
|---|---|---|
| Input | *, ++, single-pass read |
std::istream_iterator |
| Output | *=, ++, single-pass write |
std::back_insert_iterator |
| Forward | *, ++, multi-pass |
std::forward_list |
| Bidirectional | *, ++, -- |
std::list, std::map |
| Random access | *, ++, --, +n, -n, [] |
std::vector, std::array |
Algorithms document which category they require. std::sort needs random access; std::find needs only input.
Iterator adapters
The standard library provides useful iterator adapters:
#include <iterator>
#include <vector>
#include <algorithm>
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> even_dst;
// back_inserter: calls push_back on each write
std::copy_if(src.begin(), src.end(),
std::back_inserter(even_dst),
[](int x){ return x % 2 == 0; });
// even_dst: {2, 4}
// insert_iterator: calls insert at a specific position
std::vector<int> middle = {10, 20, 30};
std::vector<int> extra = {99, 99};
std::copy(extra.begin(), extra.end(),
std::inserter(middle, middle.begin() + 1));
// middle: {10, 99, 99, 20, 30}
std::advance and std::distance
#include <iterator>
std::vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
std::advance(it, 3); // advance 3 positions
std::cout << *it << "\n"; // 4
auto d = std::distance(v.begin(), it); // 3
std::advance is O(n) for bidirectional iterators and O(1) for random-access iterators. std::next and std::prev are non-mutating alternatives:
auto it2 = std::next(v.begin(), 2); // iterator to v[2], without modifying original
auto it3 = std::prev(v.end(), 1); // iterator to last element
Ranges (C++20)
Ranges are a higher-level abstraction over iterator pairs. std::ranges versions of algorithms take a range directly instead of begin/end:
#include <ranges>
#include <algorithm>
#include <vector>
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
std::ranges::sort(v); // no begin/end needed
auto evens = v | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * 10; });
for (int n : evens) std::cout << n << " "; // 20 40 60 80
std::cout << "\n";
Range views are lazy — they do not allocate or transform until you iterate. Chains of | build a pipeline that processes each element once.
Learn modern C++ through working code. Each chapter introduces one concept — variables, functions, classes, templates, smart pointers, concurrency — with a clear example, a line-by-line explanation, and notes on how it applies in real programs.
View book →