Iterators
Iterators are the glue between containers and algorithms. An iterator marks a position in a container and can move to the next element; every standard container provides them, so the same algorithm can work on a vector, a list, a set or an array.
begin and end
begin() points to the first element and end() points one past the last. Together they describe a half-open range [begin, end):
#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 << ' '; // * gives the element } std::cout << '\n'; }
10 20 30
An iterator looks and behaves much like a pointer: *it reads the element, ++it moves on, and it->member accesses a member. The range-based for loop is shorthand for exactly this loop. Never dereference end() — there's no element there.
Why the extra position?
A one-past-the-end marker means an empty range is simply begin == end, and functions can report "not found" by returning end():
#include <algorithm> #include <iostream> #include <string> #include <vector> int main() { std::vector<std::string> names{"Ana", "Ben", "Chloe"}; auto it = std::find(names.begin(), names.end(), "Ben"); if (it != names.end()) { std::cout << "found " << *it << " at index " << (it - names.begin()) << '\n'; } if (std::find(names.begin(), names.end(), "Zoe") == names.end()) { std::cout << "Zoe not found\n"; } }
found Ben at index 1 Zoe not found
Iterator categories
Containers offer different capabilities, and algorithms state which ones they need:
| Category | Can do | Containers |
|---|---|---|
| Forward | ++it, read/write | std::forward_list, unordered containers |
| Bidirectional | also --it | std::list, std::map, std::set |
| Random access | also it + n, it[n], it2 - it1, < | std::deque |
| Contiguous | also elements adjacent in memory | std::vector, std::array, std::string |
That's why std::sort works on a vector but not a std::list (which has its own sort() member instead).
Reverse and const iterators
#include <iostream> #include <string> int main() { std::string word = "stressed"; for (auto it = word.rbegin(); it != word.rend(); ++it) std::cout << *it; std::cout << '\n'; std::string copy(word.rbegin(), word.rend()); // construct from a range std::cout << copy << '\n'; for (auto it = word.cbegin(); it != word.cend(); ++it) { // *it = 'x'; // error: const iterator, read-only } }
desserts desserts
rbegin/rend walk backwards; cbegin/cend give read-only iterators.
Iterators into maps
A map's iterator points to a std::pair — use ->first for the key and ->second for the value:
#include <iostream> #include <map> #include <string> int main() { std::map<std::string, int> ages{{"Ana", 31}, {"Ben", 27}, {"Chloe", 35}}; auto it = ages.find("Ben"); it->second += 1; std::cout << it->first << " is now " << it->second << '\n'; auto first_c = ages.lower_bound("C"); // first key >= "C" std::cout << "first name from C: " << first_c->first << '\n'; }
Ben is now 28 first name from C: Chloe
Invalidation
Modifying a container can invalidate its iterators — using one afterwards is undefined behaviour. The classic bug is erasing inside a range-for loop. erase returns a valid iterator to the next element; use it:
#include <iostream> #include <vector> int main() { std::vector<int> v{1, 2, 3, 4, 5, 6}; for (auto it = v.begin(); it != v.end(); ) { if (*it % 2 == 0) it = v.erase(it); // erase returns the next valid position else ++it; } for (int x : v) std::cout << x << ' '; std::cout << '\n'; }
1 3 5
For this particular job, C++20's std::erase_if(v, predicate) (lesson 18) is simpler still.
| Container | Invalidated by |
|---|---|
vector, string | Any reallocation (growth); insert/erase invalidates from that point on |
deque | Insertion anywhere; erasure in the middle |
list, map, set | Only erasing that element |
| Unordered containers | Rehashing (growth); erasing that element |
Helper functions
#include <iostream> #include <iterator> #include <list> int main() { std::list<char> letters{'a', 'b', 'c', 'd', 'e'}; auto it = letters.begin(); std::advance(it, 3); // move 3 steps std::cout << *it << ' ' << *std::next(it) << ' ' << *std::prev(it) << ' ' << std::distance(letters.begin(), it) << '\n'; }
d e c 3
std::advance, std::next, std::prev and std::distance work for every iterator category, using the fastest method available.
Try it yourself
Use iterators to print the elements of a vector between the first and last occurrence of a marker value.
7 3 9