11

I have a function template that takes an argument of some callable type, and uses std::bind to make a new callable object with predefined argument values for the original callable. I've written it with a forwarding reference parameter and std::forward, like this:

template <typename F>
auto make_example_caller(F &&f) {
  return std::bind(std::forward<F>(f), 123, 456, 789);
}

The cppreference documentation for std::bind says that the bound object "holds a member object of type std::decay<F>::type constructed from std::forward<F>(f)". Since std::bind forwards the function to its internal data member, forwarding the same function to the std::bind call in my own code seems reasonable and appropriate.

However, it's not clear what benefit that brings. If F is a reference type, std::decay removes the reference, so the bind object is going to store its own instance of the callable type. That instance will be constructed as a move if F is an rvalue reference, or a copy if F is an lvalue, and I can get the same result if I write my function like this:

template <typename F>
auto make_example_caller(F f) {  // Note, no &&
  return std::bind(std::move(f), 123, 456, 789);  // move, not forward
}

Now my function's own f parameter will be initialized by either move or copy depending on how the function is called, but either way I now have my own instance of the function object, which I can move into the bind object.

The latter way seems simpler, but I wonder if I'm missing something — especially since the same reasoning would apply to std::bind itself, yet it takes an F&& and forwards it, instead of taking an F by value and moving it. Is there a disadvantage in doing it that way? Something that I'm not seeing?

Wyzard
  • 33,849
  • 3
  • 67
  • 87
  • 1
    Possible duplicate of [Advantages of using forward](https://stackoverflow.com/questions/3582001/advantages-of-using-forward) – Marek R Jul 08 '19 at 07:15

1 Answers1

8

Using a forwarding reference and std::forward you can eliminate the creation of an extra object.

If you don't use a forwarding reference, you have three objects involved:

  1. Caller's original object
  2. Function parameter f, constructed using the copy or move constructor as appropriate
  3. Bind object's internal object, constructed by move constructor

If you use a forwarding reference with std::forward, you eliminate the second one. There will only be two objects created:

  1. Caller's original object
  2. Bind object's internal object, constructed using the copy or move constructor as appropriate

While move-constructing an object may be cheaper than copy-constructing (depending on the type), it still contributes some overhead that perfect-forwarding can avoid.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • Item #3 in your first list is constructed by move constructor, not copy constructor — but I see your point. I tend to think of the cost of a move constructor as being negligible, but that's not necessarily true in general (though it is in my current situation). – Wyzard Jul 08 '19 at 06:22
  • Oops, you are correct; I misstyped. Fixed. I would argue that move-construction is more expensive than perfect-forwarding for any type larger than a pointer. The actual performance difference may be unmeasurably small in many cases though. If your type is already a template parameter there's no real reason not to forward, but if its a concrete type (like `std::string`) I agree the extra complexity probably isn't worth it. – Miles Budnek Jul 08 '19 at 06:26
  • IMO this answer has not enough context to understood it. I think votes are form people who already knew the answer (or they think they do). A live example could be very helpful. – Marek R Jul 08 '19 at 07:11
  • I don't see how this answers title question "Is `std::forward` > `std::move` in my case?". `std::move` may yield perfect forwarding behaviour in many cases as well, it is not restricted to `std::forward`. – R2RT Jul 08 '19 at 09:30
  • @R2RT A question is more than its title. The body of the question makes it pretty clear that the OP is asking about the difference between `std::move`ing a value and `std::forward`ing a forwarding reference. Furthermore `std::move` will never yield perfect forwarding. `std::move` will _always_ cast its parameter to an rvalue; it is not value-category preserving like `std::forward`. – Miles Budnek Jul 08 '19 at 09:42