Range-Based for Loop
The traditional for loop with an index counter works perfectly well, but when you simply want to visit every element in a container or array, managing the index yourself adds noise without adding meaning. C++11 introduced the range-based for loop— a cleaner syntax that says exactly what you mean: “for each element in this collection, do this.” It works with arrays, vectors, strings, maps, and any type that provides begin() and end() iterators.
Basic syntax
The range-based for loop declares a variable that takes the value of each element in turn. The type can be written explicitly or deduced with auto.
#include <vector>
#include <iostream>
std::vector<int> scores = {85, 92, 78, 95, 61};
// By value — each element is COPIED into 'score':
for (int score : scores)
{
std::cout << score << " ";
}
// Output: 85 92 78 95 61
// With auto:
for (auto score : scores) { ... } // same effectWhen you iterate by value, the loop variable is a copy of each element. Modifying the variable does not affect the container. For large objects, copying every element is wasteful — use a reference instead.
By reference — avoiding copies, enabling modification
Adding & after the type (or after auto) makes the loop variable a referenceto each element rather than a copy. This is both more efficient for large objects and necessary if you want to modify the elements. Adding const makes the reference read-only — the combination const auto& is the most common idiom for read-only iteration because it avoids copies without risking accidental modification.
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// const reference — no copy, cannot modify:
for (const std::string& name : names)
{
std::cout << name << "\n"; // safe and efficient
}
// With auto& for brevity:
for (const auto& name : names) { ... } // same effect
// Mutable reference — modifies the actual vector elements:
for (auto& score : scores)
{
score += 5; // adds 5 to every element in place
}Rule of thumb: always use const auto& unless you need to modify the elements (then use auto&) or the element type is a cheap-to-copy fundamental type like int or double (then by-value is fine).
Works with any range-able container
The range-based for loop works with arrays, vectors, strings (iterates over characters), maps (iterates over key-value pairs), and any other standard container.
#include <array>
#include <map>
#include <string>
// C-style array:
int primes[] = {2, 3, 5, 7, 11};
for (int p : primes) { std::cout << p << " "; }
// std::array:
std::array<double, 3> coords = {1.0, 2.5, 3.7};
for (double x : coords) { ... }
// std::string — iterates over characters:
std::string word = "hello";
for (char c : word) {
std::cout << std::toupper(c); // prints HELLO
}
// std::map — each element is a std::pair<const Key, Value>:
std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}};
for (const auto& [name, age] : ages) // structured bindings (C++17)
{
std::cout << name << " is " << age << "\n";
}The [name, age] syntax in the map example is a structured binding (C++17): it unpacks a pair or tuple into individually named variables. Without it, you would write entry.first and entry.second.
Range-for vs index-based for — when to use each
The range-based for is simpler and less error-prone when you just need to visit elements. Reach for the index-based form when you need the index itself, when you need to iterate over two collections simultaneously, or when you need to skip elements by modifying the counter.
Prefer range-based for when…
- →You just need to read or modify each element — no index needed
- →The container is not a plain array (vectors, maps, custom types)
- →You want the code to express intent clearly: 'for each item in list'
Use index-based for when…
- →You need to know the position of each element (e.g., to print 'element 3 of 5')
- →You need to iterate over two arrays in parallel using the same index
- →You need to skip elements or iterate in non-unit steps
Key rules to remember
Use const auto& for read-only iteration — no copy, no accidental modification
const auto& is safe for any element type and avoids potentially expensive copies of strings, structs, and other large objects.
Use auto& (non-const reference) to modify elements in place
for (auto& x : v) { x *= 2; } doubles every element. Without &, you are modifying a local copy and the container is unchanged.
Do not modify the container being ranged over
Adding or removing elements from a vector while iterating over it with range-for causes undefined behavior. Use an index-based loop or build a separate list of changes.
Range-based for works on any type with begin() and end()
All standard containers support it. You can also add begin/end to your own types to make them work with range-for.