C++ by Example
C++ by Example

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.

C++ by Example
C++ by Example

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 →