9

Consider the following function:

Widget f(Widget w) {
   return w;
}

Supposing that Widget implements both copy and move constructors, according to the C++ standard, w has to be treated as a rvalue object in the return statement, in case the compiler would not consider copy elision a better alternative.

On the other hand, consider the version below:

Widget f(Widget&& w) {
   return w;
}

As opposite to the first version, according to Item 25 of Effective Modern C++, the author seems to be implying that returning w would certainly invoke the copy constructor. In other words, he suggests to return std::move(w) instead, in order to make the compiler use the (possibly faster) move constructor.

Can you explain why the second version of f() taking a Widget&& as argument is not equivalent to the first version taking a Widget by value with respect to the constructor being called in the return statement, also considering that in the body of both the functions the expression w refers to an lvalue?

Complete example demonstrating the behavior:

#include <iostream>

struct Widget
{
  Widget() { std::cout << "constructed" << std::endl; }
  ~Widget() { std::cout << "destructed" << std::endl; }
  Widget(const Widget&) { std::cout << "copy-constructed" << std::endl; }
  Widget(Widget&&) { std::cout << "move-constructed" << std::endl; }
};

Widget
f1(Widget w)
{
  return w;
}

Widget
f2(Widget&& w)
{
  return w;
}

int
main()
{
  f1(Widget {});
  std::cout << std::endl;
  f2(Widget {});
}

Output:

constructed
move-constructed
destructed
destructed

constructed
copy-constructed
destructed
destructed
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
Martin
  • 9,089
  • 11
  • 52
  • 87
  • Both w in 'return w' are lvalues (which might be copy elided in return). The main difference are the function arguments, the first is a copy(!) the second not. Applying a std::move to the second function should make it (almost?) a noop. –  Feb 17 '15 at 22:45
  • 4
    @MattMcNabb It really is right. There's an exception for references, there has to be. Making `Widget f(Widget& w) { return w; }` silently move would be dangerous. Now, that exception *might* be okay if it was just for lvalue references, but it's simply for everything that's not known to be safe to move from. –  Feb 17 '15 at 22:46
  • @hvd `return w;` always copies the argument to the return value - regardless whether it is `Widget w`, `Widget &w` or `Widget &&w`. (At least, that's what my compiler is telling me).The return value can then be moved into the object it's assigned to; that move is normally elided. – M.M Feb 17 '15 at 22:53
  • In the body of both functions, you can take the address of `w` and even assign to it so it is, by all mans, an lvalue. – 5gon12eder Feb 17 '15 at 22:53
  • @MattMcNabb No, with `Widget w` the move constructor is used. See [class.copy]p32. –  Feb 17 '15 at 22:54
  • @hvd yes, in `Widget w` , `w` is copy-constructed from the argument supplied, and then moved into the return value (but the point is that there is still a copy). I guess this is what the question is about specifically – M.M Feb 17 '15 at 22:56
  • @MattMcNabb The argument itself may be constructed in-place without any copy too, for example if the function is called as `f({})`. –  Feb 17 '15 at 22:57
  • @Martin Since your claim seems controversial, I have added code to your question that demonstrates that it is true. This is a rather large edit. If you don't like it, please roll it back. – 5gon12eder Feb 17 '15 at 22:58
  • In the code example you have an extra copy elision going on that obscures things a bit ; suggest changing to `Widget u; f1(u);` ... `f2(std::move(u));` – M.M Feb 17 '15 at 23:00
  • I don't want to dupe-vote because it is not the exact same problem but I think that Jonathan Wakely's answer to [this question](https://stackoverflow.com/questions/17481018/when-is-explicit-move-needed-for-a-return-statement) could be interesting to you. If I'm not mistaken, it basically addresses the same language rule. – 5gon12eder Feb 17 '15 at 23:22
  • 1
    @5gon12eder That answer is [out of date](https://stackoverflow.com/questions/25875596/can-returning-a-local-variable-by-value-in-c11-14-result-in-the-return-value-b/25876175#25876175). – T.C. Feb 18 '15 at 07:22
  • @T.C. Thanks, I didn't know that. I see that earlier today, Jonathan already added a note to the linked answer that the standard has meanwhile been revised. Also +1 for your more recent answer. – 5gon12eder Feb 18 '15 at 19:45

1 Answers1

5

The criteria for implicitly moving are based on when copy elision is allowed, but with some exceptions. Copy elision is allowed for local objects that aren't function parameters or catch parameters. Implicit move adds local objects that are function parameters to that. So it allows it in Widget f(Widget w) { return w; }, but not in Widget f(Widget&& w) { return w; }, where w is a local variable, but not a local object.

Implicitly moving would be safe in several cases where it is not currently allowed, including in your example. However, implicitly moving is carefully considered on a case-by-case basis, and your example either hasn't been considered yet, or hasn't been deemed safe yet.

The very similar example Widget f(Widget& w) { return w; } would be unsafe: a caller doing Widget w; f(w); cannot expect w to silently be cleared out by the compiler.

That very similar example shows how risky it is to enable implicit moves, and why you have to spell it out even though it may seem obvious to you. Wording that referred to a "local variable", which would cover Widget&& w, would also cover Widget& w. It's far worse to have implicit move where it's unsafe than it is to miss implicit move where it is safe, it has to be absolutely certain that the wording only covers safe cases.

  • I didn't think copy elision was allowed when returning a function parameter. – juanchopanza Feb 17 '15 at 22:58
  • @hvd: that's what I am trying to understand: if it's "not considered" or "unsafe". In the latter case, I'd like to understand why. The "very similar" example is evidently unsafe. – Martin Feb 17 '15 at 23:01
  • 1
    @juanchopanza Oops... You're very right, and my answer is wrong. The exception isn't in copy elision, it's that it's an additional case where the standard requires an implicit move. Will edit. –  Feb 17 '15 at 23:02
  • @Martin I was hoping to show how easy it is to get the wording wrong, which would have disastrous effects, and have amended my answer slightly. Does that look better to you? –  Feb 17 '15 at 23:10