Iterator Types — Input, Output, Forward, Bidirectional, Random Access, Custom
In this tutorial, you will learn about Iterator Types. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ iterators provide a uniform interface for traversing data structures, with five categories — input, output, forward, bidirectional, and random access — determining traversal capabilities and algorithm compatibility.
What You'll Learn
You will understand the five iterator categories and their capabilities, use stream iterators for I/O, write custom iterators for user-defined containers, apply iterator traits and adaptors like reverse_iterator and move_iterator, and choose appropriate iterators for algorithm requirements.
Why It Matters
Iterators are the bridge between containers and algorithms. Understanding iterator categories tells you which algorithms work with which containers. Writing custom iterators allows your data structures to work seamlessly with the entire STL. Iterator adaptors like move_iterator enable powerful transformations without modifying containers.
Learning Path
graph LR
A["40: Ranges Library"] --> B["41: Iterator Types"]
B --> C["42: Function Templates"]
C --> D["43: Class Templates"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
Iterator Categories
#include <iostream>
#include <vector>
#include <list>
#include <forward_list>
#include <iterator>
#include <concepts>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> lst = {1, 2, 3, 4, 5};
std::forward_list<int> fl = {1, 2, 3, 4, 5};
// Random access iterator (vector, deque, array, string)
auto randIt = vec.begin();
randIt += 3; // O(1) advance
auto diff = randIt - vec.begin(); // O(1) distance
std::cout << "vec[3] = " << randIt[0] << "\n"; // O(1) indexing
// Bidirectional iterator (list, set, map)
auto biIt = lst.begin();
++biIt;
--biIt; // can go backwards
// Forward iterator (forward_list, unordered containers)
auto fwdIt = fl.begin();
++fwdIt;
// --fwdIt; // Error: cannot decrement forward iterator
// Input iterator (istream_iterator)
// Output iterator (ostream_iterator)
}
Category Hierarchy
Input Output
| |
v |
Forward |
| |
Bidirectional |
| |
Random Access |
|
Contiguous (C++17)
Each category includes all capabilities of the categories above it.
| Category | Read | Write | Multi-pass | ++ | -- | [] | +n |
|---|---|---|---|---|---|---|---|
| Input | Yes | No | No | Yes | No | No | No |
| Output | No | Yes | No | Yes | No | No | No |
| Forward | Yes | Yes | Yes | Yes | No | No | No |
| Bidirectional | Yes | Yes | Yes | Yes | Yes | No | No |
| Random Access | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
Stream Iterators
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <sstream>
int main() {
// Output iterator: writes to stream
std::ostream_iterator<int> out(std::cout, " ");
*out = 1; // writes "1 "
++out;
*out = 2; // writes "2 "
std::cout << "\n";
// Using with algorithm
std::vector<int> v = {10, 20, 30, 40, 50};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, ","));
std::cout << "\n"; // 10,20,30,40,50,
// Input iterator: reads from stream
std::istringstream input("10 20 30 40 50");
std::vector<int> parsed;
std::copy(std::istream_iterator<int>(input),
std::istream_iterator<int>(), // default = end
std::back_inserter(parsed));
// Read from cin with iterator
// std::vector<int> from_cin;
// std::copy(std::istream_iterator<int>(std::cin),
// std::istream_iterator<int>(),
// std::back_inserter(from_cin));
}
Iterator Adaptors
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// reverse_iterator: traverse backwards
std::copy(v.rbegin(), v.rend(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n"; // 5 4 3 2 1
// back_insert_iterator: push_back
std::vector<int> dest;
std::copy(v.begin(), v.end(), std::back_inserter(dest));
// front_insert_iterator: push_front (requires deque or list)
// insert_iterator: insert at arbitrary position
// move_iterator: move elements instead of copying
std::vector<std::string> src = {"hello", "world"};
std::vector<std::string> dst;
std::copy(std::make_move_iterator(src.begin()),
std::make_move_iterator(src.end()),
std::back_inserter(dst));
// src elements now in moved-from state
}
Custom Iterator
#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <cstddef>
template <typename T>
class StepIterator {
private:
T* ptr_;
size_t step_;
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
StepIterator(T* ptr, size_t step) : ptr_(ptr), step_(step) {}
reference operator*() const { return *ptr_; }
pointer operator->() const { return ptr_; }
StepIterator& operator++() { ptr_ += step_; return *this; }
StepIterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }
StepIterator& operator--() { ptr_ -= step_; return *this; }
StepIterator operator--(int) { auto tmp = *this; --(*this); return tmp; }
StepIterator& operator+=(difference_type n) { ptr_ += n * step_; return *this; }
StepIterator& operator-=(difference_type n) { ptr_ -= n * step_; return *this; }
reference operator[](difference_type n) const { return *(*this + n); }
friend bool operator==(const StepIterator& a, const StepIterator& b) {
return a.ptr_ == b.ptr_;
}
friend bool operator!=(const StepIterator& a, const StepIterator& b) {
return !(a == b);
}
friend bool operator<(const StepIterator& a, const StepIterator& b) {
return a.ptr_ < b.ptr_;
}
friend difference_type operator-(const StepIterator& a, const StepIterator& b) {
return (a.ptr_ - b.ptr_) / static_cast<difference_type>(a.step_);
}
friend StepIterator operator+(const StepIterator& it, difference_type n) {
auto tmp = it;
tmp += n;
return tmp;
}
friend StepIterator operator-(const StepIterator& it, difference_type n) {
auto tmp = it;
tmp -= n;
return tmp;
}
};
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Every 2nd element starting from index 1
StepIterator<int> start(arr + 1, 2);
StepIterator<int> end(arr + 10, 2);
std::copy(start, end, std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n"; // 2 4 6 8 10
}
Iterator Traits
#include <iostream>
#include <iterator>
#include <vector>
#include <list>
#include <type_traits>
template <typename Iter>
void advance(Iter& it, int n) {
using category = typename std::iterator_traits<Iter>::iterator_category;
if constexpr (std::is_base_of_v<std::random_access_iterator_tag, category>) {
it += n; // O(1)
} else if constexpr (std::is_base_of_v<std::bidirectional_iterator_tag, category>) {
if (n >= 0) while (n--) ++it;
else while (n++) --it;
} else {
while (n--) ++it; // forward only
}
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> lst = {1, 2, 3, 4, 5};
auto vit = vec.begin();
advance(vit, 3); // O(1) for vector
std::cout << "vec[3]: " << *vit << "\n";
auto lit = lst.begin();
advance(lit, 3); // O(n) for list
std::cout << "list[3]: " << *lit << "\n";
}
Common Mistakes
Mistake 1: Invalidating Iterators
std::vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4); // may invalidate it (reallocation)
*it = 5; // undefined behavior
Mistake 2: Dereferencing End Iterator
auto it = v.end();
// *it = 5; // undefined behavior
Mistake 3: Using Invalidated insert_iterator
auto ins = std::inserter(v, v.begin());
v.clear();
*ins = 5; // iterator points to invalid position
Mistake 4: Passing Wrong Iterator Category to Algorithm
std::forward_list<int> fl;
// std::sort(fl.begin(), fl.end()); // Error: needs random access
Mistake 5: Forgetting difference_type for Custom Iterators
Algorithms use difference_type for distance calculations. Always define it.
Mistake 6: Assuming Contiguous Storage
auto it = vec.begin();
int* ptr = &(*it); // OK for vector, UB for deque
Use vec.data() for guaranteed contiguous access.
Practice Questions
- What are the five iterator categories in C++?
- Which iterator category does
std::listprovide? - Write a custom
filter_iteratorthat skips elements not matching a predicate. - What is the difference between
std::begin()andstd::cbegin()? - How do
std::reverse_iteratorandstd::move_iteratortransform iteration?
Challenge
Implement a zip_iterator that iterates over two containers simultaneously, yielding pairs of elements. It should satisfy forward iterator requirements. Test it with std::copy and std::ostream_iterator.
FAQ
Mini Project
Build a repeating_view that cycles through elements indefinitely:
#include <iostream>
#include <ranges>
#include <vector>
#include <algorithm>
template <std::ranges::forward_range R>
class RepeatingView : public std::ranges::view_interface<RepeatingView<R>> {
private:
R base_;
std::ranges::iterator_t<R> current_;
std::ranges::iterator_t<R> begin_;
std::ranges::sentinel_t<R> end_;
public:
struct Sentinel {};
struct Iterator {
private:
R* base_;
std::ranges::iterator_t<R> current_;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = std::ranges::range_value_t<R>;
using difference_type = std::ptrdiff_t;
Iterator(R& base, std::ranges::iterator_t<R> it)
: base_(&base), current_(it) {}
auto operator*() const { return *current_; }
Iterator& operator++() {
++current_;
if (current_ == std::ranges::end(*base_)) {
current_ = std::ranges::begin(*base_);
}
return *this;
}
Iterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }
bool operator==(const Sentinel&) const { return false; }
bool operator!=(const Sentinel&) const { return true; }
};
RepeatingView(R base) : base_(std::move(base)),
current_(std::ranges::begin(base_)),
begin_(std::ranges::begin(base_)),
end_(std::ranges::end(base_)) {}
Iterator begin() { return Iterator(base_, begin_); }
Sentinel end() { return {}; }
};
int main() {
std::vector<int> v = {1, 2, 3};
RepeatingView repeated(v);
auto it = repeated.begin();
for (int i = 0; i < 10; ++i, ++it) {
std::cout << *it << " ";
}
std::cout << "\n"; // 1 2 3 1 2 3 1 2 3 1
}
What's Next
Iterator categories determine algorithm compatibility. The next lesson begins Module 6 on Templates and Metaprogramming, starting with function templates: template syntax, type deduction, and template overloading.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro