Why the sequence-operation algorithms predicates are passed by copy?
Asked Answered
B

3

17

I'm wondering why functors are passed by copy to the algorithm functions:

template <typename T> struct summatory
{
    summatory() : result(T()) {}

    void operator()(const T& value)
    { result += value; std::cout << value << "; ";};

    T result;
};

std::array<int, 10> a {{ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }};
summatory<int> sum;

std::cout << "\nThe summation of: ";
std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;

I was expecting the following output:

The summation of: 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; is: 143

But sum.result contains 0, that is the default value assigned in the ctor. The only way to achieve the desired behaviour is capturing the return value of the for_each:

sum = std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;

This is happening because the functor is passed by copy to the for_each instead of by reference:

template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );

So the outer functor remains untouched, while the inner one (which is a copy of the outer) is updated and is returned after perform the algorithm (live demo), so the result is copied (or moved) again after doing all the operations.


There must be a good reason to do the work this way, but I don't really realize the rationale in this design, so my questions are:

  • Why the predicates of the sequence-operation algorithms are passed by copy instead of reference?
  • What advantages offers the pass-by-copy approach in front of the pass-by-reference one?
Branch answered 21/6, 2013 at 11:49 Comment(5)
note how for_each returns the "ready" objectTwentytwo
Why don't you use range-based loop ? for ( int i : a ) { sum( i ); }Pinna
I'll use it if i need it, but the question isn't about how to do a summatory, is about the algorithm library.Branch
For example, if stl-algorithms will take a non-const reference then for_each( ..., just_cout_arg_to_std_out<int>() ) wouldn't be compiledPinna
Not answering the question, but just use std::accumulate to do a sum, and if you need a stateful predicate you can always store the state external to the predicate.Marsha
U
8

It's mostly for historic reasons. At '98 when the whole algo stuff made it into the standard references had all kind of problems. That got eventually resolved through core and library DRs by C++03 and beyond. Also sensible ref-wrappers and actually working bind only arrived only in TR1.

Those who tried use algos with early C++98 having functions using ref params or returns can recall all kind of trouble. Self-written algos were also prone to hit the dreaded 'reference to reference' problem.

Passing by value at least worked fine, and hardly created many problems -- and boost had ref and cref early on to help out where you needed to tweak.

Underneath answered 21/6, 2013 at 11:57 Comment(6)
Can you back that up with some references? I have not heard of any "all kinds of problems" with references in the past two decades, especially not something that was fixed in C++03 that would justify not using it in the standard library itself.Twentytwo
sure, look up the defect reports in the WG21 papers open-std.org/jtc1/sc22/wg21/docs. LDR109 that fixed bind1st and bind2nd is a trivial exampleUnderneath
I'm disappointed with an historical answer :( i was expecting some subtle performance reasons out of my knowledge. But this answer nearly answer my both questions.Branch
@BalogPal: I would not really call that "all kinds of problems with references", especially since its some mistake in the library, and not a fundamental problem with references that might make them unwanted to be used in the library.Twentytwo
@Branch If you really want a performance reason, Chandler's C++Now keynote might offer some, though this was definitely not part of the decision process. youtube.com/watch?v=eR34r7HOU14Tenatenable
I've set this answer as the accepted because the explanation (and the comments below) are a kind of explanation of my question, but the PlasmaHH guessing is really worth to read too.Branch
T
5

This is purely a guess, but...

...lets for a moment assume it takes by reference to const. This would mean that all of your members must be mutable and the operator must be const. That just doesn't feel "right".

... lets for a moment assume it takes by reference to non-const. It would call a non-const operator, members can just be worked on fine. But what if you want to pass an ad-hoc object? Like the result of a bind operation (even C++98 had -- ugly and simple -- bind tools)? Or the type itself just does everything you need and you don't need the object after that and just want to call it like for_each(b,e,my_functor());? That won't work since temporaries can not bind to non-const references.

So maybe not the best, but the least bad option is here to take by value, copy it around in the process as much as needed (hopefully not too often) and then when done with it, return it from for_each. This works fine with the rather low complexity of your summatory object, doesn't need added mutable stuff like the reference-to-const approach, and works with temporaries too.

But YMMV, and so likely did those of the committee members, and I would guess it was in the end a vote on what they thought is the most likely to fit the most use-cases.

Twentytwo answered 21/6, 2013 at 12:26 Comment(0)
E
1

Maybe this could be a workaround. Capture the functor as reference and call it in a lambda

std::for_each(a.begin(), a.end(), [&sum] (T& value) 
    {
        sum(value);   
    });

std::cout << "is: " << sum.result;
Either answered 21/6, 2013 at 12:3 Comment(3)
it's pretty neat!, but i wasn't looking for workarounds (but i will use it anyway!) :P i'm just wondering why the function signature of this algorithms isn't a referenceBranch
Hmm.. This link tries to accomplish the same thing you do: en.cppreference.com/w/cpp/algorithm/for_each. Maybe try it out.Either
maybe for ( int i : a ) { sum( i ); } is more simple ?Pinna

© 2022 - 2024 — McMap. All rights reserved.