understanding copy-initialisation and implicit conversions

  • A+

I am having trouble understanding why the following copy-initialisation 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 the second statement works and the last doesn't? Aren't non-explicit constructors considered when copy-initialising? 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 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.


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) {}  }; 

And lets change test to

struct test {     test(foo){} }; 

Then our main would be

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.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: