7

Consider this code:

template<class F>
void foo1(F f) { f(); }

template<class F>
void foo2(F const& f) { f(); }

template<class F>
void foo3(F&& f) { f(); }

Which version of foo should I use? foo1 is what I see most "in the wild" but I fear that it might introduce copies that I don't want. I have a custom functor that is kind of heavy to copy so I would like to avoid that. Currently I'm leaning towards foo3 (as foo2 would disallow mutating functors) but I'm unsure about the implications.

I'm targeting C++11.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Artificial Mind
  • 945
  • 6
  • 19

1 Answers1

7

I’d actually prefer foo3 over foo1 (though the body should be std::forward<F>(f)();)

foo3 will cause type F to be deduced to the type that lets you perfectly forward the argument due to reference collapsing. This is useful, as you won’t make copies of anything by default, and can maintain the value category (lvalue vs rvalue) if you decide you want to forward the functor to something that does want a copy of it.

In general, the first form (foo1) is fine if your function is going to store its own copy of the functor, but the third form (foo3) is better for forwarding (using) arguments.

I also recommend Scott Meyers’ excellent post about universal references, as well as this related Stack Overflow question.

John Drouhard
  • 1,209
  • 2
  • 12
  • 18
  • `std::forward` should not be used here. Nothing in the question is being forwarded. The functor is only being called. – super Jul 01 '18 at 15:01
  • 1
    Thanks. Updated the answer to be correct. @super, the functor should absolutely be forwarded. It can affect overload resolution. See the answer in the SO question I added a link to. – John Drouhard Jul 01 '18 at 15:15
  • I see. A bit of an unusual scenario, but very valid. Probably a good habit to adapt to ensure code works under all possible scenarios. – super Jul 01 '18 at 15:21
  • 2
    @super I'm not sure if you really should forward (unless you never call the parameter more than once). If `operator()` has no separate `&&`-qualified overload, then forwarding does nothing anyway. But if there is such overfload (though I've never seen anyone do that) and if the functor was passed as an rvalue and got moved with `std::forward`, then calling `operator()` can leave it in an unspecified state, which is not good if you're going to call `operator()` on it again. – HolyBlackCat Jul 01 '18 at 15:25
  • @HolyBlackCat, how would it be moved with std::forward? Would you have an example where this can go wrong? – Jeff Garrett Jul 02 '18 at 16:06
  • @JeffGarrett Something like this maybe? http://coliru.stacked-crooked.com/a/8e02c2cd10370e3f – HolyBlackCat Jul 02 '18 at 16:15
  • @HolyBlackCat, thanks! So you were thinking not of the functor being moved, but having an rvalue-ref overload which returns an rvalue-ref to some of its guts, which could inadvertently be moved. If one is being very careful about value categories, though, I would expect to capture the return value by std::string&& or auto&&, which wouldn't move out the guts in your example. But you do have to be very careful not to accidentally move, so I can see how it could be fragile – Jeff Garrett Jul 02 '18 at 16:26
  • @JeffGarrett Depends you what you mean by moving, I guess. If you mean making a new object via moving, then it's not moved. If you mean the effect that you'd get if you applied `std::move`, then the functor can be considered moved. – HolyBlackCat Jul 02 '18 at 17:06