4

To understand the way how compiler selects constructor of a class, I wrote code following:

#include <iostream>

struct Widget
{
    Widget(Widget&& w){std::cout << "Move ctor" << std::endl;}
    Widget(void){std::cout << "Default ctor" << std::endl;}
    Widget(const Widget& w){std::cout << "Copy ctor" << std::endl;}
};

Widget make_widget(void) //helper function
{
    Widget w;
    return w;
}

int main(void)
{
    Widget w(make_widget());
}

According to item 25 of Effective Modern C++, compiler treats w as to be rvalue reference because of Return Value Optimization. So I expected Widget w(make_widget()) calls move constructor. but it doesn't. Furthermove, it prints only

Default

So I have no idea which version of constructor was called. Then I also tried to return rvalue explicitly. That is, return std::move(w) . Against my expectation considering above result, it correctly called move constructor, and printed

Default
Move

It seems I'm in the maze of rvalue. Please tell me what's going on there.

legends2k
  • 31,634
  • 25
  • 118
  • 222
darkspider
  • 136
  • 5

1 Answers1

3

According to item 25 of Effective Modern C++, compiler treats w as to be rvalue reference because of Return Value Optimization.

No, I am sure the author didn't mean that. RVO has no bearing on something being an rvalue reference or not. The w in both make_widget and main is an lvalue of type Widget. If you have a variable of type Widget&&, its value category is lvalue (since it has an identity) and its type is rvalue reference.

The Default you see is due to Widget w; inside make_widget(). Due to return value optimisation, the operation of copying this to the w in main is avoided, so you see nothing further. If you want to see the output without RVO, then pass -fno-elide-constructors to g++ if you use it (disabling RVO with VC++ isn't possible), you would see

Default (first creation)
Move    (creation of temporary with the move ctor)
Move    (copying of temporary to the one in main)

When you change return w to return std::move(w), the temporary created for returning is constructed with the rvalue, hence you see an additional Move in the output.

Community
  • 1
  • 1
legends2k
  • 31,634
  • 25
  • 118
  • 222
  • Does the "temporary" in context "creation of temporary with the move ctor" mean make_widget's return value? – darkspider Mar 02 '16 at 07:45
  • @BenjaminLindley Thanks for noticing this; confusing, yes. Fixed now! – legends2k Mar 02 '16 at 08:58
  • @darkspider The type of the [temporary](http://stackoverflow.com/q/4214153/183120) is the same as the function's return type, however, its value category is an rvalue since it doesn't have a name. An expression involving an rvalue which incurs an side effects on it is disallowed. It [can only be bound](http://stackoverflow.com/q/1565600/183120) to an rvalue reference or a const lvalue reference or copied to an lvalue. – legends2k Mar 02 '16 at 09:02
  • I finally understand that thanks to (N)RVO, this code is processed as if w in make_widget is directly constructed in w in main. In this way, two moves, which appear when RVO is disabled, are eliminated, and only "Default" is printed. Considering this behavior, to keep its portability, writing constructors which have any side-effect is not promoted because the outcome can change depending on whether optimization is on or off. I hope I don't misunderstand your whole explanation. – darkspider Mar 02 '16 at 10:49
  • @darkspider: One case where it's okay for constructors to have side effects is when those side effects are undone by the destructor. For example, with `std::shared_ptr`, the copy constructor has the side effect of incrementing the reference count of the stored object, and the destructor decrements it. – Benjamin Lindley Mar 02 '16 at 17:46