Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding copy-initialisation and implicit conversions

Tags:

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.

like image 971
linuxfever Avatar asked Oct 31 '18 14:10

linuxfever


Video Answer


1 Answers

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.

like image 162
NathanOliver Avatar answered Oct 22 '22 06:10

NathanOliver