I am having trouble understanding why the following copy-initialization doesn't compile:
#include <memory>
struct base{};
struct derived : base{};
struct test
{
test(std::unique_ptr<base>){}
};
int main()
{
auto pd = std::make_unique<derived>();
//test t(std::move(pd)); // this works;
test t = std::move(pd); // this doesn't
}
A unique_ptr<derived>
can be moved into a unique_ptr<base>
, so why does the second statement work but the last does not? Are non-explicit constructors not considered when performing a copy-initialization?
The error from gcc-8.2.0 is:
conversion from 'std::remove_reference<std::unique_ptr<derived, std::default_delete<derived> >&>::type'
{aka 'std::unique_ptr<derived, std::default_delete<derived> >'} to non-scalar type 'test' requested
and from clang-7.0.0 is
candidate constructor not viable: no known conversion from 'unique_ptr<derived, default_delete<derived>>'
to 'unique_ptr<base, default_delete<base>>' for 1st argument
Live code is available here.
A std::unique_ptr<base>
is not the same type as a std::unique_ptr<derived>
. When you do
test t(std::move(pd));
You call std::unique_ptr<base>
's conversion constructor to convert pd
into a std::unique_ptr<base>
. This is fine as you are allowed a single user defined conversion.
In
test t = std::move(pd);
You are doing copy initialization so so you need to convert pd
into a test
. That requires 2 user defined conversions though and you can't do that. You first have to convert pd
to a std::unique_ptr<base>
and then you need to convert it to a test
. It's not very intuitive but when you have
type name = something;
whatever something
is needs to be only a single user defined conversion from the source type. In your case that means you need
test t = test{std::move(pd)};
which only uses a single implicit user defined like the first case does.
Lets remove the std::unique_ptr
and look at in a general case. Since std::unique_ptr<base>
is not the same type as a std::unique_ptr<derived>
we essentially have
struct bar {};
struct foo
{
foo(bar) {}
};
struct test
{
test(foo){}
};
int main()
{
test t = bar{};
}
and we get the same error because we need to go from bar -> foo -> test
and that has one user defined conversion too many.