Wiki
Range-based for loop
1.What problem does this section solve?
Traditional for loop traversal of containers requires writing three elements (initialization, condition, increment), which is error-prone: out-of-bounds indexing, using the wrong boundary condition, type mismatch, and so on.
Range-based for loops let you write "do ... for each element in the container" code without worrying about index and iterator details, making the code more concise and safer.
2.What is this feature?
The range-based for loop (Range-based for loop) is syntactic sugar introduced in C++11, allowing you to directly iterate over each element in a container (or any type that has begin() / end()), with the compiler automatically generating the corresponding iterator code.
3.C++ Standard Edition
C++11 (basic), C++20 adds support for initialization statements.
4.Required header files
No extra header file is needed. Range-for is a language feature. But the container to be traversed needs to include the corresponding header file (such as <vector>).
5.From Traditional for to Range for
Before learning about range for, let's first review how traditional for iterates over STL containers. Suppose there is a vector:
std::vector<int> v = {10, 20, 30, 40, 50};
You can iterate using subscripts:
for (std::size_t i = 0; i < v.size(); ++i)
{
std::cout << v[i] << " ";
}
You can also traverse using an iterator:
for (auto it = v.begin(); it != v.end(); ++it)
{
std::cout << *it << " ";
}
If you only want to visit each element in order, you can rewrite it as a range for:
for (int n : v)
{
std::cout << n << " ";
}
The subscript syntax is intuitive, but it only applies to containers that support subscript access, such as vector and array; the iterator syntax can be used with more STL containers, but the code is longer. The range for loop hides details such as begin() and end() and iterator increment, and more directly expresses “process each element in the container in sequence.”
6.Basic syntax
for (元素类型 变量名 : 容器)
{
// 使用变量名
}
// 推荐:不修改元素用 const auto&
for (const auto& 变量名 : 容器)
{
// 只读访问
}
// 要修改元素用 auto&
for (auto& 变量名 : 容器)
{
// 可以修改元素
}
7.Common Usage
| Usage | Description | When to use |
|---|---|---|
for (auto x : c) |
Value copy traversal | Element type is small (int/double etc.) |
for (const auto& x : c) |
Read-only reference traversal | Most commonly useddo not want to copy large objects |
for (auto& x : c) |
Modifiable reference traversal | Need to modify elements in the container |
for (int x : {1, 2, 3}) |
Traverse initializer list | Quickly traverse a known small collection |
8.When not to use range for
Range for is suitable for "accessing elements one by one." If your logic depends on indices, adjacent elements, reverse traversal, skipping traversal, or if you need to add or remove container elements during traversal, a traditional for or iterator loop is actually clearer.
| Requirement | Recommended form | Reason |
|---|---|---|
| Read-only access to each element | Range for | Most concise |
| Modify the value of each element | for (auto& x : c) |
Directly modify the original element |
| Need the index | Traditional for (size_t i = 0; ...) |
The index is part of the logic |
| Compare adjacent elements | Traditional for | Need to access i and i - 1 |
| Delete elements during traversal | Iterator loop / algorithm | Range for easily triggers iterator invalidation |
9.Example code
9.1.Example 1: Traversing a vector with a range for
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v = {10, 20, 30, 40, 50};
// 范围 for 遍历
std::cout << "elements: ";
for (int n : v)
{
std::cout << n << " ";
}
std::cout << "\n";
return 0;
}
Running result:
elements: 10 20 30 40 50
9.2.Example 2: Based on Example 1, the difference between const auto& and auto&
#include <iostream>
#include <vector>
#include <string>
int main()
{
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// const auto&:只读遍历,不拷贝(推荐)
std::cout << "const auto&: ";
for (const auto& s : names)
{
std::cout << s << " ";
// s = "X"; // ❌ 编译错误!const 引用不能修改
}
std::cout << "\n";
// auto&:可修改遍历
for (auto& s : names)
{
s = s + "!"; // 修改原容器中的元素
}
std::cout << "after modify: ";
for (const auto& s : names)
{
std::cout << s << " ";
}
std::cout << "\n";
return 0;
}
Running result:
const auto&: Alice Bob Charlie
after modify: Alice! Bob! Charlie!
9.3.Example 3: Based on Example 2, iterate over the initial value list and the map
#include <iostream>
#include <map>
#include <string>
int main()
{
// 直接遍历初始值列表
std::cout << "init list: ";
for (int n : {3, 1, 4, 1, 5, 9})
{
std::cout << n << " ";
}
std::cout << "\n";
// 遍历 map
std::map<std::string, int> scores = {
{"Alice", 85},
{"Bob", 92},
{"Charlie", 78}
};
std::cout << "scores:\n";
for (const auto& pair : scores)
{
std::cout << " " << pair.first << ": " << pair.second << "\n";
}
return 0;
}
Running result:
init list: 3 1 4 1 5 9
scores:
Alice: 85
Bob: 92
Charlie: 78
9.4.Example 4: Based on Example 3, C++17 Traverse a map with structured bindings
#include <iostream>
#include <map>
#include <string>
int main()
{
std::map<std::string, int> scores = {
{"Alice", 85},
{"Bob", 92},
{"Charlie", 78}
};
// C++17 结构化绑定:直接解包 key 和 value
std::cout << "scores (structured binding):\n";
for (const auto& [name, score] : scores)
{
std::cout << " " << name << ": " << score << "\n";
}
return 0;
}
Running result:
scores (structured binding):
Alice: 85
Bob: 92
Charlie: 78
9.5.Example 5: Building on Example 4, do not force the use of a range-based for loop when subscripts are needed
#include <cstddef>
#include <iostream>
#include <string>
#include <vector>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// vector 是动态数组,元素数量可以在运行时变化。
std::vector<int> temperatures = {22, 23, 21, 25, 24};
std::cout << "all temperatures: ";
for (int t : temperatures)
{
std::cout << t << " ";
}
std::cout << "\n";
std::cout << "changes between adjacent days:\n";
for (std::size_t i = 1; i < temperatures.size(); ++i)
{
int change = temperatures[i] - temperatures[i - 1];
std::cout << " day " << i << " -> day " << (i + 1)
<< ": " << change << "\n";
}
return 0;
}
Running result:
all temperatures: 22 23 21 25 24
changes between adjacent days:
day 1 -> day 2: 1
day 2 -> day 3: -2
day 3 -> day 4: 4
day 4 -> day 5: -1
10.Operation result
See the "run results" for each example above.
11.Key syntax explanation in the example
| Example | What was explained | Newly appearing syntax | Why it is written this way | Notes |
|---|---|---|---|---|
| Example 1 | The most basic range for | for (int n : v) |
Directly read the intent: "for each element n in v" | This form copies each int, but int is very small so it does not matter |
| Example 2 | const auto& vs auto& | const auto&、auto& |
const auto& avoid copying;auto& can modify elements |
When traversing large objects such as string, be sure to use a reference |
| Example 3 | Traversing an initializer list and a map | Traverse {...} and map |
An initializer list can be placed directly in the for | What map traversal yields is std::pair |
| Example 4 | Structured bindings to unpack a map | for (const auto& [k, v] : m) |
C++17 syntax, key/value naming them separately is clearer | If you do not use C++17, you can use the approach in Example 3 |
| Example 5 | How to write it when an index is needed | std::size_t i、traditional for |
Comparing adjacent elements is inseparable from indices | Do not sacrifice clarity just to use new syntax |
12.Common Errors
Error 1: Adding or removing elements during traversal
for (auto x : v)
{
v.push_back(x * 2); // ❌ 可能导致迭代器失效、无限循环!
}
Correct approach: first collect into a new container, and after the loop ends perform the operation uniformly, or use a traditional iterator loop.
Error 2: auto without reference, modification has no effect
for (auto s : names)
{
s = "X"; // 修改的是拷贝!
}
// names 没有变化!
Correct approach: to modify the container element, use for (auto& s : names).
Error 3: Accessing Another Container by Index During Iteration
for (int n : v1)
{
v2[i++] = n; // i 从哪来?
}
Correct approach: when an index is needed, a traditional for loop may be more appropriate, or use an enumeration trick.
13.Usage Recommendations
- Do not modify elements: use
const auto&— this is the default choice. - Modify elements: use
auto&— makes the intent to modify explicit. - Fundamental types (int/double) and no modification: you can use
auto— the copy overhead is negligible. - When indices are needed, honestly use a traditional for — the range for is not suitable for all scenarios.
- When iterating over a map, prefer
[key, value]structured bindings (since C++17).
14.Summary
- The syntax for range-based for loop traversal of a container is
for (const auto& x : container). const auto&read-only reference (default choice),auto&modifiable reference,autovalue copy.- Applicable to any type that has
begin()/end()(STL containers, initializer lists, etc.). - During traversal, container elements cannot be added or removed, as this will cause iterator invalidation.
14.1.Engineering Expansion
In ROS2, range-for is frequently used in scenarios such as iterating over sensor data lists and iterating over node lists. In Boost.Asio, range-for is also commonly used to iterate over connection lists. Once you have mastered const auto& this idiom, you can write code in a style consistent with excellent open-source projects.