Range-Based For Loops — C++
Syntax
for (declaration : range_expression) { statement }
// Example:
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto x : v) { std::cout << x << "\n"; }
How It Works — Lowering to Iterators
The compiler transforms for (auto x : range) into:
{
auto&& __range = range;
auto __begin = begin(__range);
auto __end = end(__range);
for (; __begin != __end; ++__begin) {
auto x = *__begin;
// body
}
}
Works With
- C-style arrays:
int arr[] = {1,2,3}; - STL containers:
std::vector,std::list,std::map, etc. std::initializer_list- Custom types: must provide
begin()andend()(member or ADL free functions)
// Custom type example
struct Range {
int* begin() { return data; }
int* end() { return data + size; }
int data[4] = {10, 20, 30, 40};
int size = 4;
};
Range r;
for (auto v : r) { /* ... */ }
Value vs Reference vs Const Reference
std::vector<std::string> names = {"Alice", "Bob", "Carol"};
for (auto name : names) // COPY — safe but expensive
for (auto& name : names) // REFERENCE — modify allowed, no copy
for (const auto& name : names) // CONST REF — safe, no copy (preferred for read)
[!TIP] Prefer
const auto&for read-only iteration over containers holding non-trivial types - it avoids copies entirely.
Structured Bindings with Range-For (C++17)
std::map<std::string, int> grades = {{"Alice", 95}, {"Bob", 88}};
Modifying Elements
Must use auto&:
std::vector<int> v = {1, 2, 3, 4};
for (auto& x : v) { x *= 2; } // v is now {2, 4, 6, 8}
Index Tracking Workaround
Range-for has no built-in index. Options:
// Manual counter
int i = 0;
for (const auto& x : v) { std::cout << i << ": " << x << "\n"; ++i; }
// C++23 enumerate (ranges::views::enumerate)
// for (auto [i, x] : std::views::enumerate(v)) { ... }
[!NOTE] C++23 introduces
std::views::enumeratewhich provides index-value pairs directly in a range-for loop.
Range-for Expansion
flowchart TD A[“for (auto x : range)”] –> B[“auto range = range_expr”] B –> C[“auto begin = begin(range)”] C –> D[“auto end = end(range)”] D –> E{“begin != end?”} E –>|”Yes”| F[“auto x = *begin”] F –> G[“Execute body”] G –> H[”++begin”] H –> E E –>|”No”| I[“Loop ends”]
Which Loop Form to Choose
flowchart TD A[“Range-for loop”] –> B{“Need to modify elements?”} B –>|”Yes”| C[“for (auto& x : range)”] B –>|”No”| D{“Elements expensive to copy?”} D –>|”Yes”| E[“for (const auto& x : range)”] D –>|”No”| F[“for (auto x : range)”]
Summary Table
| Syntax | Use case | Modifies? | Notes |
|---|---|---|---|
for (auto x : v) | Small/cheap types, read only | No | Makes a copy each iteration |
for (auto& x : v) | Any type, modify elements | Yes | Direct reference to element |
for (const auto& x : v) | Any type, read only | No | Most efficient for read-only |
for (auto& [k,v] : map) | Key-value containers | Yes | C++17 structured binding |
for (const auto& [k,v] : map) | Key-value containers, read | No | C++17 structured binding |