ProgrammingC++ Developer

What are the features of working with ranges and iterators in C++: why are they needed, what types exist, and what are the main rules for their correct usage?

Pass interviews with Hintsage AI assistant

Answer.

History of the issue:

Emerging in STL in C++98, iterators allowed abstraction from specific data structures, providing unified access to container elements. With the adoption of C++20, the standard ranges were added, further enhancing expressiveness and safety when working with containers.

Problem:

Incorrect usage of iterators can lead to runtime errors: going out of bounds of the container, invalidation after changes. Support for different types of iterators leads to unexpected behavior if they are confused with each other. Working "manually" with begin()/end() requires discipline.

Solution:

Use the iterator type appropriate for the container's capabilities (e.g., random access only for vector/deque/array). Do not store invalidated iterators. For modern tasks, it is more common to use standardized ranges and algorithms.

Code example:

#include <vector> #include <algorithm> #include <iostream> #include <ranges> int main() { std::vector<int> vec{1, 2, 3, 4, 5}; // Iteration through iterator for (auto it = vec.begin(); it != vec.end(); ++it) { std::cout << *it << " "; } // Iteration through range for (int x : vec | std::ranges::views::filter([](int v){return v % 2 == 0;})) { std::cout << x << " "; } return 0; }

Key features:

  • Several types of iterators: input, output, forward, bidirectional, random-access, contiguous
  • Invalidation of iterators when modifying containers
  • Support for new range and view syntaxes (C++20 and above), improving readability and safety.

Trick questions.

What happens to the std::vector iterator after calling push_back?

If the container size increases after push_back (rebalancing), all old iterators and references become invalid. After push_back without changing capacity, the iterators remain valid. It's safer not to store iterators between modifications.

What is the difference between random-access and bidirectional iterators?

Random-access supports arithmetic (it + n) and access by index (it[n]), while bidirectional supports only ++ and --. Not all STL containers support random-access.

Can standard STL algorithms work with regular pointers?

Yes, because a pointer in C++ fully meets the requirements of a random-access iterator.

Typical mistakes and anti-patterns

  • Using invalidated iterators after modifying the container
  • Mixing iterators from different containers
  • Incorrect choice of algorithm or iterator type

Real-life example

Negative case

In a loop over std::list, the developer directly modifies the container using the erase method without updating the iterator, leading to a runtime error.

Pros:

  • Short code

Cons:

  • Hidden bugs, crashes on large data

Positive case

Before modifying the container, the next iterator is always saved. Standard algorithms are used such as erase-remove idiom for vectors or list::remove_if for lists.

Pros:

  • Predictable and safe behavior

Cons:

  • Slightly more code