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:

  1. The underlying iterator advances to the next element.
  2. 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.