I’m puzzled as to how this code compiles: (https://godbolt.org/z/hjY4ca4rr)
class A
{
public:
A() = default;
A(A &&other) = default;
int x = 0;
};
A f(A a)
{
a.x = 1;
return a;
}
int main()
{
f(A());
return 0;
}
In principle, returning a from f() means copying it to the call-site location, but an A is not copy-able. NRVO copy elision may kick in but:
- NRVO is optional, so maybe the code isn’t portable (i.e. a compiler that does not employ NRVO will not be able to compile this). This is problematic because if I return std::move(a) to avoid dependency on NRVO, some compilers complain that I screw NRVO.
- I don’t understand how copy elision works here. Copy elision is based on the premise that the compiler can construct the local variable at a place of its choosing, so it constructs it at the call site location. But here the local is also a function parameter, so the location of it is pre-determined by ABI.
Anyone cares to clarify?
2
As Jarod pointed out, it’s not doing a copy, but a move, since the compiler knows the variable won’t be used afterwards
So it’s using the A(A &&other) constructor
To verify it, you can just print out something in the constructor
Also, if you want to disable copy, I recommend you to explicitly write it (and follow the rule of five).
That leads us to this code:
#include <iostream>
class A
{
public:
A() = default;
A(A &&other)
{
std::cout << "A(A&&)" << std::endl;
}
A(const A&other) = delete;
A& operator=(const A &other) = delete;
A& operator=(A &&other)
{
std::cout << "A& operator=(A &&)" << std::endl;
return *this;
}
~A() = default;
int x = 0;
};
A f(A a)
{
a.x = 1;
return a;
}
int main()
{
f(A());
return 0;
}
Output:
A(A&&)
So it is definitely calling the move constructor.