Jonathan Boccara's blog

Is std::for_each obsolete?

Published March 30, 2018 - 13 Comments

C++ for_each

Daily C++

I’m often asked the question: with C++11 introducing range-based for loops, is std::for_each now useless?

And the short answer is: No.

Let’s give a quick recap on for_each and range-based for loops, and then a heuristic for choosing between the two.

for_each and range-for loops

for_each

std::for_each is an STL algorithm that takes a collection of elements (in the form of a begin and end iterator) and a function (or function object), and applies the function on each element of the collection. It has been there since C++98.

To say this codewise, the following piece of code:

std::vector<int> numbers = {1, 2, 3, 4, 5};
std::for_each(begin(numbers), end(numbers), f);

applies the function f on each element of numbers.

range-based for loops

Range-based for loops are a native language construct present in many languages, and were added to C++11:

std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto number : numbers)
{
    // do something with number
}

Note that in this example I used auto which makes a copy of each element, but I could also have used auto& to take a non-const reference or auto const& for a constant reference. Or directly use int instead of auto, though auto seems more convenient to me here.

Similar but different

Sometimes std::for_each is seen as a complicated way to express the same thing as range-based for loops. Consider the following piece of code, using a lambda:

std::for_each(begin(numbers), end(numbers), [](int number)
{
    // do something with number
});

It looks very much like a range-based for loop, but with a mouthful of syntax added on the top. It’s because this is the wrong way to use std::for_each in C++11.

for_each is a tool for raising the level of abstraction of a range-based for loop. And showing the inside of the lambda within the call to for_each kills this purpose.

Let’s illustrate this with an example. Let’s write a piece of code that displays the numbers of the collection with a particular format, say for giving instructions to a calculator:

  • if the number is not zero it outputs it, preceded by its sign(“+” or “-“),
  • if the number is zero, it outputs “nop”.

Writing this with a range-based for loop gives the following code:

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

for (auto number : numbers)
{
    if (number > 0)
        std::cout << '+' << number << '\n';
    else if (number == 0)
        std::cout << "nop" << '\n';
    else if (number < 0)
        std::cout << number << '\n';
}

outputting:

+1
-2
+3
nop
+5

Now this code draws its reader into too many details, particularly if it is in the middle of a larger function. One simple way to factor out the logic of display is to encapsulate it into a function:

void displayAsInstruction(int number)
{
    if (number > 0)
        std::cout << '+' << number << '\n';
    else if (number == 0)
        std::cout << "nop" << '\n';
    else if (number < 0)
        std::cout << number << '\n';
}

And replace the loop code with this function:

for (auto number : numbers)
{
    displayAsInstruction(number);
}

It’s much better, but the number variable has no use any more. std::for_each packs it off:

std::for_each(begin(numbers), end(numbers), displayAsInstruction);

Making the most out of for_each

The number variable is gone, but a lot has appeared: the begin and end iterators, which we don’t need here (there are cases where they are useful though, like when applying a function until a certain element of the collection. But here they are just noise).

We were in the process of raising the level of abstraction by hiding the implementation of the display, and we’re back with new implementation details: iterators.

Here is how to fix this: wrap std::for_each to give it range semantics. Add this in a utilities header:

#include <algorithm>

namespace ranges
{
    template<typename Range, typename Function>
    Function for_each(Range& range, Function f)
    {
        return std::for_each(begin(range), end(range), f);
    }
}

And you can use it this way:

ranges::for_each(numbers, displayAsInstruction);

which reads what it does and with no extra information. Well, you could argue that the namespace should be taken away in a local using directive, but I guess this is a matter of taste.

It all comes down to levels of abstraction

Range-based for loops and for_each are two tools that serve different purposes.

Range-based for loops allow to write code directly at the loop site, but to keep expressiveness this code needs to be at the same abstraction level as the code surrounding the loop.

for_each allows to keep the abstraction level of the calling code by pushing implementation down into a dedicated function or function object, and replacing it with the function name as a label. To really achieve its purpose for_each needs range semantics.

As always, it all comes down to respecting levels of abstraction.

Related articles:

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed