Skip to content

Modern C++ • Range-based “for” loop

This is one of the features that has always surprised me it was not supported in earlier standards. Even though it’s a very simple construct, it is immensely useful and far more intuitive than the old way of sequentially iterating over ranges. The term “range” here is used to indicate any sequence of values that can be traversed in order from a begin element to an end element (excluded), such as containers, strings, initializer lists and plain old arrays.

In the old days, to iterate through a sequence of elements there were basically 3 options:

1) use a standard for loop
2) use a third party implementation of the “foreach” idiom (e.g. BOOST_FOREACH)
3) use the std::for_each() function

as illustrated in the following code

// Old C++
 
int an_array[] = {1,2,3,4,5};
std::vector<int> a_vector(5, 10);
 
// Range-loop using a standard for
 
for (std::vector<int>::iterator it = a_vector.begin(); it != a_vector.end(); ++it) {
    // Get the element with (*it)
}
     
for (size_t i = 0; i < sizeof(an_array)/sizeof(int); i++) {
    // Get the element with array[i]
}
 
// Range-loop using std::for_each
 
struct P
{
    P() { }
    void operator()(int n) { /* Do something with n */ }
} process;
 
std::for_each(an_array, an_array + sizeof(an_array) / sizeof(int), process);
std::for_each(a_vector.begin(), a_vector.end(), process);
 
// Range-loop using BOOST_FOREACH
 
BOOST_FOREACH(int e, a_vector) {
    // Do something with e
}
 
BOOST_FOREACH(int e, an_array) {
    // Do something with e
}

The Boost option was the cleanest and most efficient method, but you still had to import a third party package in order to just use this functionality. The new range-based for statement introduced with C++11 has the following syntax

for (<declaration> : <expression>) <body>
 
<declaration>  =>  <type> var | auto var
<expression>   =>  array | container | initializer-list
<body>         =>  body of the loop

where container is any object c that defines a c.begin() and c.end() member method or free-standing functions begin(c) and end(c) (e.g. an STL container or any STL-like user-defined type). So, instead of the above code we now have the following

// Modern C++
 
int an_array[] = {1,2,3,4,5};
std::vector<int> a_vector = {6,7,8,9,10};
 
for(int e : an_array) {
    // Do something with the element
}
 
for(int e : a_vector) {
    // Do something with the element
}
 
// or using type inference, like so
 
for(auto e : an_array) {
    // Do something with the element
}
 
for(auto e : a_vector) {
    // Do something with the element
}

with clear advantages in terms of readability as it removes verbose boilerplate, but also because it avoids resorting to third parties implementations and eliminates any dependencies from iterators and predicates to access the elements by automatically dereferencing and assigning the value in a variable of appropriate type (either explicitly specified or inferred with auto). Following are some more examples

// Range-based for loop works with any STL container
std::map<int, std::string> fruits {
    { 1, "Orange"},
    { 2, "Apple" },
};
 
// Get the element by value
for (auto e : fruits) {
    std::cout << e.second << "\n";
}
 
// Get the element by reference
for (auto &e : fruits) {
    e.second = "Mango";
    std::cout << "Map: " << fruits[e.first] << "\n";
}
 
// Get the element by const-reference
for (const auto &e : fruits) {
    std::cout << e.second << "\n";
}
 
// Get the element by forward-reference (T&&)
for (auto &&e : fruits) {
    e.second = "Banana";
    std::cout << "Map: " << fruits[e.first] << "\n";
}
 
// Initializer lists are also a valid range
for (auto e : { 1,2,3,4,5 }) {
    std::cout << e << "\n";
}

Under the hood, the range-based for loop is transformed into code equivalent to the following

{
 
    auto && __range = (<expression>);
    for (auto __begin = <begin-expr>, __end = <end-expr>; __begin != __end; ++__begin)
    {
        <declaration> = *__begin;
        <body>
    }
 
}

where <begin-expr> and <end-expr> are evaluated to the start and beginning of the sequence respectively, which can result in an iterator (for containers) or a pointer (for arrays and initializer lists).

Happy range-looping.

Published inModern C++