C++: Range-Based For Loops

One of the more frequently used C++ constructs I use is the range-based for loop. The range-based forloop format was introduced in C++11, so you'll need to enable that language set (or newer) to reap the benefits. This feature simplifies the verbosity of for loop constructs and is just plain nice to use.

The range-based for loop follows this general format:

for( declaration : expression)
{
    //do some loop stuff
}

Here's an simple example which prints all the elements in a std::vector:

std::vector<int> v1 = {-1, 3, 5, -8, 0};

std::cout << std::endl << "v1: " << std::endl;
for (const auto & t : v1)
{
    std::cout << t << " ";
}
std::cout << std::endl;

The range expression typically corresponds to an array or a container object (requiring iterator support). You are also free to use initializer lists as an expression.

The declaration portion of the for loop involves declaring a named variable. The type of the variable needs to correspond to the type of the element in the expression (e.g. an int for an int array, an iterator for a container class). Remember the auto keyword? It's very useful here to eliminate any thinking that may be required.

During each loop run, the variable you have declared is updated to reflect the next element in the sequence. This variable has to be declared, but it does not have to actually be used in the loop statement.

Note: the break and continue keywords still work as expected.

Types of Accesses

You can use a variety of declaration statements for accessing variables:

  • Accessing by value (auto t)
    • Makes a copy into a temporary object
  • Accessing by reference (auto &t)
    • Use this when you want to make changes to an element in the expression
  • Accessing by const reference (const auto &t)
    • Use if you want to prevent a copy of the object and don't need to modify anything
    • Especially useful if there is a copying penalty (e.g. large buffer to allocate, non-trivial construction).
    • This is my default access format

Use the auto keyword!

Most examples you see involving range-based for loops will use the auto keyword. This is a matter of convenience and stops you from having to manually write out the full type name for your loop elements. From a simplicity and readability standpoint, it's widely recommended that you use the auto keyword.

For instance, instead of:

for(std::map<int, set<std::string> >::iterator t : map_thing) { … }

You would say:

for(auto t : map_thing) { … }

Limitations

There are two minor limitations to note when using this for loop format:

  1. There is no index variable. If your loop operation requires an index, you are likely better off using the standard for loop.
  2. The loop iterates in a forward direction. If you want to iterate backward, you would need to use the standard for loop format with rbegin() and rend() iterators.

Example Loops

I think the cppreference example is very clear and demonstrates the various types of accesses you can use:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {0, 1, 2, 3, 4, 5};

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

    for (auto i : v) // access by value, the type of i is int
        std::cout << i << ' ';
    std::cout << '\n';

    for (auto&& i : v) // access by reference, the type of i is int&
        std::cout << i << ' ';
    std::cout << '\n';

    for (int n : {0, 1, 2, 3, 4, 5}) // the initializer may be a braced-init-list
        std::cout << n << ' ';
    std::cout << '\n';

    int a[] = {0, 1, 2, 3, 4, 5};
    for (int n : a) // the initializer may be an array
        std::cout << n << ' ';
    std::cout << '\n';

    for (int n : a)  
        std::cout << 1 << ' '; // the loop variable need not be used
    std::cout << '\n';

}

Further Reading