12

I was looking at the various signatures for std::find_if on cppreference.com, and I noticed that the flavors that take a predicate function appear to accept it by value:

template< class InputIt, class UnaryPredicate >
InputIt find_if( InputIt first, InputIt last,
             UnaryPredicate p );

If I understand them correctly, lambdas with captured variables allocate storage for either references or copies of their data, and so presumably a "pass-by-value" would imply that the copies of captured data are copied for the call.

On the other hand, for function pointers and other directly addressable things, the performance should be better if the function pointer is passed directly, rather than by reference-to-pointer (pointer-to-pointer).

First, is this correct? Is the UnaryPredicate above going to be a by-value parameter?

Second, is my understanding of passing lambdas correct?

Third, is there a reason for passing by value instead of by reference in this situation? And more to the point, is there not some sufficiently ambiguous syntax (hello, universal reference) that would let the compiler do whatever it wants to get the most performance out?

aghast
  • 14,785
  • 3
  • 24
  • 56
  • If it is going to store a copy of the predicate, passing by reference could be less optimal than by value. But are you asking about the passing, or about the storing? – juanchopanza Feb 15 '18 at 09:38
  • @juanchopanza i think the question is about general efficiency, so you can roll out your answer. – Yola Feb 15 '18 at 09:50
  • Possible duplicate of [Passing functor object by value vs by reference (C++)](https://stackoverflow.com/questions/8196345/passing-functor-object-by-value-vs-by-reference-c) – xskxzr Feb 15 '18 at 11:00

2 Answers2

12

Is the UnaryPredicate above going to be a by-value parameter?

Yes, that's what it says in the function parameter list. It accepts a deduced value type.

Beyond that, lambda expressions are prvalues. Meaning, with 's guaranteed copy elision, that p is initialized directly from the lambda expression. No extra copies of the closure or the captured objects are being made when passing it into the function (the function may make more copies internally however, though that's not common).

If the predicate was passed by reference, a temporary object would need to be materialized. So for a lambda expression, nothing is gained by a switch to pass by reference.

If you have other sorts of predicates, which are expansive to copy, then you can pass in std::reference_wrapper to that predicate object, for a cheap "handle" to it. The wrapper's operator() will do the right thing.

The definition is mostly historic, but nowadays it's really a non-issue to do it with pass by value.


To elaborate on why referential semantics would suck, let's try to take it through the years. A simple lvalue reference won't do, since now we don't support binding to an rvalue. A const lvalue reference won't do either, since now we require the predicate to not modify any internal state, and what for?

So up to , we don't really have an alternative. A pass by value would be better than a reference. With the new standard, we may revise our approach. To support rvalues, we may add an rvalue reference overload. But that is an exercise in redundancy, since it doesn't need to do anything different.

By passing a value, the caller has the choice in how to create it, and for prvalues, in , it's practically free. If the caller so desires, they can provide referential semantics explicitly. So nothing is lost, and I think much is gained in terms of simplicity of usage and API design.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • so, why not to make it reference parameter, why to force people use `reference_wrapper`? What is the gain? – Yola Feb 15 '18 at 09:46
  • @Yola - Historic reasons? Encouraging people to make "light" predicates? Take your pick. Nowadays there's no good reason to change that functions signature, anyway. – StoryTeller - Unslander Monica Feb 15 '18 at 09:47
  • 2
    @Yola historic reasons and referential semantics suck. Setting the "default" to be value semantics and forcing the user to do something special to get referential semantics is the right way around. – juanchopanza Feb 15 '18 at 09:53
  • I'm looking into VS17 implementation of `find_if`, it uses internal function with the following signature `template inline _InIt _Find_if_unchecked(_InIt _First, _InIt _Last, _Pr& _Pred)` why it needs to pass to the next level by reference? – Yola Feb 15 '18 at 09:54
  • @Yola Default by reference in this case might be harmful. If a functor has internal state and is an lvalue, the internal state will be change after a call, which is unlikely the desired behavior. – llllllllll Feb 15 '18 at 09:55
  • @juanchopanza i like your reasoning can elaborate it into the answer? Or maybe StoryTeller could update this answer with the reason why value semantics is preferable? – Yola Feb 15 '18 at 09:56
  • 2
    @Yola - Probably because it wants to avoid making more copies *internally*. Just because they can' doesn't mean they want to pessimize. The public API sends a clear message about referrential semantics. The internal API can do whatever it wants. – StoryTeller - Unslander Monica Feb 15 '18 at 09:56
  • What about a forwarding reference? It is more flexible at the cost of no redundancy. – Matthias Feb 20 '20 at 19:44
6

There are actually multiple reasons:

  1. you can always turn deduced value arguments into using reference semantics but not vice verse: just pass std::ref(x) instead of x. std::reference_wrapper<T> isn't entirely equivalent to passing a reference but especially for function object it does the Right Thing. That is, passing generic arguments by value is the more general approach.

  2. Pass by reference (T&) doesn't work for temporary or const objects, T const& doesn't work for non-const&, i.e., the only choice would be T&& (forwarding reference) which didn't exist pre-C++11 and the algorithm interfaces didn't change since they were introduced with C++98.

  3. Value parameters can be copy elided unlike any sort of reference parameters, including forwarding references.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • For those who wants to know other use cases of `std::ref` and `std::reference_wrapper`, see https://www.nextptr.com/tutorial/ta1441164581/stdref-and-stdreference_wrapper-common-use-cases – doraemon Mar 18 '23 at 03:20