Search code examples
c++language-lawyerc++20language-designstd-ranges

Why does ranges::for_each return the function?


The legacy std::for_each returns function as the standard only requires Function to meet Cpp17MoveConstructible according to [alg.foreach]:

template<class InputIterator, class Function>
  constexpr Function for_each(InputIterator first, InputIterator last, Function f);

Preconditions: Function meets the Cpp17MoveConstructible requirements.

[Note: Function need not meet the requirements of Cpp17CopyConstructible. end note]

This is reasonable since the user may want to reuse the function after the call.

The parallel version of for_each has no return:

template<class ExecutionPolicy, class ForwardIterator, class Function>
  void for_each(ExecutionPolicy&& exec,
                ForwardIterator first, ForwardIterator last,
                Function f);

Preconditions: Function meets the Cpp17CopyConstructible requirements.

This is because the standard requires Function to meet the Cpp17CopyConstructible, so returning the function is unnecessary as the user can freely create a copy if they want on the call side.

I noticed that ranges::for_each also returns the function:

template<input_iterator I, sentinel_for<I> S, class Proj = identity,
         indirectly_unary_invocable<projected<I, Proj>> Fun>
  constexpr ranges::for_each_result<I, Fun>
    ranges::for_each(I first, S last, Fun f, Proj proj = {});

However, the function signature already requires Fun to satisfy indirectly_unary_invocable which already guarantees that it is copy constructible.

The question is, why does the ranges::for_each still return the function? What's the point of doing this?


Solution

  • It returns the functor because that allowed some clever tricks with stateful functors back in the day (in C++98, I assume). You don't see those often today, because lambdas are usually more straightforward.

    Here's an example:

    #include <algorithm>
    #include <iostream>
    
    struct EvenCounter
    {
        int count;
    
        EvenCounter() : count(0) {}
    
        void operator()(int x)
        {
            if (x % 2 == 0)
                count++;
        }
    };
    
    int main()
    {
        int array[] = {1,2,3,4,5};
        int num_even = std::for_each(array, array+5, EvenCounter()).count;
        std::cout << num_even << '\n';
    }
    

    This is reasonable since the user may want to reuse the function after the call.

    I think the logic is backwards here. The function isn't required to be copyable simply because there's no reason for for_each to copy it.

    If you have a non-copyable (or even non-movable) function, you can pass it by reference using std::ref to avoid copies/moves, so you don't win anything here from the algorithm returning the function back to you.

    There was no std::ref in C++98, but there was also no move semantics, so for_each couldn't have worked with non-copyable functors in the first place.