8

I have following class

class widget {
// The methods only print their name, i.e. c'tor, destructor etc.
public:
    widget();
    widget(const widget&);
    widget(widget&&);
    ~widget();
    auto operator=(const widget&)  -> widget&;
    auto operator=(widget&&) -> widget&;
};

which I use in following code

#include "widget.h"

auto main() -> int {

    widget c(std::move(widget()));
    c = std::move(widget());

    return 0;
};

The resulting behavior is comprehensible to me. In the first call a widget is constructed, then the move constructor is invoked and the destructor is called on the temporary widget.

The second call does the same, expect for calling the move assignment operator instead of the move constructor. Leaving the main method, the destructor is invoked on c.


Now comes the interesting part:

#include "widget.h"

auto main() -> int {

    widget c((widget()));
    c = widget();

    return 0;
};

If I leave out the call to std::move, the first case stops working and results in just one constructor call. Whereas the second case is still working as before.

What am I missing here? Why are those two function calls treating their parameters differently? I tried this on gcc and clang.

mike
  • 4,929
  • 4
  • 40
  • 80
  • `(widghet())` is a prvalue, i.e. an rvalue, hence it is moved (if possible). `std::move` doesn't move, it just changes the value category of an expression. If that expression had the corresponding value category anyway, it's superfluous. – Columbo May 05 '15 at 16:03
  • 1
    The parentheses are necessary, otherwise the expression is evaluated as function pointer to a function that takes no parameters and returns a `widget`. – mike May 05 '15 at 16:06
  • 2
    Could it be your compiler is optimizing some constructions and copies away for you? – Maarten Bamelis May 05 '15 at 16:06
  • 2
    Try compiling with `-fno-elide-constructors`. Your copy is being elided (which can only work if `c` is being initialized by a temporary that isn't designated by a reference). – David G May 05 '15 at 16:08

1 Answers1

12

widget() is a pure rvalue (prvalue), so in the line

widget c((widget())); // use widget c{widget()} or c{widget{}} for more clear code

it will be moved. However, the compiler just performs copy/move elision. Compile with -fno-elide-constructors and you'll see the calls to the move constructors in all their glory.

Whenever you explicitly use std::move to move a prvalue, you're not allowing the compiler to perform the elision; that's why you see the move constructor in action in the first snippet. That's why it's almost always a bad idea to try to "help" the compiler by using a std::move as the return (unless you really want to return a rvalue reference).

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 2
    Yes, the extra parens are necessary. Otherwise, it is a function declaration, not an object definition. – Benjamin Lindley May 05 '15 at 16:17
  • @BenjaminLindley you're right, I edited it. For some reason I mistakenly thought `widget` is an object, not the name of the class. – vsoftco May 05 '15 at 16:28