16

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?
David G
  • 94,763
  • 41
  • 167
  • 253
PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
  • note how for_each returns the "ready" object – PlasmaHH Jun 21 '13 at 12:20
  • Why don't you use range-based loop ? `for ( int i : a ) { sum( i ); }` – borisbn Jun 21 '13 at 12:34
  • I'll use it if i need it, but the question isn't about how to do a summatory, is about the algorithm library. – PaperBirdMaster Jun 21 '13 at 12:40
  • For example, if stl-algorithms will take a non-const reference then `for_each( ..., just_cout_arg_to_std_out() )` wouldn't be compiled – borisbn Jun 21 '13 at 12:46
  • 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. – Mark B Jun 21 '13 at 12:48

3 Answers3

7

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.

Balog Pal
  • 16,195
  • 2
  • 23
  • 37
  • 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. – PlasmaHH Jun 21 '13 at 12:11
  • sure, look up the defect reports in the WG21 papers http://www.open-std.org/jtc1/sc22/wg21/docs. LDR109 that fixed bind1st and bind2nd is a trivial example – Balog Pal Jun 21 '13 at 12:15
  • 1
    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. – PaperBirdMaster Jun 21 '13 at 12:16
  • @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. – PlasmaHH Jun 21 '13 at 12:29
  • 1
    @PaperBirdMaster 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. http://www.youtube.com/watch?v=eR34r7HOU14 – Sebastian Redl Jun 21 '13 at 12:53
  • 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](http://stackoverflow.com/a/17235216/499359) is really worth to read too. – PaperBirdMaster Jun 28 '13 at 11:13
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.

PlasmaHH
  • 15,673
  • 5
  • 44
  • 57
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;
Enigma
  • 1,699
  • 10
  • 14
  • 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 reference – PaperBirdMaster Jun 21 '13 at 12:06
  • Hmm.. This link tries to accomplish the same thing you do: http://en.cppreference.com/w/cpp/algorithm/for_each. Maybe try it out. – Enigma Jun 21 '13 at 12:10
  • maybe `for ( int i : a ) { sum( i ); }` is more simple ? – borisbn Jun 21 '13 at 12:30