Improving Stability with Modern C++, Part 2 — Range-Based For-Loops

Ralph Kootker
FactSet
Published in
3 min readOct 25, 2021

Last time we began FactSet’s journey into modern C++ with the new keywords nullptr and override. This week we'll explore a new language feature introduced in C++11, the range-based for-loop. It's the C++ equivalent of a for...in or foreach loop you may find in other languages. Using this new style of loop can improve readability, correctness, and in some cases code locality.

Overview

Consider the following code:

std::vector<double> prices = {409.60, 410.86, 405.15, 401.13, 401.66};
for (int i = 0; i <= prices.size(); ++i) {
std::cout << prices[i] << '\n';
}

We have a small vector of numbers and print each element one line at a time. Simple, no? But there are four (!) problems with this code in as many lines:

  • Typo in the loop condition, should have been i < prices.size(). (undefined behavior)
  • Loop counter is the wrong type. The size of the vector is std::vector<double>::size_type, not int. (type safety)
  • Each element is mutable even though we’re just reading from it. (const safety)
  • No use for i other than counting loop indexes. (verbosity)

With the new range-based for-loop syntax, we can solve all of these problems.

std::vector<double> prices = {409.60, 410.86, 405.15, 401.13, 401.66};
for (const double p : prices) {
std::cout << p << '\n';
}

That’s much easier to read. You can imagine this being implemented by an iterator under the hood. It will work for any type callable by std::begin() and std::end(), which includes all containers in the standard library. The loop counter is replaced by a copy of each element of the container, so we can't accidentally modify the original value. Use a reference if you do want to modify the elements, or reference-to-const for reading from non-primitive types. Also, consider using auto when declaring your loop variable to further reduce verbosity and avoid type mismatches.

Can this improve other parts of my code?

A range-based for-loop can replace most uses of std::for_each or in-place std::transform. The resulting code is less wordy, and keeps the important parts inline with the surrounding function. For example, this C++98 code using a functor...

struct PercentOfPar
{
void operator()(double& elem) {
elem /= 100.0;
}
};
std::vector<double> prices = {125.297, 125.217, 124.909, 124.797, 124.565};// The reader has to find the functor's definition to figure out what this does.
std::for_each(prices.begin(), prices.end(), PercentOfPar());

…can be replaced with an equivalent loop:

std::vector<double> prices = {125.297, 125.217, 124.909, 124.797, 124.565};
for (double& p : prices) {
p /= 100.0;
}

What if I need to iterate in reverse?

If the Ranges library from C++20 isn’t available, you’ll have to write an old-style loop using reverse_iterator or by indexing backwards. But if you can depend on Boost 1.43 or newer, there's an adaptor for that.

#include <boost/range/adaptors.hpp>std::vector<int> collection = {1, 2, 3, 4, 5};
for (const int elem : boost::adaptors::reverse(collection)) {
std::cout << elem << ' ';
}
// Outputs 5 4 3 2 1

Does C++ have an equivalent to enumerate in Python?

Python has a function that pairs a loop counter with each element:

for i, elem in enumerate(collection):
# ...

Sadly, C++ does not have such a feature, but you can still write for-loops using the old syntax if you need access to the loop index.

Where can I learn more?

The C++ Core Guidelines recommend using range-based for-loops whenever possible. They’re safe to use as a drop-in replacement for most old-style for-loops.

Note for clang users: This is another change clang-tidy can make for you automatically.

What’s next?

This post mentioned an old C keyword that was repurposed in C++11, auto. Next time we'll cover auto in more detail.

As always, please comment below if you have any questions or suggestions for what topics we should cover next.

Acknowledgments

Special thanks to all that contributed to this blog post:

Author: Michael Kristofik
Managing Editor: Ralph Kootker
Reviewers: Jim Arena, Jennifer Ma, Jens Maurer, Tim Severeijns, Matt Topol, and Sam Wenninger.

--

--

Ralph Kootker
FactSet
Writer for

I publish on behalf of others or myself. Please carefully look at the acknowledgements at the bottom of each article