{"id":110516,"date":"2024-11-13T07:00:00","date_gmt":"2024-11-13T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110516"},"modified":"2024-11-13T14:23:42","modified_gmt":"2024-11-13T22:23:42","slug":"20241113-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20241113-00\/?p=110516","title":{"rendered":"The puzzle of trying to put an object into a <CODE>std::optional<\/CODE>"},"content":{"rendered":"<p>The C++ standard library template type <code>std::<wbr \/>optional&lt;T&gt;<\/code> has one of two states. It could be empty (not contain anything), or it could contain a <code>T<\/code>.<\/p>\n<p>Suppose you start with an empty <code>std::<wbr \/>optional&lt;T&gt;<\/code>. How do you put a <code>T<\/code> into it?<\/p>\n<p>One of my colleagues tried to do it in what seemed to be the most natural way: Use the assignment operator.<\/p>\n<pre>struct Doodad\r\n{\r\n    Doodad();\r\n    ~Doodad();\r\n    std::unique_ptr&lt;DoodadStuff&gt; m_stuff;\r\n};\r\n\r\nstruct Widget\r\n{\r\n    std::optional&lt;Doodad&gt; m_doodad;\r\n\r\n    Widget()\r\n    {\r\n        if (doodads_enabled()) {\r\n            \/\/ I guess we need a Doodad too.\r\n            Doodad d;\r\n            m_doodad = d;\r\n        }\r\n    }\r\n};\r\n<\/pre>\n<p>Unfortunately, the assignment failed to compile:<\/p>\n<pre style=\"white-space: pre-wrap;\">Widget.cpp: error C2679: binary '=': no operator found which takes a right-hand operand of type 'Doodad' (or there is no acceptable conversion)\r\n<\/pre>\n<p>I asked for the rest of the error message, because the details will explain what the compiler tried to do (and why it couldn&#8217;t). It&#8217;s long, but we&#8217;ll walk through it.<\/p>\n<pre style=\"white-space: pre-wrap;\">    optional(617,1):\r\n    could be 'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(const std::optional&lt;Doodad&gt; &amp;)'\r\n        Widget.cpp(100,9):\r\n        'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(const std::optional&lt;Doodad&gt; &amp;)': cannot convert argument 2 from 'Doodad' to 'const std::optional&lt;Doodad&gt; &amp;'\r\n            Widget.cpp(100,27):\r\n            Reason: cannot convert from 'Doodad' to 'const std::optional&lt;Doodad&gt;'\r\n            Widget.cpp(100,27):\r\n            No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called\r\n    optional(283,28):\r\n    or       'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::nullopt_t) noexcept'\r\n        Widget.cpp(100,9):\r\n        'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::nullopt_t) noexcept': cannot convert argument 2 from 'Doodad' to 'std::nullopt_t'\r\n            Widget.cpp(100,27):\r\n            No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called\r\n    optional(321,28):\r\n    or       'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::optional&lt;_Ty2&gt; &amp;&amp;) noexcept(&lt;expr&gt;)'\r\n        Widget.cpp(100,9):\r\n        'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::optional&lt;_Ty2&gt; &amp;&amp;) noexcept(&lt;expr&gt;)': could not deduce template argument for 'std::optional&lt;_Ty2&gt; &amp;&amp;' from 'Doodad'\r\n    optional(307,28):\r\n    or       'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(const std::optional&lt;_Ty2&gt; &amp;) noexcept(&lt;expr&gt;)'\r\n        Widget.cpp(100,9):\r\n        'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(const std::optional&lt;_Ty2&gt; &amp;) noexcept(&lt;expr&gt;)': could not deduce template argument for 'const std::optional&lt;_Ty2&gt; &amp;' from 'Doodad'\r\n    optional(292,28):\r\n    or       'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(_Ty2 &amp;&amp;) noexcept(&lt;expr&gt;)'\r\n        Widget.cpp(100,9):\r\n        'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(_Ty2 &amp;&amp;) noexcept(&lt;expr&gt;)': could not deduce template argument for '__formal'\r\n            optional(288,33):\r\n            'std::enable_if_t&lt;false,int&gt;' : Failed to specialize alias template\r\n    Widget.cpp(100,9):\r\n    while trying to match the argument list '(std::optional&lt;Doodad&gt;, Doodad)'\r\n<\/pre>\n<p>The compiler is showing its work. It&#8217;s showing you all the possible overloaded assignment operators and explained why each one failed. The way to understand what went wrong is to look for the overload you intended to use and see why the compiler rejected it. Let&#8217;s take them one at a time.<\/p>\n<pre style=\"white-space: pre-wrap;\">could be 'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(const std::optional&lt;Doodad&gt; &amp;)'\r\n    cannot convert argument 2 from 'Doodad' to 'const std::optional&lt;Doodad&gt; &amp;'\r\n    Reason: cannot convert from 'Doodad' to 'const std::optional&lt;Doodad&gt;'\r\n    No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called\r\n<\/pre>\n<p>The first assignment operator available is the one where you assign a <code>std::optional&lt;Doodad&gt;<\/code> to another <code>std::optional&lt;Doodad&gt;<\/code>. This one failed because you passed a <code>Doodad<\/code>, not a <code>std::optional&lt;Doodad&gt;<\/code>, and there was no eligible conversion.<\/p>\n<p>Okay, what&#8217;s next?<\/p>\n<pre style=\"white-space: pre-wrap;\">or 'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::nullopt_t) noexcept'\r\n    'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::nullopt_t) noexcept': cannot convert argument 2 from 'Doodad' to 'std::nullopt_t'\r\n    No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called\r\n<\/pre>\n<p>This is the emptying assignment, where you can assign a <code>std::nullopt<\/code> to the optional to return it to the empty state. This is not what we wanted either, so we&#8217;re not surprised that it failed.<\/p>\n<p>Onward.<\/p>\n<pre style=\"white-space: pre-wrap;\">or 'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::optional&lt;_Ty2&gt; &amp;&amp;) noexcept(&lt;expr&gt;)'\r\n    'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(std::optional&lt;_Ty2&gt; &amp;&amp;) noexcept(&lt;expr&gt;)': could not deduce template argument for 'std::optional&lt;_Ty2&gt; &amp;&amp;' from 'Doodad'\r\n<\/pre>\n<p>This is the case of move-assigning a <code>std::optional&lt;T2&gt;<\/code> to a <code>std::optional&lt;T1&gt;<\/code>. This is also not what we were trying to do, so the fact that it failed is expected.<\/p>\n<p>Keep going.<\/p>\n<pre style=\"white-space: pre-wrap;\">or 'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(const std::optional&lt;_Ty2&gt; &amp;) noexcept(&lt;expr&gt;)'\r\n    'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(const std::optional&lt;_Ty2&gt; &amp;) noexcept(&lt;expr&gt;)': could not deduce template argument for 'const std::optional&lt;_Ty2&gt; &amp;' from 'Doodad'\r\n<\/pre>\n<p>This is the copy-assignment version of the above, so we can skip this one, too.<\/p>\n<pre style=\"white-space: pre-wrap;\">or 'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(_Ty2 &amp;&amp;) noexcept(&lt;expr&gt;)'\r\n    'std::optional&lt;Doodad&gt; &amp;std::optional&lt;Doodad&gt;::operator =(_Ty2 &amp;&amp;) noexcept(&lt;expr&gt;)': could not deduce template argument for '__formal'\r\n        'std::enable_if_t&lt;false,int&gt;' : Failed to specialize alias template\r\n    while trying to match the argument list '(std::optional&lt;Doodad&gt;, Doodad)'\r\n<\/pre>\n<p>This is the final catch-all case of assigning an arbitrary object to an optional. This is the one we were hoping to use, but somehow it failed because of a &#8220;could not deduce template argument&#8221; from <code>std::<wbr \/>enable_if_t&lt;false, int&gt;<\/code>, and that leading <code>false<\/code> tells us that an <code>enable_if<\/code> precondition failed. <a href=\"https:\/\/github.com\/microsoft\/STL\/blob\/e0ad9c36380c2fa78042ac556eb8c2100fe2d22e\/stl\/inc\/optional#L291\"> Let&#8217;s look at the precondition<\/a>.<\/p>\n<pre style=\"white-space: pre-wrap;\">template &lt;class _Ty2 = _Ty,\r\n        enable_if_t&lt;\r\n            conjunction_v&lt;\r\n                negation&lt;\r\n                    is_same&lt;optional, _Remove_cvref_t&lt;_Ty2&gt;&gt;\r\n                &gt;,\r\n                negation&lt;\r\n                    conjunction&lt;is_scalar&lt;_Ty&gt;, is_same&lt;_Ty, decay_t&lt;_Ty2&gt;&gt;&gt;\r\n                &gt;,\r\n                is_constructible&lt;_Ty, _Ty2&gt;,\r\n                is_assignable&lt;_Ty&amp;, _Ty2&gt;\r\n            &gt;,\r\n            int&gt; = 0&gt;\r\n    _CONSTEXPR20 optional&amp; operator=(_Ty2&amp;&amp; _Right) noexcept(\u27e6...\u27e7)\r\n<\/pre>\n<p>Let&#8217;s work on simplifying this template metaprogramming. In our case, <code>_Ty2<\/code> is <code>Doodad&amp;<\/code>, so <code>std::<wbr \/>decay_t&lt;_Ty2&gt;<\/code> is <code>std::<wbr \/>decay_t&lt;Doodad&amp;&gt;<\/code>, which is <code>Doodad<\/code>. From its name, it&#8217;s highly likely that the internal template <code>_Remove_cvref_t<\/code> is <code>std::<wbr \/>remove_cv_t<\/code>+<code>std::<wbr \/>remove_reference_t<\/code>, but if you don&#8217;t trust your intuition, you can <a href=\"https:\/\/github.com\/microsoft\/STL\/blob\/e0ad9c36380c2fa78042ac556eb8c2100fe2d22e\/stl\/inc\/xtr1common#L234\"> look it up for yourself<\/a>:<\/p>\n<pre>template&lt;class _Ty&gt;\r\nusing _Remove_Cvref_t _MSVC_KNOWN_SEMANTICS = remove_cv_t&lt;remove_reference_t&lt;_Ty&gt;&gt;;\r\n<\/pre>\n<p>Applying it to the case where <code>_Ty2<\/code> is <code>Doodad&amp;<\/code> results in <code>remove_cv_t&lt;remove_reference_t&lt;Doodad&amp;&gt;&gt;<\/code> which is <code>remove_cv_t&lt;Doodad&gt;<\/code> which is just <code>Doodad<\/code>. Plugging all that back into the <code>enable_if<\/code>, as well as <code>_Ty = Doodad<\/code> (since <code>_Ty<\/code> is the template parameter to <code>optional<\/code> itself) gives us this:<\/p>\n<pre style=\"white-space: pre-wrap;\">        enable_if_t&lt;\r\n            conjunction_v&lt;\r\n                negation&lt;\r\n                    is_same&lt;optional, Doodad&gt;\r\n                &gt;,\r\n                negation&lt;\r\n                    conjunction&lt;is_scalar&lt;Doodad&gt;, is_same&lt;Doodad, Doodad&gt;&gt;\r\n                &gt;,\r\n                is_constructible&lt;Doodad, Doodad&amp;&gt;,\r\n                is_assignable&lt;Doodad&amp;, Doodad&amp;&gt;\r\n            &gt;,\r\n            int&gt; = 0&gt;\r\n    _CONSTEXPR20 optional&amp; operator=(Doodad;&amp; _Right) noexcept(\u27e6...\u27e7)\r\n<\/pre>\n<p>Now we can interpret the expression. The operator is enabled if&#8230;<\/p>\n<pre style=\"white-space: pre-wrap;\">    !is_same&lt;optional, Doodad&gt; &amp;&amp;\r\n    !(is_scalar&lt;Doodad&gt; &amp;&amp; is_same&lt;Doodad, Doodad&gt;) &amp;&amp;\r\n    is_constructible&lt;Doodad, Doodad&amp;&gt; &amp;&amp;\r\n    is_assignable&lt;Doodad&amp;, Doodad&amp;&gt;\r\n<\/pre>\n<p>(It so happens that <a href=\"https:\/\/timsong-cpp.github.io\/cppwp\/optional.optional#optional.assign-14\"> these are precisely the conditions spelled out in the C++ language specification<\/a>. I doubt this is a coincidence.)<\/p>\n<p>The first clause says &#8220;you are not assigning from a <code>std::optional&lt;Doodad&gt;<\/code>&#8220;, which is true. We are assigning from a <code>Doodad<\/code>. The purpose of this clause is to remove this overload from consideration in favor of the other overload that specifically is for optional-to-optional assignment.<\/p>\n<p>The second clause says &#8220;you are not trying to assign a scalar that is the same type of the optional.&#8221; I think this is to remove this overload from consideration in favor of converting the source scalar to an <code>optional&lt;_Ty&gt;<\/code> and assigning that. Regardless, it doesn&#8217;t apply here, so we pass that test too.<\/p>\n<p>The next test is to see whether a <code>Doodad<\/code> can be constructed from a <code>Doodad&amp;<\/code>, and in the case of a <code>Doodad<\/code>, it turns out that this is not true because the <code>Doodad<\/code> contains a <code>unique_ptr<\/code>, which makes it non-copyable.<\/p>\n<p>Okay, so we can fix that by using <code>std::move<\/code> to move the <code>Doodad<\/code> on the stack into the optional, right?<\/p>\n<pre>            Doodad d;\r\n            m_doodad = std::move(d);\r\n            \/\/ or even\r\n            m_doodad = Doodad();\r\n<\/pre>\n<p>Unfortunately, this fails in basically the same way. But how can that be?<\/p>\n<p>It&#8217;s because <code>Doodad<\/code> is not move-assignable, even though all of its members are movable!<\/p>\n<p>The requirements for an implicitly-defined move-assignment operator are that the type have no user-declared copy constructors, move constructors, copy assignment operators, or destructors. Our <code>Doodad<\/code> has a destructor, so that removes the implicitly-defined move-assignment operator.<\/p>\n<p><b>Bonus reading<\/b>: <a href=\"https:\/\/wg21.link\/n3153\"> Implicit Move Must Go<\/a>.<\/p>\n<p>So our <code>Doodad<\/code> is not movable, not copyable.<\/p>\n<p>One solution is to make our <code>Doodad<\/code> movable. This means investigating the class invariants and verifying that memberwise <code>std::move<\/code> preserves them. This can get tricky if, for example, the <code>Doodad<\/code> allowed pointers to itself to escape. If you&#8217;ve done the analysis and confirmed that memberwise <code>std::move<\/code> is correct behavior, you can add<\/p>\n<pre>    Doodad(Doodad&amp;&amp;) = default;\r\n    Doodad&amp; operator=(Doodad&amp;&amp;) = default;\r\n<\/pre>\n<p>to ask for the compiler to generate a default move constructor and default move assignment operator.<\/p>\n<p>But maybe you study the <code>Doodad<\/code> and conclude that it is not movable for whatever reason. What else can you do?<\/p>\n<p>We&#8217;ll look at our options next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The C++ standard library template type std::optional&lt;T&gt; has one of two states. It could be empty (not contain anything), or it could contain a T. Suppose you start with an empty std::optional&lt;T&gt;. How do you put a T into it? One of my colleagues tried to do it in what seemed to be the most [&hellip;]<\/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-110516","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The C++ standard library template type std::optional&lt;T&gt; has one of two states. It could be empty (not contain anything), or it could contain a T. Suppose you start with an empty std::optional&lt;T&gt;. How do you put a T into it? One of my colleagues tried to do it in what seemed to be the most [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110516","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=110516"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110516\/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=110516"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110516"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110516"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}