I tried the following on gcc 13.1 on C++ on C++11/17/20/23 but it fails to compile when the move or copy constructor is deleted.
If those constructors are not deleted, then named return value optimization works, and neither copy/move are done.
Interestingly enough, if I remove the name, and return the prvalue directly then the plain return value optimization works.
Can anyone provide an explanation for this?
#include <memory>
#include <iostream>
struct Foo{
Foo(int v): a{v} { std::cout << "Create!\n"; }
~Foo() { std::cout << "Destruct!\n"; }
Foo(const Foo&)=delete;
Foo(Foo&&)=delete;
int a;
};
// I DON'T WORK!
Foo makeFoo() {
Foo foo{5};
return foo;
}
// I WORK!
//Foo makeFoo() {
// return Foo{5};
//}
int main() {
auto foo = makeFoo();
std::cout << "Hello world! " << foo.a << "\n";
}
Although copy elision is indeed mandatory from C++17 on, there's a reason why you must still provide a move constructor when returning a named object from a function by value.
This is because it is possible to write code where NVRO is not possible. Here's a simple example:
std::string foo (bool b)
{
std::string s1 = "hello";
std::string s2 = "world";
return (b) ? s1 : s2;
}
Now NVRO works by allocating memory for the object to be returned at the call site, and then doing, essentially, a placement new
when the object is constructed in foo
.
But with the code above the compiler can't do that (not in the general case anyway) because there are two possible objects it might return, and it doesn't know, ahead of time, which one it will be. So it is forced to move-construct the returned string from either s1
or s2
, depending on what's passed in b
.
Now you could argue that in code where the compiler doesn't face this problem, NVRO should not require a viable move constructor. But the committee evidently decided that what is and is not permissible would then be too confusing, and I agree with them. Let's face it, life is hard enough for compiler writers already.