{"id":107443,"date":"2022-11-23T07:00:00","date_gmt":"2022-11-23T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107443"},"modified":"2024-01-02T21:03:59","modified_gmt":"2024-01-03T05:03:59","slug":"20221123-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221123-00\/?p=107443","title":{"rendered":"Sometimes perfect forwarding can be too perfect: Lazy conversion is lazy"},"content":{"rendered":"<p>C++11 introduced the concept of <i>perfect forwarding<\/i>, which uses <span style=\"text-decoration: line-through;\"><i>universal references<\/i><\/span> <i>forwarding references<\/i> to capture the value category of a passed-in parameter, so that it can be passed to another function as the same kind of reference.<\/p>\n<p>Forwarding references are used in many places, but the trap I&#8217;m looking at today is when the forwarding reference causes a conversion to be delayed to a point where it is no longer possible.<\/p>\n<pre>class Base\r\n{\r\n    \/* ... *\/\r\n};\r\n\r\nclass BaseAcceptor\r\n{\r\npublic:\r\n    BaseAcceptor(Base* base);\r\n    \/* ... *\/\r\n};\r\n\r\nclass Derived : Base\r\n{\r\n    void DoSomething()\r\n    {\r\n        auto p1 = std::unique_ptr&lt;BaseAcceptor&gt;(new BaseAcceptor(this));\r\n        auto p2 = std::shared_ptr&lt;BaseAcceptor&gt;(new BaseAcceptor(this));\r\n\r\n        std::vector&lt;BaseAcceptor&gt; v;\r\n        v.push_back(BaseAcceptor(this));\r\n    }\r\n\r\n    \/* ... *\/\r\n};\r\n<\/pre>\n<p>But then you realize that you should be using the <code>make_<\/code> functions, so that you can <a href=\"https:\/\/isocpp.org\/blog\/2019\/06\/quick-q-differences-between-stdmake-unique-and-stdunique-ptr-with-new\"> avoid having to write the <code>new<\/code> keyword<\/a>, and you can use the <code>emplace<\/code> alternative to construct the vector element in place.<\/p>\n<pre>class Derived : Base\r\n{\r\n    void DoSomething()\r\n    {\r\n        auto p1 = std::make_unique&lt;BaseAcceptor&gt;(this);\r\n        auto p2 = std::make_shared&lt;BaseAcceptor&gt;(this);\r\n\r\n        std::vector&lt;BaseAcceptor&gt; v;\r\n        v.emplace_back(this);\r\n    }\r\n};\r\n<\/pre>\n<p>And then everything explodes into tiny little pieces.<\/p>\n<pre style=\"white-space: pre-wrap;\">\/\/ clang\r\n\r\nIn file included from memory:76:\r\nunique_ptr.h:1072:38: error: cannot cast 'Derived' to its private base class 'Base'\r\n    { return unique_ptr&lt;_Tp&gt;(new _Tp(std::forward&lt;_Args&gt;(__args)...)); }\r\n                                     ^\r\ntest.cpp:19:24: note: in instantiation of function template specialization 'std::make_unique&lt;BaseAcceptor, Derived *&gt;' requested here\r\n        auto p1 = std::make_unique&lt;BaseAcceptor&gt;(this);\r\n                       ^\r\ntest.cpp:14:17: note: implicitly declared private here\r\nclass Derived : Base\r\n                ^~~~\r\n\r\nIn file included from vector:62:\r\nstl_construct.h:119:29: error: cannot cast 'Derived' to its private base class 'Base'\r\n      ::new((void*)__p) _Tp(std::forward&lt;_Args&gt;(__args)...);\r\n                            ^\r\nalloc_traits.h:635:9: note: in instantiation of function template specialization 'std::_Construct&lt;BaseAcceptor, Derived *&gt;' requested here\r\n        { std::_Construct(__p, std::forward&lt;_Args&gt;(__args)...); }\r\n               ^\r\nshared_ptr_base.h:604:30: note: in instantiation of function template specialization 'std::allocator_traits&lt;std::allocator&lt;void&gt;&gt;::construct&lt;BaseAcceptor, Derived *&gt;' requested here\r\n          allocator_traits&lt;_Alloc&gt;::construct(__a, _M_ptr(),\r\n                                    ^\r\nshared_ptr_base.h:972:6: note: in instantiation of function template specialization 'std::_Sp_counted_ptr_inplace&lt;BaseAcceptor, std::allocator&lt;void&gt;, __gnu_cxx::_S_atomic&gt;::_Sp_counted_ptr_inplace&lt;Derived *&gt;' requested here\r\n            _Sp_cp_type(__a._M_a, std::forward&lt;_Args&gt;(__args)...);\r\n            ^\r\nshared_ptr_base.h:1712:14: note: in instantiation of function template specialization 'std::__shared_count&lt;__gnu_cxx::_S_atomic&gt;::__shared_count&lt;BaseAcceptor, std::allocator&lt;void&gt;, Derived *&gt;' requested here\r\n        : _M_ptr(), _M_refcount(_M_ptr, __tag, std::forward&lt;_Args&gt;(__args)...)\r\n                    ^\r\nshared_ptr.h:464:4: note: in instantiation of function template specialization 'std::__shared_ptr&lt;BaseAcceptor, __gnu_cxx::_S_atomic&gt;::__shared_ptr&lt;std::allocator&lt;void&gt;, Derived *&gt;' requested here\r\n        : __shared_ptr&lt;_Tp&gt;(__tag, std::forward&lt;_Args&gt;(__args)...)\r\n          ^\r\nshared_ptr.h:1009:14: note: in instantiation of function template specialization 'std::shared_ptr&lt;BaseAcceptor&gt;::shared_ptr&lt;std::allocator&lt;void&gt;, Derived *&gt;' requested here\r\n      return shared_ptr&lt;_Tp&gt;(_Sp_alloc_shared_tag&lt;_Alloc&gt;{__a},\r\n             ^\r\ntest.cpp:20:24: note: in instantiation of function template specialization 'std::make_shared&lt;BaseAcceptor, Derived *&gt;' requested here\r\n        auto p2 = std::make_shared&lt;BaseAcceptor&gt;(this);\r\n                       ^\r\ntest.cpp:14:17: note: implicitly declared private here\r\nclass Derived : Base\r\n                ^~~~\r\n\r\nIn file included from vector:61:\r\nIn file included from allocator.h:46:\r\nIn file included from c++allocator.h:33:\r\nnew_allocator.h:182:27: error: cannot cast 'Derived' to its private base class 'Base'\r\n        { ::new((void *)__p) _Up(std::forward&lt;_Args&gt;(__args)...); }\r\n                                 ^\r\nalloc_traits.h:516:8: note: in instantiation of function template specialization 'std::__new_allocator&lt;BaseAcceptor&gt;::construct&lt;BaseAcceptor, Derived *&gt;' requested here\r\n          __a.construct(__p, std::forward&lt;_Args&gt;(__args)...);\r\n              ^\r\nvector.tcc:117:21: note: in instantiation of function template specialization 'std::allocator_traits&lt;std::allocator&lt;BaseAcceptor&gt;&gt;::construct&lt;BaseAcceptor, Derived *&gt;' requested here\r\n            _Alloc_traits::construct(this-&gt;_M_impl, this-&gt;_M_impl._M_finish,\r\n                           ^\r\ntest.cpp:23:11: note: in instantiation of function template specialization 'std::vector&lt;BaseAcceptor&gt;::emplace_back&lt;Derived *&gt;' requested here\r\n        v.emplace_back(this);\r\n          ^\r\ntest.cpp:14:17: note: implicitly declared private here\r\nclass Derived : Base\r\n                ^~~~\r\n\r\n\/\/ gcc\r\nIn file included from memory:76,\r\n                 from test.cpp:2:\r\ntest.cpp std::make_unique(_Args&amp;&amp; ...) [with _Tp = BaseAcceptor; _Args = {Derived*}; __detail::__unique_ptr_t&lt;_Tp&gt; = __detail::__unique_ptr_t&lt;BaseAcceptor&gt;]':\r\ntest.cpp:19:49:   required from here\r\nunique_ptr.h:1072:30: error: 'Base' is an inaccessible base of 'Derived'\r\n 1072 |     { return unique_ptr&lt;_Tp&gt;(new _Tp(std::forward&lt;_Args&gt;(__args)...)); }\r\n      |                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\nIn file included from c++allocator.h:33,\r\n                 from allocator.h:46,\r\n                 from vector:61,\r\n                 from test.cpp:1:\r\nnew_allocator.h: In instantiation of 'void std::__new_allocator&lt;_Tp&gt;::construct(_Up*, _Args&amp;&amp; ...) [with _Up = BaseAcceptor; _Args = {Derived*}; _Tp = BaseAcceptor]':\r\nalloc_traits.h:516:17:   required from 'static void std::allocator_traits&lt;std::allocator&lt;_Tp1&gt; &gt;::construct(allocator_type&amp;, _Up*, _Args&amp;&amp; ...) [with _Up = BaseAcceptor; _Args = {Derived*}; _Tp = BaseAcceptor; allocator_type = std::allocator&lt;BaseAcceptor&gt;]'\r\nvector.tcc:117:30:   required from 'std::vector&lt;_Tp, _Alloc&gt;::reference std::vector&lt;_Tp, _Alloc&gt;::emplace_back(_Args&amp;&amp; ...) [with _Args = {Derived*}; _Tp = BaseAcceptor; _Alloc = std::allocator&lt;BaseAcceptor&gt;; reference = BaseAcceptor&amp;]'\r\nrequired from here\r\nnew_allocator.h:182:11: error: 'Base' is an inaccessible base of 'Derived'\r\n  182 |         { ::new((void *)__p) _Up(std::forward&lt;_Args&gt;(__args)...); }\r\n      |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\nIn file included from vector:62:\r\nstl_construct.h: In instantiation of 'void std::_Construct(_Tp*, _Args&amp;&amp; ...) [with _Tp = BaseAcceptor; _Args = {Derived*}]':\r\nalloc_traits.h:635:19:   required from 'static void std::allocator_traits&lt;std::allocator&lt;void&gt; &gt;::construct(allocator_type&amp;, _Up*, _Args&amp;&amp; ...) [with _Up = BaseAcceptor; _Args = {Derived*}; allocator_type = std::allocator&lt;void&gt;]'\r\nshared_ptr_base.h:604:39:   required from 'std::_Sp_counted_ptr_inplace&lt;_Tp, _Alloc, _Lp&gt;::_Sp_counted_ptr_inplace(_Alloc, _Args&amp;&amp; ...) [with _Args = {Derived*}; _Tp = BaseAcceptor; _Alloc = std::allocator&lt;void&gt;; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:971:16:   required from 'std::__shared_count&lt;_Lp&gt;::__shared_count(_Tp*&amp;, std::_Sp_alloc_shared_tag&lt;_Alloc&gt;, _Args&amp;&amp; ...) [with _Tp = BaseAcceptor; _Alloc = std::allocator&lt;void&gt;; _Args = {Derived*}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:1712:14:   required from 'std::__shared_ptr&lt;_Tp, _Lp&gt;::__shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {Derived*}; _Tp = BaseAcceptor; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr.h:464:59:   required from 'std::shared_ptr&lt;_Tp&gt;::shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {Derived*}; _Tp = BaseAcceptor]'\r\nshared_ptr.h:1009:14:   required from 'std::shared_ptr&lt;typename std::enable_if&lt;(! std::is_array&lt; &lt;template-parameter-1-1&gt; &gt;::value), _Tp&gt;::type&gt; std::make_shared(_Args&amp;&amp; ...) [with _Tp = BaseAcceptor; _Args = {Derived*}; typename enable_if&lt;(! is_array&lt; &lt;template-parameter-1-1&gt; &gt;::value), _Tp&gt;::type = BaseAcceptor]'\r\ntest.cpp:20:49:   required from here\r\nstl_construct.h:119:7: error: 'Base' is an inaccessible base of 'Derived'\r\n  119 |       ::new((void*)__p) _Tp(std::forward&lt;_Args&gt;(__args)...);\r\n      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n\r\n\/\/ msvc\r\nmemory(1630): error C2243: 'type cast': conversion from 'Derived *const ' to 'Base *' exists, but is inaccessible\r\ntest.cpp(19): note: see reference to function template instantiation 'std::unique_ptr&lt;BaseAcceptor,std::default_delete&lt;_Ty&gt;&gt; std::make_unique&lt;BaseAcceptor,Derived*const &gt;(Derived *const &amp;&amp;)' being compiled\r\n        with\r\n        [\r\n            _Ty=BaseAcceptor\r\n        ]\r\n\r\nvector(1604): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify \/EHsc\r\nvector(1601): note: while compiling class template member function 'void std::vector&lt;BaseAcceptor,std::allocator&lt;_Ty&gt;&gt;::_Reallocate(unsigned __int64)'\r\n        with\r\n        [\r\n            _Ty=BaseAcceptor\r\n        ]\r\nvector(1631): note: see reference to function template instantiation 'void std::vector&lt;BaseAcceptor,std::allocator&lt;_Ty&gt;&gt;::_Reallocate(unsigned __int64)' being compiled\r\n        with\r\n        [\r\n            _Ty=BaseAcceptor\r\n        ]\r\ntest.cpp(22): note: see reference to class template instantiation 'std::vector&lt;BaseAcceptor,std::allocator&lt;_Ty&gt;&gt;' being compiled\r\n        with\r\n        [\r\n            _Ty=BaseAcceptor\r\n        ]\r\n\r\nmemory(901): error C2243: 'type cast': conversion from 'Derived *const ' to 'Base *' exists, but is inaccessible\r\nmemory(971): note: see reference to function template instantiation 'std::_Ref_count_obj&lt;_Ty&gt;::_Ref_count_obj&lt;Derived*const &gt;(Derived *const &amp;&amp;)' being compiled\r\n        with\r\n        [\r\n            _Ty=BaseAcceptor\r\n        ]\r\nmemory(971): note: see reference to function template instantiation 'std::_Ref_count_obj&lt;_Ty&gt;::_Ref_count_obj&lt;Derived*const &gt;(Derived *const &amp;&amp;)' being compiled\r\n        with\r\n        [\r\n            _Ty=BaseAcceptor\r\n        ]\r\ntest.cpp(20): note: see reference to function template instantiation 'std::shared_ptr&lt;BaseAcceptor&gt; std::make_shared&lt;BaseAcceptor,Derived*const &gt;(Derived *const &amp;&amp;)' being compiled\r\n<\/pre>\n<p>What does all this mean?<\/p>\n<p>The error boils down to this:<\/p>\n<pre style=\"white-space: pre-wrap;\">\/\/ clang\r\nerror: cannot cast 'Derived' to its private base class 'Base'\r\n\r\n\/\/ gcc\r\nerror: 'Base' is an inaccessible base of 'Derived'\r\n\r\n\/\/ msvc\r\nerror C2243: 'type cast': conversion from 'Derived *const ' to 'Base *' exists, but is inaccessible\r\n<\/pre>\n<p>When <code>Derived::<wbr \/>DoSomething<\/code> constructs the <code>BaseAcceptor<\/code> class, it passes its own <code>this<\/code> as the constructor parameter. Inside that class&#8217;s own method, <code>this<\/code> gives you a <code>Derived*<\/code>.\u00b9 However, the constructor of <code>BaseAcceptor<\/code> takes a <code>Base*<\/code>. Fortunately, there is a conversion from <code>Derived*<\/code> to <code>Base*<\/code> available to <code>Derived<\/code>, and the compiler performs that conversion in order to construct the <code>BaseAcceptor<\/code>.<\/p>\n<p>On the other hand, if you use <code>make_<wbr \/>unique<\/code>, <code>make_<wbr \/>shared<\/code>, <code>emplace_*<\/code>, or other functions that perfect-forward their parameters, the parameter is passed as a <code>Derived*<\/code>. That <code>Derived*<\/code> is then forwarded perfectly to the <code>BaseAcceptor<\/code> constructor, and the compiler realizes, &#8220;Oh, wait, <code>BaseAcceptor<\/code>&#8216;s constructor doesn&#8217;t accept a <code>Derived*<\/code>. Let me see if I can convert it.&#8221; And it can&#8217;t because the <code>Derived*<\/code>-to-Base* conversion is not available to <code>make_<wbr \/>unique<\/code>.<\/p>\n<p>The conversion is not available because <code>Base<\/code> is a private base class of <code>Derived<\/code>. Only <code>Derived<\/code> itself is allowed to access the <code>Base<\/code> portion of <code>Derived<\/code>, and that includes producing a pointer to it.<\/p>\n<p>The goal of perfect forwarding is to keep all the parameters in their original form, so that they reach their destination in their original form, without triggering a decay or copy or conversion or any other nonsense along the way.<\/p>\n<p>That&#8217;s great, but it also means that the conversion is deferred. What leaves the friendly confines of the <code>Derived<\/code> class is a <code>Derived*<\/code>, and when it is finally used to construct a <code>BaseAcceptor<\/code>, the desired conversion is no longer available.<\/p>\n<p>Okay, now that we understand the problem, how do we fix it?<\/p>\n<p>One way is to force the conversion while still inside the <code>Derived<\/code> class. That way, the conversion happens while it is still accessible.<\/p>\n<pre>class Derived : Base\r\n{\r\n    void DoSomething()\r\n    {\r\n        auto p1 = std::make_unique&lt;BaseAcceptor&gt;(<span style=\"border: solid 1px currentcolor; border-bottom: none;\">static_cast&lt;Base*&gt;<\/span>(this));\r\n        auto p2 = std::make_shared&lt;BaseAcceptor&gt;(<span style=\"border: solid 1px currentcolor; border-top: none;\">static_cast&lt;Base*&gt;<\/span>(this));\r\n\r\n        std::vector&lt;BaseAcceptor&gt; v;\r\n        v.emplace_back(<span style=\"border: solid 1px currentcolor;\">static_cast&lt;Base*&gt;<\/span>(this));\r\n    }\r\n};\r\n<\/pre>\n<p>Another way is to make the conversion public.<\/p>\n<pre>class Derived : <span style=\"border: solid 1px currentcolor;\">public<\/span> Base\r\n{\r\n    void DoSomething()\r\n    {\r\n        auto p1 = std::make_unique&lt;BaseAcceptor&gt;(this);\r\n        auto p2 = std::make_shared&lt;BaseAcceptor&gt;(this);\r\n\r\n        std::vector&lt;BaseAcceptor&gt; v;\r\n        v.emplace_back(this);\r\n    }\r\n};\r\n<\/pre>\n<p>In this specific case, the private-ness of the <code>Base<\/code> class was an accident, and it was intended to be public all along. Adding the <code>public<\/code> keyword was the correct solution.<\/p>\n<p>\u00b9 I&#8217;m ignoring cv qualifiers here. You can put down your pitchforks.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lazy conversion is too lazy.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-107443","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Lazy conversion is too lazy.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107443","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=107443"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107443\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=107443"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107443"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107443"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}