Iteratoren und Generatoren in C++: Eine ausführliche Anleitung
What is it and why? (real-life analogy)
Imagine you are a librarian, and in front of you stands a huge bookshelf with books. You need to write down the titles of all the books on a piece of paper. You have two ways to do this:
First way (without an iterator): You take all the books off the shelf at once, pile them on the table, and only then start copying. But what if there are 10,000 books? The table will break, your arms will get tired, and there won't be enough space. In programming, this is called "loading everything into memory."
Second way (with an iterator): You approach the shelf, look at the first book, write down its title. Then you move your gaze to the second one, write it down. And so on — book by book, without taking them off the shelf. You don't hold all the books in your hands at once, you simply point your finger at each next one.
This programmer's "pointing finger" is the iterator. And the process when the finger itself moves from book to book until it reaches the end of the shelf is iteration.
Generator is a magic book that writes its own text when you open it. You ask: "Give me the next chapter," and the book creates it right before your eyes. It doesn't need to store all the chapters inside itself — it invents them as needed. In C++, generators are functions that can "fall asleep" and "wake up," remembering where they left off.
💡 Main idea: Iterators allow you to traverse elements without knowing how they are structured internally. Generators allow you to create elements "on the fly" without storing them all in memory.
Theory: how it works from the inside
Iterators: pointers with intelligence
In C++, an iterator is an object that behaves like a pointer. But while a regular pointer simply stores a memory address, an iterator can be trickier. It can traverse elements of a linked list (where each element "doesn't know" where the next one is), or elements of a tree, or even lines in a file on disk.
Each container (vector, list, map, set) has its own iterators. They know the internal structure of the container and know how to correctly move to the next element.
Iterator categories (from simple to complex):
| Category | What it can do | Example container |
|---|---|---|
| Input | Read only, forward only | Input stream (cin) |
| Output | Write only, forward only | Output stream (cout) |
| Forward | Read and write, forward only | forward_list |
| Bidirectional | Read, write, forward and backward | list, set, map |
| Random Access | All of the above + jump 5 steps forward or backward in O(1) | vector, deque, array |
⚠️ Important: The more powerful the iterator, the faster it works. But not all containers can provide a powerful iterator. For example, a linked list has no random access — to get to the 5th element, you need to go through the 4 previous ones.
Generators: long-lived functions
In C++20, coroutines appeared — these are generators. A regular function works like this: called — it executed — returned a result — that's it, the function died. A coroutine works differently: it can pause (yield), save its state, and then continue from the same place.
Imagine you are reading a book. A regular function is when you read the entire book in one sitting and retell it to a friend. A coroutine is when you read one page at a time and every time a friend asks "what's next?", you open the book to the place where you stopped last time.
Inside a coroutine, the compiler creates a special structure that stores:
- All local variables of the function
- The current execution point (which line we stopped at)
- A call counter (how many times we have already returned a value)
Syntax in C++: all forms and variants
Basic iterators
Each container provides the begin() and end() methods. begin() points to the first element, end() points to the element "past the last one" (not the last one!).
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
// Get an iterator to the beginning
std::vector<int>::iterator it = numbers.begin();
// Dereference the iterator (get the value)
std::cout << *it << std::endl; // Outputs 10
// Move to the next element
it++;
std::cout << *it << std::endl; // Outputs 20
return 0;
}
Range-based for loop
Since C++11, a convenient syntax has appeared that hides the work with iterators:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// This loop uses iterators internally, but we don't see them
for (int x : vec) {
std::cout << x << " ";
}
// Outputs: 1 2 3 4 5
// If you need to change elements, use a reference
for (int& x : vec) {
x = x * 2;
}
// Now vec = {2, 4, 6, 8, 10}
return 0;
}
Constant and reverse iterators
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// const_iterator — cannot change the value through it
std::vector<int>::const_iterator cit = vec.cbegin();
// *cit = 10; // Error! Cannot change
// Reverse iterator — traverses from end to beginning
std::vector<int>::reverse_iterator rit = vec.rbegin();
std::cout << *rit << std::endl; // Outputs 5 (last element)
// Range-based for in reverse order (C++20)
for (int x : vec | std::views::reverse) {
std::cout << x << " "; // Outputs: 5 4 3 2 1
}
return 0;
}
Iterators for custom types
#include <iostream>
#include <vector>
#include <string>
struct Student {
std::string name;
int grade;
};
int main() {
std::vector<Student> group = {
{"Alice", 90},
{"Bob", 85},
{"Charlie", 95}
};
// Iterator to a structure
std::vector<Student>::iterator it = group.begin();
std::cout << it->name << ": " << it->grade << std::endl;
// Outputs: Alice: 90
// Modify through iterator
it->grade = 92;
return 0;
}
Generator via coroutine (C++20)
#include <iostream>
#include <generator>
#include <ranges>
// Generator that yields even numbers up to n
std::generator<int> even_numbers(int n) {
for (int i = 0; i <= n; i += 2) {
co_yield i; // Pause and return value
}
}
int main() {
// Use the generator in a range-based for loop
for (int x : even_numbers(10)) {
std::cout << x << " "; // Outputs: 0 2 4 6 8 10
}
return 0;
}
Generator with state (Fibonacci numbers)
#include <iostream>
#include <generator>
// Generator that yields Fibonacci numbers
std::generator<int> fibonacci(int count) {
int a = 0, b = 1;
for (int i = 0; i < count; ++i) {
co_yield a; // Return current value
int next = a + b;
a = b;
b = next;
}
}
int main() {
std::cout << "First 10 Fibonacci numbers: ";
for (int x : fibonacci(10)) {
std::cout << x << " ";
}
// Outputs: 0 1 1 2 3 5 8 13 21 34
return 0;
}
Common mistakes and pitfalls
1. Iterator invalidation
If you modify a container (add or remove elements), existing iterators may become invalid. This is the most common mistake.
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin(); // Points to element 1
vec.push_back(6); // May reallocate memory!
// it may now point to freed memory!
// std::cout << *it; // Undefined behavior!
2. Off-by-one error with end()
end() points to the element "past the last one". You cannot dereference it!
std::vector<int> vec = {1, 2, 3};
auto it = vec.end();
// std::cout << *it; // Error! end() does not point to an element
it--; // Now points to the last element (3)
std::cout << *it; // Outputs 3
3. Mixing iterators from different containers
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
auto it = vec1.begin();
// vec2.insert(vec2.begin(), it); // Error! Cannot insert iterator from vec1 into vec2
// Need to dereference first:
vec2.insert(vec2.begin(), *it); // Correct
Conclusion
Iterators and generators are powerful tools that make code more flexible and efficient. Iterators separate the traversal algorithm from the container structure, and generators allow working with infinite or very large sequences without loading them entirely into memory.
Remember the main rule: if you can use a range-based for loop — use it. It is safer and more readable. Explicit iterators are needed when you need more control (for example, to skip elements or modify the container during traversal). Generators are useful when you need to create a sequence "on the fly" without storing it entirely.