When should I declare my function as:
void foo(Widget w);
As opposed to:
void foo(Widget&& w);
?
Assume this is the only overload (as in, I pick one or the other, not both, and no other overloads). No templates involved. Assume that the function foo
requires ownership of the Widget
(e.g. const Widget&
is not part of this discussion). I'm not interested in any answer outside the scope of these circumstances. (See addendum at end of post for why these constraints are part of the question.)
The primary difference that my colleagues and I can come up with is that the rvalue reference parameter forces you to be explicit about copies. The caller is responsible for making an explicit copy and then passing it in with std::move
when you want a copy. In the pass by value case, the cost of the copy is hidden:
// If foo is a pass by value function, calling + making a copy:
Widget x{};
foo(x); // Implicit copy
// Not shown: continues to use x locally
// If foo is a pass by rvalue reference function, calling + making a copy:
Widget x{};
// foo(x); //This would be a compiler error
auto copy = x; //Explicit copy
foo(std::move(copy));
// Not shown: continues to use x locally
Other than forcing people to be explicit about copying and changing how much syntactic sugar you get when calling the function, how else are these different? What do they say differently about the interface? Are they more or less efficient than one another?
Other things that my colleagues and I have already thought of:
- The rvalue reference parameter means that you may move the argument, but does not mandate it. It is possible that the argument you passed in at the call site will be in its original state afterwards. It's also possible the function would eat/change the argument without ever calling a move constructor but assume that because it was an rvalue reference, the caller relinquished control. Pass by value, if you move into it, you must assume that a move happened; there's no choice.
- Assuming no elisions, a single move constructor call is eliminated with pass by rvalue.
- The compiler has better opportunity to elide copies/moves with pass by value. Can anyone substantiate this claim? Preferably with a link to
gcc.godbolt.org
showing optimized generated code from GCC/Clang rather than a line in the standard. My attempt at showing this was probably not able to successfully isolate the behavior.
Addendum: why am I constraining this problem so much?
- No overloads - if there were other overloads, this would devolve into a discussion of pass by value vs a set of overloads that include both const reference and rvalue reference, at which point the set of overloads is obviously more efficient and wins. This is well known, and therefore not interesting.
- No templates - I'm not interested in how forwarding references fit into the picture. If you have a forwarding reference, you call
std::forward
anyway. The goal with a forwarding reference is to pass things as you received them. Copies aren't relevant because you just pass an lvalue instead. It's well known, and not interesting. foo
requires ownership ofWidget
(aka noconst Widget&
) - We're not talking about read-only functions. If the function was read-only or didn't need to own or extend the lifetime of theWidget
, then the answer trivially becomesconst Widget&
, which again, is well known, and not interesting. I also refer you to why we don't want to talk about overloads.