understanding copy-initialisation and implicit conversions
Clash Royale CLAN TAG#URR8PPP
up vote
10
down vote
favorite
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
c++ c++17 implicit-conversion unique-ptr
add a comment |Â
up vote
10
down vote
favorite
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
c++ c++17 implicit-conversion unique-ptr
add a comment |Â
up vote
10
down vote
favorite
up vote
10
down vote
favorite
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
c++ c++17 implicit-conversion unique-ptr
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
c++ c++17 implicit-conversion unique-ptr
c++ c++17 implicit-conversion unique-ptr
asked 23 mins ago
linuxfever
1,89511024
1,89511024
add a comment |Â
add a comment |Â
3 Answers
3
active
oldest
votes
up vote
4
down vote
accepted
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 = teststd::move(pd);
which only uses a single implicit user defined like the first case does.
So, this isn't anything to do withunique_ptr
? Would we see the same problem with any other smart pointer?
â Tim Randall
5 mins ago
@TimRandall Kind of. If has to do with the types not being the same. See this example. Think I should add that to the answer?
â NathanOliver
2 mins ago
add a comment |Â
up vote
3
down vote
Pretty sure that only single implicit conversion is allowed to be considered by the compiler. In first case only conversion from std::unique_ptr<derived>&&
to std::unique_ptr<base>&&
is required, in the second case the base pointer would also need to be converted to test
(for default move constructor to work).
So for example converting the derived pointer to base: std::unique_ptr<base> bd = std::move(pd)
and then move assigning it would work as well.
add a comment |Â
up vote
2
down vote
The semantics of initializers are described in [dcl.init] ö17. The choice of direct initialization vs copy initialization takes us into one of two different bullets:
If the destination type is a (possibly cv-qualified) class type:
[...]
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated ([over.match.ctor]), and the best one is chosen through
overload resolution. The constructor so selected is called to
initialize the object, with the initializer expression or
expression-list as its argument(s). If no constructor applies, or the
overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in
[over.match.copy], and the best one is chosen through overload
resolution. If the conversion cannot be done or is ambiguous, the
initialization is ill-formed. The function selected is called with the
initializer expression as its argument; if the function is a
constructor, the call is a prvalue of the cv-unqualified version of
the destination type whose result object is initialized by the
constructor. The call is used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization.
In the direct initialization case, we enter the first quoted bullet. As detailed there, constructors are considered and enumerated directly. The implicit conversion sequence that is required is therefore only to convert unique_ptr<derived>
to a unique_ptr<base>
as a constructor argument.
In the copy initialization case, we are not directly considering constructors anymore, but rather trying to see which implicit conversion sequence is possible. The only one available is unique_ptr<derived>
to a unique_ptr<base>
to a test
. Since an implicit conversion sequence can contain only one user defined conversion, this is not allowed. As such the initialization is ill-formed.
One could say that using direct initialization sort of "bypasses" one implicit conversion.
add a comment |Â
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
4
down vote
accepted
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 = teststd::move(pd);
which only uses a single implicit user defined like the first case does.
So, this isn't anything to do withunique_ptr
? Would we see the same problem with any other smart pointer?
â Tim Randall
5 mins ago
@TimRandall Kind of. If has to do with the types not being the same. See this example. Think I should add that to the answer?
â NathanOliver
2 mins ago
add a comment |Â
up vote
4
down vote
accepted
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 = teststd::move(pd);
which only uses a single implicit user defined like the first case does.
So, this isn't anything to do withunique_ptr
? Would we see the same problem with any other smart pointer?
â Tim Randall
5 mins ago
@TimRandall Kind of. If has to do with the types not being the same. See this example. Think I should add that to the answer?
â NathanOliver
2 mins ago
add a comment |Â
up vote
4
down vote
accepted
up vote
4
down vote
accepted
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 = teststd::move(pd);
which only uses a single implicit user defined like the first case does.
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 = teststd::move(pd);
which only uses a single implicit user defined like the first case does.
edited 7 mins ago
answered 12 mins ago
NathanOliver
80.2k15108165
80.2k15108165
So, this isn't anything to do withunique_ptr
? Would we see the same problem with any other smart pointer?
â Tim Randall
5 mins ago
@TimRandall Kind of. If has to do with the types not being the same. See this example. Think I should add that to the answer?
â NathanOliver
2 mins ago
add a comment |Â
So, this isn't anything to do withunique_ptr
? Would we see the same problem with any other smart pointer?
â Tim Randall
5 mins ago
@TimRandall Kind of. If has to do with the types not being the same. See this example. Think I should add that to the answer?
â NathanOliver
2 mins ago
So, this isn't anything to do with
unique_ptr
? Would we see the same problem with any other smart pointer?â Tim Randall
5 mins ago
So, this isn't anything to do with
unique_ptr
? Would we see the same problem with any other smart pointer?â Tim Randall
5 mins ago
@TimRandall Kind of. If has to do with the types not being the same. See this example. Think I should add that to the answer?
â NathanOliver
2 mins ago
@TimRandall Kind of. If has to do with the types not being the same. See this example. Think I should add that to the answer?
â NathanOliver
2 mins ago
add a comment |Â
up vote
3
down vote
Pretty sure that only single implicit conversion is allowed to be considered by the compiler. In first case only conversion from std::unique_ptr<derived>&&
to std::unique_ptr<base>&&
is required, in the second case the base pointer would also need to be converted to test
(for default move constructor to work).
So for example converting the derived pointer to base: std::unique_ptr<base> bd = std::move(pd)
and then move assigning it would work as well.
add a comment |Â
up vote
3
down vote
Pretty sure that only single implicit conversion is allowed to be considered by the compiler. In first case only conversion from std::unique_ptr<derived>&&
to std::unique_ptr<base>&&
is required, in the second case the base pointer would also need to be converted to test
(for default move constructor to work).
So for example converting the derived pointer to base: std::unique_ptr<base> bd = std::move(pd)
and then move assigning it would work as well.
add a comment |Â
up vote
3
down vote
up vote
3
down vote
Pretty sure that only single implicit conversion is allowed to be considered by the compiler. In first case only conversion from std::unique_ptr<derived>&&
to std::unique_ptr<base>&&
is required, in the second case the base pointer would also need to be converted to test
(for default move constructor to work).
So for example converting the derived pointer to base: std::unique_ptr<base> bd = std::move(pd)
and then move assigning it would work as well.
Pretty sure that only single implicit conversion is allowed to be considered by the compiler. In first case only conversion from std::unique_ptr<derived>&&
to std::unique_ptr<base>&&
is required, in the second case the base pointer would also need to be converted to test
(for default move constructor to work).
So for example converting the derived pointer to base: std::unique_ptr<base> bd = std::move(pd)
and then move assigning it would work as well.
answered 14 mins ago
paler123
594214
594214
add a comment |Â
add a comment |Â
up vote
2
down vote
The semantics of initializers are described in [dcl.init] ö17. The choice of direct initialization vs copy initialization takes us into one of two different bullets:
If the destination type is a (possibly cv-qualified) class type:
[...]
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated ([over.match.ctor]), and the best one is chosen through
overload resolution. The constructor so selected is called to
initialize the object, with the initializer expression or
expression-list as its argument(s). If no constructor applies, or the
overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in
[over.match.copy], and the best one is chosen through overload
resolution. If the conversion cannot be done or is ambiguous, the
initialization is ill-formed. The function selected is called with the
initializer expression as its argument; if the function is a
constructor, the call is a prvalue of the cv-unqualified version of
the destination type whose result object is initialized by the
constructor. The call is used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization.
In the direct initialization case, we enter the first quoted bullet. As detailed there, constructors are considered and enumerated directly. The implicit conversion sequence that is required is therefore only to convert unique_ptr<derived>
to a unique_ptr<base>
as a constructor argument.
In the copy initialization case, we are not directly considering constructors anymore, but rather trying to see which implicit conversion sequence is possible. The only one available is unique_ptr<derived>
to a unique_ptr<base>
to a test
. Since an implicit conversion sequence can contain only one user defined conversion, this is not allowed. As such the initialization is ill-formed.
One could say that using direct initialization sort of "bypasses" one implicit conversion.
add a comment |Â
up vote
2
down vote
The semantics of initializers are described in [dcl.init] ö17. The choice of direct initialization vs copy initialization takes us into one of two different bullets:
If the destination type is a (possibly cv-qualified) class type:
[...]
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated ([over.match.ctor]), and the best one is chosen through
overload resolution. The constructor so selected is called to
initialize the object, with the initializer expression or
expression-list as its argument(s). If no constructor applies, or the
overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in
[over.match.copy], and the best one is chosen through overload
resolution. If the conversion cannot be done or is ambiguous, the
initialization is ill-formed. The function selected is called with the
initializer expression as its argument; if the function is a
constructor, the call is a prvalue of the cv-unqualified version of
the destination type whose result object is initialized by the
constructor. The call is used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization.
In the direct initialization case, we enter the first quoted bullet. As detailed there, constructors are considered and enumerated directly. The implicit conversion sequence that is required is therefore only to convert unique_ptr<derived>
to a unique_ptr<base>
as a constructor argument.
In the copy initialization case, we are not directly considering constructors anymore, but rather trying to see which implicit conversion sequence is possible. The only one available is unique_ptr<derived>
to a unique_ptr<base>
to a test
. Since an implicit conversion sequence can contain only one user defined conversion, this is not allowed. As such the initialization is ill-formed.
One could say that using direct initialization sort of "bypasses" one implicit conversion.
add a comment |Â
up vote
2
down vote
up vote
2
down vote
The semantics of initializers are described in [dcl.init] ö17. The choice of direct initialization vs copy initialization takes us into one of two different bullets:
If the destination type is a (possibly cv-qualified) class type:
[...]
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated ([over.match.ctor]), and the best one is chosen through
overload resolution. The constructor so selected is called to
initialize the object, with the initializer expression or
expression-list as its argument(s). If no constructor applies, or the
overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in
[over.match.copy], and the best one is chosen through overload
resolution. If the conversion cannot be done or is ambiguous, the
initialization is ill-formed. The function selected is called with the
initializer expression as its argument; if the function is a
constructor, the call is a prvalue of the cv-unqualified version of
the destination type whose result object is initialized by the
constructor. The call is used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization.
In the direct initialization case, we enter the first quoted bullet. As detailed there, constructors are considered and enumerated directly. The implicit conversion sequence that is required is therefore only to convert unique_ptr<derived>
to a unique_ptr<base>
as a constructor argument.
In the copy initialization case, we are not directly considering constructors anymore, but rather trying to see which implicit conversion sequence is possible. The only one available is unique_ptr<derived>
to a unique_ptr<base>
to a test
. Since an implicit conversion sequence can contain only one user defined conversion, this is not allowed. As such the initialization is ill-formed.
One could say that using direct initialization sort of "bypasses" one implicit conversion.
The semantics of initializers are described in [dcl.init] ö17. The choice of direct initialization vs copy initialization takes us into one of two different bullets:
If the destination type is a (possibly cv-qualified) class type:
[...]
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated ([over.match.ctor]), and the best one is chosen through
overload resolution. The constructor so selected is called to
initialize the object, with the initializer expression or
expression-list as its argument(s). If no constructor applies, or the
overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in
[over.match.copy], and the best one is chosen through overload
resolution. If the conversion cannot be done or is ambiguous, the
initialization is ill-formed. The function selected is called with the
initializer expression as its argument; if the function is a
constructor, the call is a prvalue of the cv-unqualified version of
the destination type whose result object is initialized by the
constructor. The call is used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization.
In the direct initialization case, we enter the first quoted bullet. As detailed there, constructors are considered and enumerated directly. The implicit conversion sequence that is required is therefore only to convert unique_ptr<derived>
to a unique_ptr<base>
as a constructor argument.
In the copy initialization case, we are not directly considering constructors anymore, but rather trying to see which implicit conversion sequence is possible. The only one available is unique_ptr<derived>
to a unique_ptr<base>
to a test
. Since an implicit conversion sequence can contain only one user defined conversion, this is not allowed. As such the initialization is ill-formed.
One could say that using direct initialization sort of "bypasses" one implicit conversion.
answered 4 mins ago
StoryTeller
87.1k12174242
87.1k12174242
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53085773%2funderstanding-copy-initialisation-and-implicit-conversions%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password