Why does ranges::for_each return the function?

Why does ranges::for_each return the function?


10

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?

1

  • 1

    Interesting question. It might just be to make it easier to drop in as a replacement for std::for_each. But that's pure speculation.

    – Toby Speight

    14 hours ago

3 Answers
3


14

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.

2

  • Thanks for the guess. However, I don't see the need to specifically allow this kind of trickery in modern C++, especially for more powerful C++20 constraint functions. Note that ranges::for_each does not just return fun, so the spelling will be ranges::for_each(...).fun.count in your case, which makes even less sense.

    – 康桓瑋

    13 hours ago

  • @康桓瑋 I guess some people still like this trick, or at least the committee members thought so.

    – HolyBlackCat

    13 hours ago


4

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

The answer here is simply: all std::ranges::meow algorithms simply either (a) return the same thing as std::meow (e.g. count, find, etc.) or (b) additionally return the newly advanced input iterator (e.g. copy) if that would be a useful thing to return. Technically some of the value-returning functions (like, again, count) could also potentially benefit from returning the end iterator, but that’s a bit hit to ergonomics for those, so that didn’t happen.

Notably, they all either (a) preserve or (b) add to the existing return types.

Nobody wanted to go through and actually change return types of algorithms and make additional design decisions like that on top of the massive body of work that Ranges is already. As our favorite cat already points out, std::for_each returning the function object itself is already kind of unnecessary in C++11 since you can accomplish the stateful thing by simply using std::ref if you really want – but it doesn’t seem worthwhile to deprecate this and change std::for_each to return void, and then doubly it doesn’t seem worthwhile to have std::ranges::for_each be the one algorithm that actually differs in behavior from its std:: counterpart.


Well, technically not the only one. std::ranges::copy specifies how many iterator increments happen while std::copy does not. I happened to touch on this a bit in my CppNow talk this year: take(5).


-1

Here is a straightforward use case:

struct sum {
  int total;
  auto operator ()(const int x) { total += x ; }
};

int main() {
  const auto v = std::vector{ 1, 2, 3, };
  const auto [i, f] = std::ranges::for_each(v, sum());
  std::cout << f.total;
}

Compiler Explorer
NB: I noticed there was already a similar answer when I was done writing this…

I’m currently looking for the actual rationale behind this choice, I’ll update this when I have an answer from a reputable source.



Leave a Reply

Your email address will not be published. Required fields are marked *