C++ course menu
C++ Iterators: begin, end, Iterator Categories and Invalidation
Learn C++ iterators: begin() and end(), dereferencing and advancing, const and reverse iterators, using iterators with algorithms, std::next and std::distance, iterator categories, erasing while iterating, and iterator invalidation.
On this page
An iterator is an object that points to an element inside a container, and knows how to move to the next one. Iterators are the glue of the STL: every container provides them, and every algorithm uses them. They behave much like pointers: *it gives the element and ++it moves forward.
begin() and end()#
c.begin() points to the first element. c.end() points one past the last element; it is a marker, not an element, and must never be dereferenced:
#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 << ' ';
}
std::cout << '\n';
}10 20 30This half-open range [begin, end) makes an empty container simple: begin() == end(), and the loop runs zero times. The range-based for loop is written in terms of exactly this loop.
Changing elements through an iterator#
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> names = {"asha", "ravi"};
for (auto it = names.begin(); it != names.end(); ++it) {
(*it)[0] = static_cast<char>((*it)[0] - 'a' + 'A'); // capitalize
std::cout << *it << " has " << it->size() << " letters\n";
}
}Asha has 4 letters
Ravi has 4 lettersit->size() is shorthand for (*it).size(), just like with pointers.
const and reverse 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 reversed(word.rbegin(), word.rend()); // build from a range
std::cout << reversed << '\n';
}desserts
dessertsMany containers can be constructed from any iterator range, which is a handy way to copy or convert data.
Iterators and algorithms#
Algorithms take a range as two iterators, so you can work on part of a container:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {9, 4, 7, 1, 8, 2};
std::sort(v.begin(), v.begin() + 3); // sort only the first three
for (int x : v) std::cout << x << ' ';
std::cout << '\n';
auto it = std::find(v.begin(), v.end(), 8);
if (it != v.end()) {
std::cout << "8 found at index " << (it - v.begin()) << '\n';
}
}4 7 9 1 8 2
8 found at index 4Search algorithms return end() to mean "not found", so always compare the result with end() before using it.
std::next, std::prev and std::distance#
Not every iterator supports + 3 (for example, a std::list iterator can only step one at a time). The functions in <iterator> work with all of them:
#include <iostream>
#include <iterator>
#include <list>
int main() {
std::list<char> letters = {'a', 'b', 'c', 'd', 'e'};
auto third = std::next(letters.begin(), 2);
auto last = std::prev(letters.end());
std::cout << *third << ' ' << *last << '\n';
std::cout << std::distance(letters.begin(), last) << '\n';
}c e
4Iterator categories#
Containers provide iterators with different abilities, and each algorithm states which it needs:
That is why std::sort (which needs random access) does not work on a std::list; a list has its own .sort() method instead.
Erasing while iterating#
erase invalidates the iterator to the erased element, but returns an iterator to the next one. Use that return value, and only increment when you did not erase:
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> stock = {{"pens", 40}, {"books", 0}, {"bags", 5}, {"caps", 0}};
for (auto it = stock.begin(); it != stock.end(); ) {
if (it->second == 0) {
it = stock.erase(it); // erase returns the next valid iterator
} else {
++it;
}
}
for (const auto& [item, count] : stock) {
std::cout << item << ' ' << count << '\n';
}
}bags 5
pens 40For vectors and strings, C++20's std::erase_if is simpler; for maps and sets, std::erase_if also works in C++20.
Iterator invalidation#
Some operations make existing iterators (and pointers and references to elements) invalid: using them afterwards is undefined behaviour.
The safest habit: do not keep iterators around across operations that change the container.
Common mistakes#
Practice#
Practice exercise
Given std::vector<int> v = {3, 8, 2, 8, 5, 8};, use std::find in a loop to print the index of every 8.
Show hint
After finding one match at it, search again starting from std::next(it).
Show solution
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
int main() {
std::vector<int> v = {3, 8, 2, 8, 5, 8};
auto it = std::find(v.begin(), v.end(), 8);
while (it != v.end()) {
std::cout << std::distance(v.begin(), it) << ' ';
it = std::find(std::next(it), v.end(), 8);
}
std::cout << '\n';
}1 3 5Key takeaway#
Finished this lesson?