Skip to content
Theory · Containers and Iterators · lesson 5 of 11

Iterators

A range-for loop visits every element of a container but does not tell where the current one lies. Yet functions that erase elements need exactly that: the element's place in the container. std::map and std::set have no indices, so an element's place is marked by an iterator, an object that points to the element:

C++
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v{10, 20, 30};
    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << ' ';  // 10 20 30
    }
}

begin and end

v.begin() returns an iterator to the first element, and v.end() returns one to the place right after the last. There is no element at end(), and reading *v.end() is undefined behavior. That is why the loop runs while it != v.end(). In an empty container, begin() equals end(), and the loop body never runs.

An iterator resembles a pointer: *it gives the element, and ++it moves to the next one. The full name of its type is std::vector<int>::iterator, so the type is deduced with auto. Iterators of std::map and std::set work the same way, and a range-for loop is a short form of such a loop.

Erasing by iterator

v.erase(it) removes the element it points to and shifts the following elements into its place. After that, it and every iterator after it become invalidated: using them is undefined behavior. In return, erase gives back an iterator to the element that followed the erased one. So a loop that erases advances the iterator itself:

C++
std::vector<int> v{1, 2, 4, 5, 6};
for (auto it = v.begin(); it != v.end();) {
    if (*it % 2 == 0) {
        it = v.erase(it);
    } else {
        ++it;
    }
}
for (int x : v) {
    std::cout << x << ' ';  // 1 5
}

The loop header has no step. After an erase, it already points to the next element, and ++it runs only when nothing was erased. In a std::map or a std::set, erase(it) invalidates only the iterator to the erased element, and the erase loop looks the same.

Common mistake

A loop over a vector adds elements to the same vector:

C++
std::vector<int> v{1, 2, 3};
for (int x : v) {
    v.push_back(x * 10);  // ✗
}

A range-for loop gets the begin() and end() iterators once, before the first iteration. push_back always invalidates end(), and on a reallocation it also invalidates every iterator, pointer and reference to the elements. So this loop is undefined behavior whatever the capacity. Store the size before the loop and walk the vector by index: an index stays correct even after a reallocation.

C++
std::vector<int> v{1, 2, 3};
std::size_t n = v.size();
for (std::size_t i = 0; i < n; ++i) {
    v.push_back(v[i] * 10);
}
for (int x : v) {
    std::cout << x << ' ';  // 1 2 3 10 20 30
}