Today we are going to learn about another very useful feature of Modern C++: Range-based for
loops. As their name indicates, these kind of loops are useful when we want to iterate over the elements of a container, from beginning to end.
Here are some of the advantages of using range-based for
loops:
- Improve the readability of the code
- Will be as optimized as possible
- The awesome
auto
keyword can be used in the range declaration - There's no need to know the size of the container we want to iterate over
And, of course, all statements that work within a regular for
loop (continue
, break
, etc.) also work within range-based for
loops.
But enough talking, let's see some examples! Imagine we have a vector
of int
s, and we want to print all of its elements to stdout
, how can we do it?
// Older standards
// Option 1
for (int i = 0; i < (int)vector.size(); i++) {
std::cout << vector[i] << std::endl;
}
// Option 2
for (std::vector<int>::iterator it = vector.begin(); it != vector.end(); it++) {
std::cout << *it << std::endl;
}
// Modern C++
for (auto element : vector) {
std::cout << element << std::endl;
}
That's very clean and concise! 🧐 And it gets even better when we use features from standards older than C++11. For instance, let's now imagine we have a map
, with int
s for both its key
s and value
s. How can we loop through it, displaying its contents?:
// Older standards
for (int i = 0; i < (int)map.size(); i++) {
std::pair<int, int> pair = map[i];
std::cout << pair.first << ": " << pair.second << std::endl;
}
// Modern C++
for (auto [key, value] : map) {
std::cout << key << ": " << value << std::endl;
}
What was that? 😮 From C++17, we can use Structured Bindings to unpack the contents of a pair
(or a tuple
, in general) in such a compact way! But what if, for instance, we only cared about the value
s of the map? From C++20 we can use the Ranges Library to do:
for (auto value : map | std::views::values) {
std::cout << value << std::endl;
}
Simple and clean -- and there's more! Also from C++20, we can use the Init Statement if we need to. Let's add a very simple init statment to the last example so we can track the index of the current value
, as I know you are already wondering what to do when the index is needed:
for (auto i = 0; auto value : map | std::views::values) {
std::cout << "Value " << i++ << ": " << value << std::endl;
}
Do you need more examples, or are you already convinced that range-based for
loops are such a great addition to C++? Tell me in the comments! 👇
Top comments (1)
In old code, you should have used
size_t i
, then there's no need for the cast. Avoid casting if at all possible.It depends on the types of
key
andvalue
because those are copied by value. For large-ish types, you probably want to do: