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:
Now NVRO works by allocating memory for the object to be returned at the call site, and then doing, essentially, a placement
newwhen the object is constructed infoo.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
s1ors2, depending on what's passed inb.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.