Consider the following example:
#include <iostream>
struct foo {
foo(int) {}
foo(const foo&) { std::cout << "copy\n"; }
foo(foo&&) { std::cout << "move\n"; }
};
foo f() {
//return 42;
return { 42 };
}
int main() {
foo obj = f();
(void) obj;
}
When compiled with gcc 4.8.1 with -fno-elide-constructors
to prevent RVO the output is
move
If in f
the return statement without curly braces is used then, then the output is
move
move
With no RVO, what happens is the following. f
must create a temporary object of type foo
, let's call it ret
, to be returned.
If return { 42 };
is used, then ret
is direct initialized from the value 42
. So no copy/move constructor was called so far.
If return 42;
is used, then another temporary, let's call it tmp
is direct initialized from 42
and tmp
is moved to create ret
. Hence, one move constructor was called so far. (Notice that tmp
is an rvalue and foo
has a move constructor. If there was no move constructor, then the copy constructor would be called.)
Now ret
is an rvalue and is used to initialize obj
. Hence the move constuctor is called to move from ret
to obj
. (Again, in some circumstances, the copy constructor could be called instead.) Hence either one (for return { 42 };
) or two (for return 42;
) moves happen.
As I said in my comment to the OP's question, this post is very relevant:
construction helper make_XYZ allowing RVO and type deduction even if XZY has noncopy constraint. Especially the excelent answer by
R. Martinho Fernandes.