Purpose of std::counted_iterator in C++
Introduced in C++20, std::counted_iterator is an
iterator adaptor designed to track traversal distance over a sequence of
elements when only a starting iterator and a desired element count are
available. By bundling an underlying iterator with an internal counter,
it allows developers to safely iterate through a specific number of
steps without needing an explicit end iterator or sentinel from the
underlying container.
The Problem with Undefined End Iterators
In standard C++ algorithms, iteration traditionally depends on a
half-open range defined by a pair of iterators:
[begin, end). However, many scenarios present a known
starting position and a fixed number of elements (\(N\)), but no natural or easily accessible
end iterator. Common examples include:
- Reading a fixed chunk of data from an unbounded stream or generator.
- Processing a sub-range from a legacy C-style API (a pointer and a length).
- Stepping through a linked list or node-based structure for a set number of nodes without traversing to the very end of the list.
Constructing an artificial end iterator in these cases
can be inefficient, requiring an extra \(O(N)\) traversal just to find the end
position, or impossible if the sequence is infinite or dynamically
evaluated.
How
std::counted_iterator Solves This
std::counted_iterator wraps an underlying iterator along
with an integer count. As the iterator is incremented, two things occur
simultaneously:
- The underlying iterator advances to the next element.
- The internal count is decremented toward zero.
Because the iterator tracks its own remaining steps, it can be
compared directly against std::default_sentinel (or
std::default_sentinel_t). When the internal counter reaches
zero, equality comparison with std::default_sentinel
evaluates to true, signaling that the end of the logical
range has been reached.
Key Use Cases and Benefits
1. Integration with C++20 Ranges and Algorithms
std::counted_iterator pairs with
std::default_sentinel to form a valid C++20 range. This
allows range-based algorithms to operate on count-bounded sequences
directly:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50, 60};
// Process only the first 3 elements without slicing the vector
auto start = std::counted_iterator(numbers.begin(), 3);
auto end = std::default_sentinel;
std::for_each(start, end, [](int n) {
std::cout << n << " "; // Outputs: 10 20 30
});
}2. Avoiding Manual Loop Counters
Without std::counted_iterator, tracking distance
requires maintaining a separate loop counter variable alongside the
iterator. This introduces boilerplate code and increases the risk of
off-by-one errors. std::counted_iterator encapsulates this
state into a single iterator object.
3. Inspecting Traversal Progress
std::counted_iterator provides utility member functions
to inspect and extract its current state:
.count()returns the number of remaining elements left to traverse..base()returns the underlying iterator at its current position.
This allows an algorithm to consume up to \(N\) elements and subsequently pass the advanced underlying iterator to another function to continue processing the remaining data.