How Range-based for loop works in C++

In this article, we are going to learn about how Range-based for loop works in C++. Range-based for loop in C++ were introduced with C++ 11. There are some enhancements done to the range-based for loops in C++20.So in this article, we will be covering the C++11, C++17, and C++20 features of range-based for loops in C++. Lets us begin with C++11 first.

What are Range based for loops in C++


Range-based for loops is very similar to the for loops we know in C++ and many other languages. It executes a loop over a range. It is an improvement in the implementation and calling convention.

Syntax

for ( range_declaration : range_expression ) loop_statement

Syntax in C++ 20

for ( init-statement(optional)range_declaration : range_expression ) loop_statement

Parameters :

  • init-statement:
    • Either An expression statement (which may be a null statement “;”.
    • A simple declaration, typically a declaration of a variable with an initializer, but it may declare arbitrary many variables or be a structured binding declaration It must end with a semicolon;
  • range_declaration: A declaration of a named variable, whose type is the type of the element of the sequence represented by range_expression, or a reference to that type. Often uses the auto specifier for automatic type deduction.
  • range_expression: Any expression that represents a suitable sequence or a braced-init-list.
  • loop_statement :Any statement, typically a compound statement, which is the body of the loop.

Let us understand more with the help of the examples below:

C++ Program range-based for loop

#include <iostream>
#include <vector>
int main() {

// We are using a vector of integer values.

std::vector v = {0, 1, 2, 3, 4, 5};

// access by const reference
// Here const int &i is the range_declaration and v is the range_expression on which we are going to iterate.

for (const int& i : v)
std::cout << i << ' ';
std::cout << '\n';

// Here we are using auto which helps us to make the code short and serve same purpose. i will be serving as iterator on vector v.

for (auto i : v)
std::cout << i << ' ';
std::cout << '\n';

// Here we are providing the initializer list as the range to iterate on.

for (int n : {0, 1, 2, 3, 4, 5})
std::cout << n << ' ';
std::cout << '\n';

// Here we are providing the initializer list in the form of array as the range to iterate on.

int a[] = {0, 1, 2, 3, 4, 5};
for (int n : a)
std::cout << n << ' ';
std::cout << '\n';

// Iterating over a map of key and value

std::map MAP({{5, 5}, {4, 4}, {0, 0}});
for (auto var : MAP)
std::cout << '{' << var.first << ", "
<< var.second << "}\n";
}

1. Range Based for loop in C++ 17


This is supported in C++ 17. This is making use of structured binding.

for (auto&& [first,second] : MAP) 
    std::cout << '{' << first << ", " 
              << second << "}\n";


2. Range based in C++ 20


If we see the latest example with C++ 20 syntax which includes the init-statement in the for a loop.

#include <iostream>
#include <vector>
int main() {
// We are using a vector of integer values.

std::vector v = {0, 1, 2, 3, 4, 5};
for (auto n = v.size(); auto i : v) 
    std::cout << --n + i << ' ';
std::cout << '\n';
}

3. Range based for loop Using Temporary Range Expression


When you are using a range-based for loop then you need to be careful with your range expressions. If you are using a range_expression that returns a temporary value, its lifetime is extended until the end of the loop, but beware that the lifetime of any temporary within range_expression is not extended. If this kind of use is done then it may result in an application crash or undefined behavior.Let us understand this with the below example:

C++ Program Range based for loop Using Temporary Range Expression



Here we are using func().items() as range_expression to iterate on. let us say this function is returning a value. This is a risky operation as this value is temporary on the stack. If this function is returning a value by reference then it is safe to use.In a range-based for loop, the range_expression is evaluated to determine the sequence or range to iterate. Each element of the sequence, in turn,is dereferenced and assigned to the variable with the type and name given in range_declaration.

for (auto& x : func().items())
{
/* .. */
}

With the introduction of C++ 20, you should use the new syntax to avoid this problem.
In C++20 same can be done like the below example.

for (T thing = foo(); auto& x : thing.items())
{
/* … */ // OK
}