2
  1. Given the following; will C++17 guarantee copy elision?
template <typename Widget>
Widget frobnicate(Widget w) {
  // optionally mutate w in some way
  return w;
}
  1. Does answer change if Widget implements a move constructor or not?

  2. Should I ever return by move? Such as, in this case:

return std::move(w);
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
dalle
  • 18,057
  • 5
  • 57
  • 81

2 Answers2

7
  1. No. We cannot have guaranteed copy elision from a function parameter. If you think about this from an implementation perspective - your argument w has to exist in a register or an address somewhere, so it cannot then be first constructed into the return slot.

  2. Somewhat moot, given (1).

  3. You should never†‡ write return std::move(w); because that actually would inhibit copy elision in the happy cases:

    Widget frobnicate() {
        Widget w;
        return std::move(w); // oops
    }
    

Except there are actually a few unfortunate places in C++17 where you actually need to write std::move because there are places where objects aren't automatically moved-from for you. See P0527 and P1155 for several examples (the OP case - returning a function parameter - is not one of these; that one will be implicitly moved from even in C++17). C++20 will implicitly move from all of these situations as well, so then you really should never write return std::move(w);

Of course, unless w isn't a function parameter or local variable with automatic storage duration. Then nothing in the language could safely assume that it could move it on your behalf, so you have to do so explicitly.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • [cppreference](https://en.cppreference.com/w/cpp/language/copy_elision) agrees with you: *"[NRVO is allowed] In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter ..."*, and *"if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue"*. – HolyBlackCat Dec 10 '19 at 20:19
  • The very first example of P0527 is more or less what I'm after. – dalle Dec 11 '19 at 07:24
2

Copy ellision will only happen for variables instantiated in the method. Thar is due to how copy ellision. The caller will make space for the return value when it calls the callee. But in order to use that space, the callee will have to create a variable using this space in its definition (theoretically it maybe could direct the copy that is made in the paramter (since it is passed by value) to this space vut compilers aren't that good yet)) Source: A cppcon talk about copy ellision.

That a move constructor exists will not give you copy ellision, but if copy ellision is impossible, the compiler will first try to move and then to copy if move is impossible. So the existence of a move constructor will probably improve the speed if there is no copy ellision.

You should never return a temporary (i.e. a variable going out of scope at the end of the function) by std::move since it prevents copy ellision and even if the copy ellision is not possible, the compiler will move by default. The only reason (I can think of) to return by mkve is, if you are releasing a resource the object held before the call. For example std::unique_ptr::release should return by move, iirc.

n314159
  • 4,990
  • 1
  • 5
  • 20