Search code examples
c++c++17copy-elisionreturn-value-optimization

Why doesn't RVO happen with structured bindings when returning a pair from a function using std::make_pair?


Consider this code, which defines a simple struct Test (with a default constructor and copy constructor) and returns a std::pair <Test, Test> from a function.

#include <iostream>
#include <utility>

using namespace std;

struct Test {
    Test() {}
    Test(const Test &other) {cout << "Test copy constructor called\n";}
};

auto func() {
    return make_pair(Test(), Test());
}

int main()
{
    auto [a, b] = func(); // Expectation: copies for a and b are both elided

    return 0;
}

Surprisingly, the output is

Test copy constructor called
Test copy constructor called

Whereas modifying func to

auto func() {
    return Test();
}

int main()
{
    Test a(func());

    return 0;
}

Results in the copy constructor not being called. My g++ version is 11.2.0, so I thought that copy elision was guaranteed in this case, but I could be wrong. Could someone confirm if I am misunderstanding RVO?


Solution

  • std::make_pair is a function that takes the arguments by reference. Therefore temporaries are created from the two Test() arguments and std::make_pair constructs a std::pair from these, which requires copy-constructing the pair elements from the arguments. (Move-constructing is impossible since your manual definition of the copy constructor inhibits the implicit move constructor.)

    This has nothing to do with structured bindings or RVO or anything else besides std::make_pair.

    Because std::pair is not an aggregate class, you cannot solve this by simply constructing the std::pair directly from the two arguments either. In order to have a std::pair construct the elements in-place from an argument list you need to use its std::piecewise_construct overload:

    auto func() {
        return std::pair<Test, Test>(std::piecewise_construct, std::forward_as_tuple(), std::forward_as_tuple());
    }