The first example:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&) = delete;
A(A&&) = default;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
It works perfectly. So here the MOVE constructor is used.
Let's remove the move constructor and add a copy one:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&a)
: ref( a.ref.get() ? new int(*a.ref) : nullptr )
{ }
A(A&&) = delete;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
Now the compilation falls with the error "use of deleted function ‘A::A(A&&)’"
So the MOVE constructor is REQUIRED and there is no fall back to a COPY constructor.
Now let's remove both copy- and move-constructors:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
And it falls with "use of deleted function ‘A::A(const A&)’" compilation error. Now it REQUIRES a COPY constructor!
So there was a fallback (?) from the move constructor to the copy constructor.
Why? Does anyone have any idea how does it conform to the C++ standard and what actually is the rule of choosing among copy/move constructors?
The function that it's going to use is chosen before it checks if it is
delete
d or not. In the case that the copy constructor was available and the move constructor wasdelete
d, the move constructor was still the best choice out of the two. Then it sees that it'sdelete
d and gives you an error.If you had the same example but actually removed the move constructor, rather than making it
delete
d, you'll see that it compiles fine and does fall back to use the copy constructor:This class does not have a move constructor declared for it at all (not even implicitly), therefore it cannot be chosen.
There is no "fallback". It is called overload resolution. If there are more than one possible candidate in overload resolution then the best match is chosen, according to a complicated set of rules which you can find by reading the C++ standard or a draft of it.
Here is an example without constructors.
In overload resolution, binding rvalue to rvalue has higher preference than lvalue to rvalue.
Here is a very similar example:
In overload resolution, an exact match is preferred to a conversion.
In your three examples on this thread we have:
1: Two candidate functions; rvalue prefers rvalue (as in my first example)
2: Two candidate functions; rvalue prefers rvalue (as in my first example)
3: One candidate function; no contest
As explained earlier, there is no implicit declaration of A(A&&) in case 3 because you have a destructor.
For overload resolution it does not matter whether the function body exists or not, it is whether the function is declared (either explicitly or implicitly).