I expected to see copy elision from Named Return Value Optimization (NRVO) from this test program but its output is "Addresses do not match!" so NRVO didn't happen. Why is this?
// test.cpp
// Compile using:
// g++ -Wall -std=c++17 -o test test.cpp
#include <string>
#include <iostream>
void *addr = NULL;
class A
{
public:
int i;
int j;
#if 0
~A() {}
#endif
};
A fn()
{
A fn_a;
addr = &fn_a;
return fn_a;
}
int main()
{
A a = fn();
if (addr == &a)
std::cout << "Addresses match!\n";
else
std::cout << "Addresses do not match!\n";
}
Notes:
If a destructor is defined by enabling the
#if
above, then the NRVO does happen (and it also happens in some other cases such as defining a virtual method or adding astd::string
member).No methods have been defined so A is a POD struct, or in more recent terminology a trivial class. I don't see an explicit exclusion for this in the above links.
Adding compiler optimisation (to a more complicated example that doesn't just reduce to the empty program!) doesn't make any difference.
Looking at the assembly for a second example shows that this even happens when I would expect mandatory Return Value Optimization (RVO), so the NRVO above was not prevented by taking the address of
fn_a
infn()
. Clang, GCC, ICC and MSVC on x86-64 show the same behaviour suggesting this behaviour is intentional and not a bug in a specific compiler.class A { public: int i; int j; #if 0 ~A() {} #endif }; A fn() { return A(); } int main() { // Where NRVO occurs the call to fn() is preceded on x86-64 by a move // to RDI, otherwise it is followed by a move from RAX. A a = fn(); }
The language rule which allows this in case of returning a prvalue (the second example) is:
The motivation for the rule is explained in the note of the quoted rule. Essentially, RVO is sometimes less efficient than no RVO.
In the second case, this is explained by the rule because creating the temporary is only allowed when the destructor is trivial.
In the NRVO case, I suppose this is up to the language implementation.