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

南楼画角 提交于 2019-12-08 17:43:27

问题


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?

回答1:


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.




回答2:


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.




回答3:


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;


来源:https://stackoverflow.com/questions/42721683/taking-predicates-by-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!