How to Make for_each Stop When a Condition Is True
std::for_each
applies a function to each of the elements within a range:
std::for_each(begin(v), end(v), f);
But it doesn’t allow to stop somewhere in the range, when a condition becomes true on an element.
Let’s see how to achieve this by using STL algorithms, and with more modern C++ libraries such as ranges and pipes.
Stopping std::for_each
In the previous post, we saw how to stop std::for_each
after N elements. One solution was to use std::for_each_n
(with the drawbacks it comes with). But to stop after a condition on an element becomes true, there is no algorithm the STL offers for that.
A clean solution using algorithms is to use a combination of std::find_if
and std::for_each
:
auto numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; auto rangeEnd = std::find_if(begin(numbers), end(numbers), [](int i){ return i > 5; }); std::for_each(begin(numbers), rangeEnd, [](int& i){ i *= 10; });
This code starts by locating the position of the first element satisfying a predicate (being greater than 5), and then runs a std::for_each
from the beginning of the collection and to that position.
There is a bit of noise coming from the begin and end iterators, which we can do away with by using C++20 range algorithms:
auto numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; auto rangeEnd = std::ranges::find_if(numbers, [](int i){ return i > 5; }); std::ranges::for_each(begin(numbers), rangeEnd, [](int& i){ i *= 10; });
But forfor_each
we have to keep writing the end iterator, because it is not the end of the range and the range algorithm cannot guess it.
A hacky solution
Just for fun, let’s mention that there is a way to achieve this by using one STL algorithm. But as Martin mentions it in our discussion on bitsets, we should not use it in production because it is a misuse of this algorithm:
auto numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; auto rangeEnd = std::find_if(begin(numbers), end(numbers), [](int& i){ bool stop = i > 5; i *= 10; return stop; });
This code use std::find_if
to perform both the check for when to stop, and to apply the function.
We shouldn’t do this because std::find_if
is made for locating a position in a collection, and nothing more. By making it have a side effect we make that code stressful, and not expressive because it says that it only finds a position.
But it’s fun enough to mention it, at least for some definition of fun.
Why no single algorithm can do this job
Why isn’t there a for_each_until
algorithm in the STL ? Should we make one?
In fact, we can make such an algorithm:
template<typename InputRange, typename Function, typename Predicate> Function for_each_until(InputRange&& range, Function function, Predicate predicate) { auto rangeEnd = std::find_if(begin(range), end(range), predicate); std::for_each(begin(range), rangeEnd, function); return function; }
It would be called this way:
auto numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; for_each_until(numbers, [](int& i){ i *= 10; }, [](int i){ return i > 5; });
But this may be not a good idea, for several reasons.
First, it is not clear from the call site which lambda serves what purpose.
If we swap them the code would not compile though, unless they both return something:
auto numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; for_each_until(numbers, [](int i){ return i > 5; }, [](int& i){ i *= 10; return i;});
In which case the code would compile and the results would be completely wrong. But even without swapping them, the code doesn’t say which lambda is used for what purpose. We could use strong lambdas to mitigate this problem though.
A second reason is that it is not clear from the call site if the predicate is applied on elements before the function is applied on them or after.
And a third reason is that this technique doesn’t scale.
Indeed, if we want to perform a transform
algorithm and make it stop, should we create a transform_until
?
And if we want make find_if
stop, should we create a find_if_until
? This one would be really confusing:
find_if_until(numbers, [](int i){ return i > 5; }, [](int& i){ return i % 2;});
Which predicate makes the algorithm stop? Which one is the real predicate to locate the position?
This *_until
technique is not modular.
Let’s look outside of the STL algorithms. Here is how to make for_each
stop by using two modern C++ libraries, ranges and pipes, that can make for_each
stop without suffering from the above issues.
How to make for_each
stop with ranges
Ranges are entering the standard in C++20. Before C++20, the range-v3 library implements most of what is in the C++20 standard (and a lot more things too).
Here is how to make for_each
stop by using ranges:
auto numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; ranges::for_each(numbers | ranges::view::take_while([](int i){ return i <= 5; }), [](int& i){ i *= 10; });
Our three above issues are solved:
- it is clear which lambda serves what purpose, because they are at two different positions,
- it is clear that the predicate is applied on the input of
for_each
, - the design is modular because we can reuse
take_while
with other algorithms.
Note that I don’t use the ranges::view::transform
adaptor because it has a semantics of producing an output out of applying a function on the input. This is different from the semantics of for_each
, that perform a side effect on the input (or on anything else).
How to make for_each
stop with pipes
Here is now how to make for_each
stop by using pipes. Pipes is a library allowing to create pipelines for expressive code with collections in C++.
auto numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; numbers >>= pipes::take_while([](int i){ return i <= 5; }) >>= pipes::for_each([](int& i){ i *= 10; });
Here too, the three initial issues are solved:
- it is clear which lambda serves what purpose, because they are at two different positions,
- it is clear that the predicate is applied on the input of
for_each
, - the design is modular because we can reuse
take_while
with other pipes.
Applying a predicate on the result of for_each
?
So far we’ve seen how to apply a predicate on the input of for_each
. This is the need I have encountered in practice.
But what if the function that for_each
applies modifies the inputs, and we want to apply the predicate on that modified value, and not on the element itself?
In other words, how would you rewrite that for loop with more modern code?
bool found = false; for(auto number = begin(numbers); number != end(numbers) && !found; ++number) { *number *= 10; found = *number > 50; }
Leave your answer in a godbolt or coliru link a comment!
You will also like
- How to Make
for_each
Stop After N Elements - Is std::for_each obsolete?
- The Pipes library
- Why You Should Use std::for_each over Range-based For Loops
- for_each_arg: Applying a Function to Each Argument of a Function in C++
- The World Map of C++ STL Algorithms
Share this post!