Iterating through a list in C++ means visiting each element of a sequence to read or modify it. This guide explains core techniques for std::list and other sequences, including range-based for loops, index-based loops, iterators, and standard algorithms. You will learn when each approach is appropriate, how to avoid common pitfalls, and best practices for writing clear, efficient, and safe traversal code in modern C++.
Range-Based For Loop
The range-based for loop (C++11) is the simplest way to iterate over all elements of a list without manually managing indices or iterators. It works with containers that expose begin() and end(). Use const references when you do not need to modify elements to avoid unnecessary copies.
Syntax and Usage
Use auto&& or const T& to prevent copying and preserve correctness. For non-const modification, drop const and use T&. Prefer structured bindings when working with pairs or tuples inside the list.
Performance and Safety Notes
Range-based for on std::list compiles to iterator-based code similar to a manual loop. It does not invalidate iterators unless you erase elements; if you erase, update the iterator using the return value of list::erase.
Index-Based For Loop
Index-based access with operator[] at() is not recommended for std::list because lists do not provide contiguous storage. Using indexes results in O(n) random access cost per element, degrading performance to O(n^2).
When Indexing Is Acceptable
Use indexes only with contiguous containers such as std::vector or std::array. For std::list, prefer iterator-based traversal to maintain linear complexity.
Example Comparison
| Container | Recommended Style | Complexity |
|---|---|---|
| std::list | Range-based for or iterator | O(n) |
| std::vector | Range-based for or index | O(n) |
| std::array | Range-based for or index | O(n) |
Manual Iterator Loop
Explicit iterators give fine-grained control and are necessary when you need to erase elements safely. list.erase returns the next valid iterator, enabling safe removal during traversal.
Begin/End Idioms
Use cbegin/cend for const traversal. Dereference with *it to access elements. Prefer ++it over it++ to avoid unnecessary copies for non-primitive iterator types.
Erasing Elements
Calling erase invalidates only the removed iterator; capture the return value to continue traversal safely. Do not use indexes or operator[] with list iterators.
Standard Algorithms
Algorithms from <algorithm> provide expressive and reusable traversal patterns. Use std::for_each with a lambda, or range-based execution where applicable.
for_each and Lambdas
std::for_each applies a function object to each element. Capture externally by reference with [&] to modify elements; prefer range-based for for clarity unless algorithm composition is needed.
Algorithm Selection Guide
- for_each: apply an action to each element
- transform: produce a transformed sequence
- copy: copy elements to another range
- remove_if: erase-remove idiom for conditional deletion
Best Practices and Pitfalls
Choose iteration style based on container, readability, and modification requirements. Avoid writing manual index loops for std::list, reserve end/cbegin when appropriate, and prefer range-based for for straightforward traversal.
Checklist for Safe Traversal
- Use range-based for by default
- Use iterators when erasing during traversal
- Prefer const references to avoid copies
- Do not rely on operator[] for lists
- Use .erase(it++) or erase returned iterator safely