understanding copy-initialisation and implicit conversions

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
10
down vote

favorite
1












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










share|improve this question

























    up vote
    10
    down vote

    favorite
    1












    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










    share|improve this question























      up vote
      10
      down vote

      favorite
      1









      up vote
      10
      down vote

      favorite
      1






      1





      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










      share|improve this question













      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






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked 23 mins ago









      linuxfever

      1,89511024




      1,89511024






















          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.






          share|improve this answer






















          • 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


















          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.






          share|improve this answer



























            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.





            share




















              Your Answer






              StackExchange.ifUsing("editor", function ()
              StackExchange.using("externalEditor", function ()
              StackExchange.using("snippets", function ()
              StackExchange.snippets.init();
              );
              );
              , "code-snippets");

              StackExchange.ready(function()
              var channelOptions =
              tags: "".split(" "),
              id: "1"
              ;
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function()
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled)
              StackExchange.using("snippets", function()
              createEditor();
              );

              else
              createEditor();

              );

              function createEditor()
              StackExchange.prepareEditor(
              heartbeatType: 'answer',
              convertImagesToLinks: true,
              noModals: false,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: 10,
              bindNavPrevention: true,
              postfix: "",
              imageUploader:
              brandingHtml: "",
              contentPolicyHtml: "",
              allowUrls: true
              ,
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              );



              );













               

              draft saved


              draft discarded


















              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






























              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.






              share|improve this answer






















              • 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















              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.






              share|improve this answer






















              • 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













              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.






              share|improve this answer














              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.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited 7 mins ago

























              answered 12 mins ago









              NathanOliver

              80.2k15108165




              80.2k15108165











              • 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

















              • 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
















              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













              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.






              share|improve this answer
























                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.






                share|improve this answer






















                  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.






                  share|improve this answer












                  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.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered 14 mins ago









                  paler123

                  594214




                  594214




















                      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.





                      share
























                        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.





                        share






















                          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.





                          share












                          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.






                          share











                          share


                          share










                          answered 4 mins ago









                          StoryTeller

                          87.1k12174242




                          87.1k12174242



























                               

                              draft saved


                              draft discarded















































                               


                              draft saved


                              draft discarded














                              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













































































                              Comments

                              Popular posts from this blog

                              Long meetings (6-7 hours a day): Being “babysat” by supervisor

                              Is the Concept of Multiple Fantasy Races Scientifically Flawed? [closed]

                              Confectionery