I started with this piece of code to show the effects of NRVO:
struct test_nrvo {
bool b;
long x[100]; // Too large to return in register
};
test_nrvo tester(test_nrvo* p) {
test_nrvo result{ p == &result };
return result;
}
bool does_nrvo() {
test_nrvo result(tester(&result));
return result.b;
}
int main() {
__builtin_printf("nrvo: %d\n", does_nrvo());
}
Which happily prints nrvo: 1
at -O0
on my compiler.
I tried to do a similar thing for RVO for returning a prvalue:
struct test_rvo {
bool b;
test_rvo(test_rvo* p) : b(p == this) {}
};
test_rvo tester(test_rvo* p) {
return test_rvo(p);
}
bool does_rvo() {
test_rvo result(tester(&result));
return result.b;
}
int main() {
__builtin_printf("rvo: %d\n", does_rvo());
}
https://godbolt.org/z/abh9v14bv (Both of these snippets with some logging)
And this prints rvo: 0
. This implies that p
and this
are different pointers, so there are at least two different objects (and it is moved from).
If I delete the move constructor or make it non-trivial, this prints rvo: 1
, so it seems like this elision is allowed.
Confusingly the constexpr version fails in GCC, even with a deleted move constructor (where there absolutely could not have been two different objects, so it seems like a GCC bug). Clang seems to just have different behaviour to the non-constexpr version, so it seems like this is permitted-but-not-mandatory copy elision. Is that right? If so, what makes a function call different from other prvalues that must have their moves elided?