The posts General Loop Statements in Modern C++ and Range-for-statement in modern C++ cover ways to write explicit loops. But explicit loops can be tedious to write and, what is more important, – harder to read, because the resulting code requires to spend the extra time by others in order to understand what is going on in the explicit loop. As alternative, the C++ standard library provides algorithms (defined in <algorithm>
) that takes a callback function as an argument to be called for each element of a sequence. But what the advantage of using standard algorithms instead of writing the explicit loops? The main advantage is that it saves the time of both authors and users of the code, because the algorithms provided by the C++ standard library are proven, well-designed, documented and commonly known which allow to express the intentions of the programmers clearly. As a rule, the more standard library facilities are used to write code, the easier it is to maintain that code in the future.
The most general and simple algorithm intended to traverse sequences is std::for_each
. This algorithm just eliminate the need to write the explicit loop, for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <algorithm> #include <iostream> #include <iterator> #include <vector> void negate(std::vector<int>& nums) { for_each(begin(nums), end(nums), [](auto& n){n = -n;}); } int main() { std::vector<int> v{1,2,3,4,5}; negate(v); copy(cbegin(v), cend(v), std::ostream_iterator<int>{std::cout}); } |
The example above demonstrates how to modify the entire vector of integers, and print the result to the standard output without writing the loops explicitly. Iterators were used to specify the sequence. Although it seems like it looks good, this snippet can be improved. Actually, the function negate()
transforms the vector of integers by negating each element. The C++ standard library provides the special algorithm std::transform
and unary function object std::negate
(defined in <functional>
). std::transform
applies the given function to each element of the input sequence and stores the returned value to the corresponding element of the output sequence. std::negate<T>
just calls operator -
(minus) on an instance of type T and returns the result. Hence, the example above could be rewritten as follow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <algorithm> #include <functional> #include <iostream> #include <iterator> #include <vector> void negate(std::vector<int>& nums) { transform(cbegin(nums), cend(nums), begin(nums), std::negate{}); } int main() { std::vector<int> v{1,2,3,4,5}; negate(v); copy(cbegin(v), cend(v), std::ostream_iterator<int>{std::cout}); } |
Looks more clear, isn’t it? The intention to transform the elements of the vector by negating each element is clearly expressed now.
Summarizing this post:
- in many cases explicit loops can be avoided;
std::for_each
algorithm is a good alternative to the explicit loops;- as a rule of thumb, before using
std::for_each
with a home-brewed callback, consider if there is a more specialized algorithm and standard helper function which are allows to express the intention more clearly and save you and the reader of your code from extra efforts.
And thanks to the algorithms of the C++ standard library which eliminates the need to write the loops explicitly in many cases!