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?
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());
}