c++c++11c++17rvostructured-bindings

Why Structured Bindings disable both RVO and move on return statement?


Suppose we have a class named AAA that supports both copy/move:

class AAA
{
public:
    AAA() = default;
    ~AAA() = default;

    AAA(const AAA& rhs)
    {
       std::cout << "Copy constructor" << std::endl;
    }

    AAA(AAA&& rhs)
    {
       std::cout << "Move constructor" << std::endl;
    }
};

In the following code, get_val returns second:

AAA get_val()
{
    auto [ first, second ]  = std::make_tuple(AAA{}, AAA{});

    std::cout << "Returning - " << std::endl;
    return second;
}

auto obj = get_val();
std::cout << "Returned - " << std::endl;

Now second is copied, printing the following output:

...
Returning - 
Copy constructor 
Returned -

This is unfortunate, because my expectation of the result is either there's no call to copy constructor, or at least it's implicitly moved.

To avoid copying, I'll have to explicitly apply std::move on it.

return std::move(second);

Then I'd receive the result of:

...
Returning - 
Move constructor 
Returned - 

I assume that the reason RVO is not performed is that probably the compilers would see second as a reference instead, while get_val returns prvalue.

However why can implicit move NOT be expected either? Using explicit std::move on the return statement does not look intuitive in this particular case, because you generally don't want to make RVO, which is in most cases a better optimization than move, accidentally gone away.

Tested by both compilers gcc and clang with -O3.

Live Demo


Solution

  • However why can implicit move NOT be expected either?

    For the exact same reason as elision is turned off: because it's a reference, not the name of an independent object. Every use of second is essentially equivalent to saying obj.whatever or get<1>(obj) (though in the latter case, we store the reference). And there's no implicit move from either of those expressions.

    Structured binding is for accessing subobjects of the given object. You cannot elide returns of subobjects, nor can you implicitly move from them. Therefore, you cannot elide structured binding names nor implicitly move from them.