{"id":105057,"date":"2021-04-06T07:00:00","date_gmt":"2021-04-06T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105057"},"modified":"2021-04-06T21:53:40","modified_gmt":"2021-04-07T04:53:40","slug":"20210406-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210406-00\/?p=105057","title":{"rendered":"C++ coroutines: Building a result holder for movable types"},"content":{"rendered":"<p>One of the pieces we need for <a title=\"C++ coroutines: Making the promise itself be the shared state, the outline\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210405-00\/?p=105054\"> the <code>simple_promise<\/code> we use to construct a coroutine<\/a> is what we have been calling the &#8220;result holder&#8221;. This is basically a variant that starts out empty, and can atomically transition to holding either the result of a successful coroutine, an exception pointer for a failed coroutine.<\/p>\n<p>Our variant is more complicated than a <code>std::variant<\/code> thanks to the atomic nature (since it is used from both the coroutine as well as the awaiter). On the other hand, we&#8217;re simpler than a <code>std::variant<\/code> because we do not have to deal with the <code>valueless_<wbr \/>_<wbr \/>by_<wbr \/>exception<\/code> state.<\/p>\n<p>The storage of the value result is made complicated by a few things:<\/p>\n<ul>\n<li>If the promise produces a <code>void<\/code>, we can&#8217;t actually store a <code>void<\/code> anywhere because there is no such thing as an object of type <code>void<\/code>. Instead, <code>void<\/code> needs to be represented by the <i>absence<\/i> of an object.<\/li>\n<li>If the promise produces a reference, we have to be careful to preserve the reference rather than accidentally decaying it to a value.<\/li>\n<li>If the promise produces a non-reference, then we need to move the value around, in case the value is non-copyable, and more generally to avoid unnecessary copies. Even better is if we just pass the value by reference as much as possible, so that there is only a single move operation to get it to its destination.<\/li>\n<\/ul>\n<p>As we saw earlier, we can handle the first two cases by wrapping the value inside a structure. And the third case just requires vigilance.<\/p>\n<pre>\/\/ \u27e6simple_promise_result_holder definition\u27e7 \u2254\r\n\r\n    template&lt;typename T&gt;\r\n    struct simple_promise_value\r\n    {\r\n        T value;\r\n        T&amp;&amp; get_value()\r\n        { return static_cast&lt;T&amp;&amp;&gt;(value); }\r\n    };\r\n\r\n    template&lt;&gt;\r\n    struct simple_promise_value&lt;void&gt;\r\n    {\r\n        void get_value() { }\r\n    };\r\n<\/pre>\n<p>This is similar to the pattern we&#8217;ve seen before for wrapping something (possibly a reference, possibly a <code>void<\/code>) in a structure. The <code>get_value<\/code> method here is a little different because we want to move the object out when retrieving it.<\/p>\n<p>The first thing to observe is that we cast the value to <code>T&amp;&amp;<\/code>, so everything hinges on what <code>T&amp;&amp;<\/code> is.<\/p>\n<p>In the case where <code>T<\/code> is a reference type, <code>T&amp;&amp;<\/code> collapses back down to just <code>T<\/code>: Adding a <code>&amp;&amp;<\/code> to a reference has no effect. Therefore, if <code>T<\/code> is a reference type, the net effect is to return the <code>value<\/code> as whatever type of reference <code>T<\/code> already is.<\/p>\n<p>In the case where <code>T<\/code> is not a reference, adding <code>&amp;&amp;<\/code> makes it an rvalue reference, so net effect is to return an rvalue reference to the held value. That rvalue reference is then ready to be moved out.<\/p>\n<p>That was a lot to unpack in that one function <code>get_<wbr \/>value<\/code>.\u00b2<\/p>\n<p>Okay, so here comes the holder itself.<\/p>\n<pre>    template&lt;typename T&gt;\r\n    struct simple_promise_result_holder\r\n    {\r\n        enum class result_status\r\n            { empty, value, error };\r\n<\/pre>\n<p>We define an enumeration that describes what is in the result holder. A status of <code>empty<\/code> means that the coroutine has started but hasn&#8217;t yet completed, <code>value<\/code> means that it completed with a result, and <code>error<\/code> means that it completed with an error.<\/p>\n<p>Next we have the member variables <code>simple_<wbr \/>promise_<wbr \/>result_<wbr \/>holder<\/code>.<\/p>\n<pre>        std::atomic&lt;result_status&gt; status\r\n            { result_status::empty };\r\n\r\n        union result_holder\r\n        {\r\n            result_holder() { }\r\n            ~result_holder() { }\r\n            simple_promise_value&lt;T&gt; wrapper;\r\n            std::exception_ptr error;\r\n        } result;\r\n<\/pre>\n<p>We put the wrapper inside a union, and explicitly give the union trivial constructors and destructors to indicate that we will take responsibility for constructing and destructing the active member of the union.<\/p>\n<p>Okay, so how do we put values into the holder?<\/p>\n<pre>        template&lt;typename...Args&gt;\r\n        void set_value(Args&amp;&amp;... args)\r\n        {\r\n            new (std::addressof(result.wrapper))\r\n                simple_promise_value&lt;T&gt;\r\n                    { std::forward&lt;Args&gt;(args)... };\r\n            status.store(result_status::value,\r\n                std::memory_order_release);\r\n        }\r\n<\/pre>\n<p>If we are setting a value into the holder, then use placement new to construct the wrapper, forwarding the parameters into the constructor. If <code>T<\/code> is <code>void<\/code>, then there are no parameters, and we construct an empty wrapper. This seems pointless, but it presreves the invariant that if the status is <code>succeeded<\/code>, then the wrapper is the active member of the union, which saves us some <code>if constexpr<\/code> effort at destruction. In practice, constructing and destructing the empty structure has no code generation effect, so this is just bookkeeping to avoid tripping over undefined behavior.<\/p>\n<p>If <code>T<\/code> is non-<code>void<\/code>, then the parameter is the <code>T<\/code> (or possibly an rvalue or const lvalue reference to <code>T<\/code>) with which to initialize the wrapper.<\/p>\n<p>In both cases, if the wrapper has been successfully constructed, we mark the status as <code>value<\/code> to indicate that we have a value. Note that this must wait until construction is complete for two reasons.<\/p>\n<ol>\n<li>If an exception occurs during construction, we don&#8217;t want to say that we have a working wrapper.<\/li>\n<li>We don&#8217;t want the awaiting thread to see the value until it is fully constructed.<\/li>\n<\/ol>\n<p>Updating the status with release memory order ensures that the construction of the wrapper is visible to other threads before we set the status.<\/p>\n<pre>        void unhandled_exception() noexcept\r\n        {\r\n            new (std::addressof(result.error))\r\n                std::exception_ptr(std::current_exception());\r\n            status.store(result_status::error,\r\n                std::memory_order_release);\r\n        }\r\n<\/pre>\n<p>Saving the current exception is entirely analogous. The current exception is obtained from the C++ language itself and used to initialize the <code>error<\/code> member of our holder. Again, we update the status after the exception is stowed, and use release semantics to avoid races.<\/p>\n<pre>        bool is_empty() const noexcept\r\n        {\r\n            return status.load(std::memory_order_relaxed) ==\r\n                result_status::empty;\r\n        }\r\n<\/pre>\n<p>The <code>is_empty<\/code> method says whether there&#8217;s anything interesting in the holder yet.<\/p>\n<p>Of course, once we put the value into the holder, we presumably want to be able to get it out again.<\/p>\n<pre>        T get_value()\r\n        {\r\n            switch (status.load(std::memory_order_acquire)) {\r\n            case result_status::value:\r\n                return result.wrapper.get_value();\r\n            case result_status::error:\r\n                std::rethrow_exception(\r\n                    std::exchange(result.error, {}));\r\n            }\r\n            assert(false);\r\n            std::terminate();\r\n        }\r\n<\/pre>\n<p>We check the status to tell us whether the thing inside the holder is a value or an exception. We need to use acquire semantics here, to ensure that we don&#8217;t read the <code>T<\/code> or <code>exception_<wbr \/>ptr<\/code> before it has been completely written by the coroutine.<\/p>\n<p>If the status says that the contents are a value, then we let the wrapper&#8217;s <code>get_value<\/code> extract the result (or return nothing, if the type is <code>void<\/code>). Reference types come out as themselves, and non-reference types are returned as rvalue references, so that the results can be moved out.<\/p>\n<p>If the status says that we have an error, then we rethrow the stowed exception. We exchange it out so we can detect at destruction if we had an unobserved exception.<\/p>\n<p>If the status is neither of these, then something horrible has happened, and we terminate.<\/p>\n<p>The last step in the lifetime of the holder is destruction:<\/p>\n<pre>        ~simple_promise_result_holder()\r\n        {\r\n            switch (status.load(std::memory_order_relaxed)) {\r\n            case result_status::value:\r\n                result.wrapper.~simple_promise_value();\r\n                break;\r\n            case result_status::error:\r\n                if (result.error)\r\n                    std::rethrow_exception(result.error);\r\n                result.error.~exception_ptr();\r\n            }\r\n        }\r\n    };\r\n<\/pre>\n<p>When it&#8217;s time to clean up, we use the status as a discriminant to figure out what is in the holder and destruct the appropriate member. (Note that we take advantage of the <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/injected-class-name\"> injected class name<\/a> to save us some <code>&lt;T&gt;<\/code> and <code>std::<\/code> keypresses.)<\/p>\n<p>If we are cleaning an exception and the exception is still there, then that means that an exception occurred in the coroutine but was never observed, much less caught. This is not a great thing, so we rethrow the exception immediately so that the program can crash with an unhandled exception.\u00b2<\/p>\n<p>That takes care of the missing pieces related to the result holder. Next time, we&#8217;ll look at the <code>return_<wbr \/>value<\/code> and <code>return_<wbr \/>void<\/code> methods which are responsible for mediating the <code>co_return<\/code>.<\/p>\n<p>\u00b9 An earlier version of this code used <code>std::forward&lt;T&gt;(value)<\/code> instead of <code>static_cast&lt;T&amp;&amp;&gt;(value)<\/code>, in the mistaken belief that the two are equivalent. But they&#8217;re not: <code>std::forward&lt;T&gt;(value)<\/code> contains extra magic in its declaration to reject lvalue-to-rvalue conversion. Mind you, at the end of the day, they are both <code>static_cast&lt;T&amp;&amp;&gt;(value)<\/code>, so it&#8217;s a toss-up which one you use if you aren&#8217;t trying to block lvalue-to-rvalue conversion.<\/p>\n<p>\u00b2 We are explicitly ignoring the guidance not to throw an exception in a destructor, because we really do want to crash.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Not that kind of movable type.<\/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-105057","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Not that kind of movable type.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105057","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=105057"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105057\/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=105057"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105057"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105057"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}