Can returning a braced enclosed initializer lead to a copy in C++?

c++, c++11, initializer-list, return-value-optimization

Solution

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.

Problem

Example: ``` struct s { int a; }; s func() { return {42}; } int main() { s new_obj = func(); // line 6 (void) new_obj; return 0; } ``` This works. Now, what happens, if we assume that our compiler does no RVO? - `func` returns a struct of `s`, so `{42}` must be converted to `s`, is then returned and finally copied to `new_obj` in line 6. - `func` returns an initializer list, so a deep copy is impossible. What does the language say? Can you give a proof? Note: I know that this does not seem useful in this example, but for returning very large, constant sized `std::array`s, I do not want to rely on RVO.

Original source

Related problems