DEV Community

Cover image for Range-based `for` loops
Alberto Pérez de Rada Fiol
Alberto Pérez de Rada Fiol

Posted on

Range-based `for` loops

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 ints, 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;
}
Enter fullscreen mode Exit fullscreen mode

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 ints for both its keys and values. 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;
}
Enter fullscreen mode Exit fullscreen mode

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 values 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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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! 👇

Discussion (1)

Collapse
pauljlucas profile image
Paul J. Lucas

for (int i = 0; i < (int)vector.size(); i++) {

In old code, you should have used size_t i, then there's no need for the cast. Avoid casting if at all possible.

for (auto [key, value] : map) {

It depends on the types of key and value because those are copied by value. For large-ish types, you probably want to do:

for (auto const &[key, value] : map ) {
Enter fullscreen mode Exit fullscreen mode