The big STL Algorithms tutorial: for_each

Sandor Dargo - Apr 3 '19 - - Dev Community

In this next part of the big STL algorithm tutorial, I'll explain only one function. The for_each algorithm.

What does it do?

for_each takes a range and a function to apply on each element of the given range.

As we have seen, a range (unless you are using the ranges library) means two iterators describing the beginning and the end of a range.

The function must be unary, meaning that it should take one parameter that has the type of that given range element. Or at least it should be convertible to it (e.g. an int can be converted to a boolean).

But how to pass a function? What is a function in this context?

It can be either the function itself or a function pointer, a function object or a lambda function.

Let's have all of them in the next example:

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

void printElement(const std::string& i_element) {
    std::cout << i_element << "\n";
}

class ElementPrinter {
public:

    void operator()(const std::string& i_element) const {
        std::cout << i_element << "\n";
    }
};

int main () {

  std::vector<std::string> strings {"The", "best", "revenge", "is", "not", "to", "be", "like", "your", "enemy"};

  std::for_each(strings.begin(), strings.end(), printElement);
  std::for_each(strings.begin(), strings.end(), ElementPrinter());
  std::for_each(strings.begin(), strings.end(), [](const std::string& i_element) {
        std::cout << i_element << "\n";
  });

  return 0;
}

Enter fullscreen mode Exit fullscreen mode

The first for_each takes a function.

The second one takes an instance of a functor.

In the third case, we use a lambda expression.

Which one should you use? It really depends on your use case. Sometimes you'll need a one-off logic and you don't want to store it anywhere and you go with a lambda. In some other cases, you might use any of the previous two. For more details refer to my introduction to lambda functions.

If you go with a functor, pay special attention to the rule of five. for_each needs functors to be move and copy constructible. Use a lambda and no such issues - everything needed is generated.

You should also note that it doesn't matter what the applied function returns, it will be omitted.

You might remember that for_each is a non-modifying sequence operation. Does it mean, that we cannot modify what we have in a sequence?

Let's give it a try!

#include <iostream>
#include <vector>
#include <algorithm>

int main () {

  std::vector<int> numbers {1,2,3,4,5};

  std::for_each(numbers.begin(), numbers.end(), [](int& i) {
        i = i * i;
  });

  for(auto num : numbers) {
    std::cout << num << "\n";
  }

  return 0;
}
Enter fullscreen mode Exit fullscreen mode

What is the output?

1
4
9
16
25
Enter fullscreen mode Exit fullscreen mode

So we could modify the list! We just had to pass the element to the function by reference. Great! But again, what about that non-modifying part?

You cannot modify the number of the elements in a container with for_each, you cannot add or delete elements, but you can modify the value of the given elements. Anyway, it would be quite difficult to iterate over a sequence that is being modified in its length during the iteration, right?

The Alternatives

We've seen what for_each is used for, we've seen how to use it, but why should we use it? What are its alternatives?

For loop with index

The good old way of iterating over a container. Sooooo uncool, isn't it?

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

int main () {

  std::vector<std::string> strings {"The", "answer", "is", "within", "you"};

  for (size_t i=0; i<strings.size();++i) {
      std::cout << strings[i] << "\n";

  }

  return 0;
}
Enter fullscreen mode Exit fullscreen mode

Well, coolness is not the issue. But handling the index in a for loop is tedious and not elegant. But if you need the index of an element, it's the goto option. Unless you have boost at your hands and want to use something fancy.

For loop with iterators

You can use iterators to walk through a list. You don't have to take care of the index anymore!

#include <iostream>
#include <vector>
#include <string>

int main () {


  std::vector<std::string> strings {"Be", "tolerant", "with", "others", "and", "strict", "with", "yourself"};
  for (std::vector<std::string>::iterator it = strings.begin(); it != strings.end(); ++it) {
      std::cout << *it << "\n";

  }

  return 0;
}

Enter fullscreen mode Exit fullscreen mode

Initializing the iterator is simply awful, isn't it? They have a long type, that's the reason. Besides iterators act like pointers, hence you need to dereference it if you want to get the value.

Since C++11, we can easily get rid off that awful iterator declaration by using the auto keyword.

#include <iostream>
#include <vector>
#include <string>

int main () {


  std::vector<std::string> strings {"Be", "tolerant", "with", "others", "and", "strict", "with", "yourself"};
  for (auto it = strings.begin(); it != strings.end(); ++it) {
      std::cout << *it << "\n";
  }

  return 0;
}

Enter fullscreen mode Exit fullscreen mode

You see, it's not inconvenient anymore. But we have better.

Range-based for loop

We used the auto keyword to omit the iterator's type at declaration time. But we can use that auto for an even better purpose.

#include <iostream>
#include <vector>
#include <string>

int main () {


  std::vector<std::string> strings {"The", "best", "revenge", "is", "not", "to", "be", "like", "your", "enemy"};
  for (auto element: strings) {
      std::cout << element << "\n";
  }

  return 0;
}
Enter fullscreen mode Exit fullscreen mode

Range based for loops vs. for_each

The main question is when we don't need the indexes, what should we use? A range based for loop or the for_each algorithm?

To me, the range based for loop is the go to solution. On the other hand, it can be used only with the whole container, while with for_each it's up to you to specify the range you want to iterate over.

If you want to abstract out the logic that the loop has to perform on each element than using a for_each might be more elegant.

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

void printElement(const std::string& i_element) {
    std::cout << i_element << "\n";
}

int main () {

  std::vector<std::string> strings {"The", "best", "revenge", "is", "not", "to", "be", "like", "your", "enemy"};

  std::for_each(strings.begin(), strings.end(), printElement);

  for(const auto& element: strings) {
    printElement(element);
  }

  return 0;
}

Enter fullscreen mode Exit fullscreen mode

Which one reads better? Probably the first one. But it would not be worthwhile to use the for_each with a lambda.

std::for_each(strings.begin(), strings.end(), [](const std::string& i_element) {
    std::cout << i_element << "\n";
}]);
Enter fullscreen mode Exit fullscreen mode

This doesn't read well. So the choice is mainly a question of abstraction. Here you can read a deeper analysis on this topic.

Conclusion

Today, we've seen the for_each algorithm which was a cool enhancement in the pre-C++11 times when we didn't have range-based for loops around. In comparison with it, it's not a default solution for looping over containers, but we still have its fair usage. And don't forget the pearls of wisdom of Marcus Aurelius and Seneca hidden in this post.

Keep tuned, in the next episode we'll discuss how to find items in a container.

This article has been originally published on my blog.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .