{"id":105146,"date":"2021-04-23T07:00:00","date_gmt":"2021-04-23T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105146"},"modified":"2021-04-23T08:12:17","modified_gmt":"2021-04-23T15:12:17","slug":"20210423-00-2","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210423-00\/?p=105146","title":{"rendered":"C++ coroutines: Associating multiple task types with the same promise type"},"content":{"rendered":"<p>We created two very similiar promises for hot-start and cold-start coroutines. It turns out that we can unify them.<\/p>\n<p>The association between a promise and a task is not one-to-one, but rather one-to-many: A single promise can back multiple tasks. But how can you do that? After all, the task associated with a promise is the thing returned by the <code>get_<wbr \/>return_<wbr \/>object<\/code> method.<\/p>\n<p>Well, not exactly.<\/p>\n<p>The rule is that the thing returned by <code>get_<wbr \/>return_<wbr \/>object<\/code> method is used to <i>initialize<\/i> the task. It doesn&#8217;t have to be the task itself.<\/p>\n<p>Therefore, you can associate multiple tasks with the promise if you arrange for <code>get_<wbr \/>return_<wbr \/>object<\/code> to return something that all of the tasks can initialize from.<\/p>\n<p>The coroutine code generation goes like this:<\/p>\n<ul>\n<li>Call <code>get_<wbr \/>return_<wbr \/>object<\/code> to get the object that initializes the task.<\/li>\n<li>Perform these two operations in some unspecified order:\n<ul>\n<li>Begin the coroutine at <code>initial_<wbr \/>suspend<\/code> and let it run until its first suspension point (determined at runtime).<\/li>\n<li>Create a task\u00b9 from the return value of <code>get_<wbr \/>return_<wbr \/>object<\/code>.<\/li>\n<\/ul>\n<\/li>\n<li>Return the task.<\/li>\n<\/ul>\n<p>In our case, we have two flavors of awaiters, one of which leaves the coroutine cold, and the other of which hot-starts the coroutine. The coroutine machinery itself can be left unaware of this detail and leave the mechanics to the task.<\/p>\n<p>For Windows developers, two kinds of tasks that would be useful are one that awaits in a thread-unaware way (the version we have been writing so far), and another that awaits in a way that preserves the COM context.<\/p>\n<p>But for today, I&#8217;ll show how a single promise can be used for both cold-start and hot-start tasks. Go back to our hot-start coroutine promise and make these changes:<\/p>\n<pre>    template&lt;typename T&gt;\r\n    struct simple_promise_base\r\n    {\r\n        ...\r\n\r\n        std::atomic&lt;void*&gt; m_waiting{ <span style=\"color: blue;\">cold_ptr<\/span> };\r\n\r\n        static constexpr void* cold_ptr = reinterpret_cast&lt;void*&gt;(3);\r\n<\/pre>\n<p>The coroutine now starts out cold. The warm-start task will auto-start it, whereas the cold-start task will leave it cold until it is awaited.<\/p>\n<pre>        auto get_return_object() noexcept\r\n        {\r\n            return <span style=\"color: blue;\">as_promise();<\/span>\r\n        }\r\n<\/pre>\n<p>We alter the <code>get_<wbr \/>return_<wbr \/>object<\/code> method so that it returns a pointer to the promise, rather than the task constructed from it. This allows us to have multiple tasks that construct in different ways, and more importantly, have different awaiters.<\/p>\n<pre>        void start()\r\n        {\r\n            m_waiting.store(running_ptr, std::memory_order_relaxed);\r\n            as_handle().resume();\r\n        }\r\n<\/pre>\n<p>A new explicit <code>start()<\/code> method kicks off the coroutine. The hot-start task will call this immediately, whereas the cold-start task will wait until the task is <code>co_await<\/code>ed.<\/p>\n<pre>        <span style=\"color: blue;\">std::experimental::suspend_always<\/span> initial_suspend() noexcept\r\n        {\r\n            return {};\r\n        }\r\n<\/pre>\n<p>The coroutine now suspends at its initial suspend point instead of continuing to run. This makes the coroutine a cold-start coroutine by default.<\/p>\n<pre>        bool cold_client_await_ready()\r\n        {\r\n            return false;\r\n        }\r\n\r\n        auto cold_client_await_suspend(\r\n            std::experimental::coroutine_handle&lt;&gt; handle)\r\n        {\r\n            start();\r\n            return m_waiting.exchange(handle.address(),\r\n                std::memory_order_acq_rel) == running_ptr;\r\n        }\r\n<\/pre>\n<p>These new functions are carried over from our previous conversion from hot-start to cold-start, but with different names so we can keep both versions.<\/p>\n<p>Of course, we need to create an awaiter that uses these cold versions.<\/p>\n<pre>    template&lt;typename T&gt;\r\n    struct cold_promise_awaiter\r\n    {\r\n        promise_ptr&lt;T&gt; self;\r\n\r\n        bool await_ready()\r\n        {\r\n            return self-&gt;cold_client_await_ready();\r\n        }\r\n\r\n        auto await_suspend(std::experimental::coroutine_handle&lt;&gt; handle)\r\n        {\r\n            return self-&gt;cold_client_await_suspend(handle);\r\n        }\r\n\r\n        T await_resume()\r\n        {\r\n            return self-&gt;client_await_resume();\r\n        }\r\n    };\r\n<\/pre>\n<p>This is analogous to our <code>promise_<wbr \/>awaiter<\/code>, except that it uses the cold versions of <code>await_<wbr \/>ready<\/code> and <code>await_<wbr \/>suspend<\/code>.<\/p>\n<pre>namespace async_helpers::details\r\n{\r\n    template&lt;typename T&gt;\r\n    struct simple_task_base\r\n    {\r\n        simple_task_base(simple_promise&lt;T&gt;*\r\n            initial = nullptr) noexcept : promise(initial) { }\r\n\r\n        struct cannot_await_lvalue_use_std_move {};\r\n        cannot_await_lvalue_use_std_move operator co_await() &amp; = delete;\r\n\r\n    protected:\r\n        promise_ptr&lt;T&gt; promise;\r\n    };\r\n}\r\n\r\nnamespace async_helpers\r\n{\r\n    template&lt;typename T&gt;\r\n    struct simple_task : details::simple_task_base&lt;T&gt;\r\n    {\r\n        using base = details::simple_task_base&lt;T&gt;;\r\n        simple_task() = default;\r\n        simple_task(details::simple_promise&lt;T&gt;*\r\n            initial) : base(initial)\r\n            { this-&gt;promise-&gt;start(); }\r\n\r\n        void swap(simple_task&amp; other)\r\n        {\r\n            std::swap(this-&gt;promise, other.promise);\r\n        }\r\n\r\n        using base::operator co_await;\r\n\r\n        auto operator co_await() &amp;&amp;\r\n        {\r\n            return details::promise_awaiter&lt;T&gt;\r\n                { std::move(this-&gt;promise) };\r\n        }\r\n    };\r\n\r\n    template&lt;typename T&gt;\r\n    void swap(simple_task&lt;T&gt;&amp; left, simple_task&lt;T&gt;&amp; right)\r\n    {\r\n        left.swap(right);\r\n    }\r\n}\r\n<\/pre>\n<p>We factor out the promise-management code and the &#8220;you&#8217;re holding it wrong&#8221; class into a common base class <code>simple_<wbr \/>task_<wbr \/>base<\/code>.<\/p>\n<p>The <code>simple_<wbr \/>task<\/code> used to have a single constructor that covered both construction from a promise and construction of an empty task. We split them up, so that we can <code>start()<\/code> the promise in the case where we are being constructed as a result of a call to <code>get_<wbr \/>return_<wbr \/>object<\/code>. This is what turns the cold-start coroutine into a hot-start coroutine.<\/p>\n<p>We can also create a <code>cold_<wbr \/>simple_<wbr \/>task<\/code> that is the cold-start version.<\/p>\n<pre>namespace async_helpers\r\n{\r\n    template&lt;typename T&gt;\r\n    struct cold_simple_task : details::simple_task_base&lt;T&gt;\r\n    {\r\n        using base = details::simple_task_base&lt;T&gt;;\r\n        cold_simple_task(details::simple_promise&lt;T&gt;*\r\n            initial = nullptr) : base(initial) { }\r\n\r\n        void swap(cold_simple_task&amp; other)\r\n        {\r\n            std::swap(this-&gt;promise, other.promise);\r\n        }\r\n\r\n        using base::operator co_await;\r\n\r\n        auto operator co_await() &amp;&amp;\r\n        {\r\n            return details::cold_promise_awaiter&lt;T&gt;\r\n                { std::move(this-&gt;promise) };\r\n        }\r\n    };\r\n\r\n    template&lt;typename T&gt;\r\n    void swap(cold_simple_task&lt;T&gt;&amp; left, cold_simple_task&lt;T&gt;&amp; right)\r\n    {\r\n        left.swap(right);\r\n    }\r\n}\r\n<\/pre>\n<p>This is the same as our <code>simple_task<\/code> except that<\/p>\n<ul>\n<li>It doesn&#8217;t <code>start()<\/code> the coroutine, leaving it cold.<\/li>\n<li>It uses <code>cold_<wbr \/>promise_<wbr \/>awaiter<\/code> instead of <code>promise_<wbr \/>awaiter<\/code>.<\/li>\n<\/ul>\n<p>Finally, we teach the compiler how to create a coroutine that returns a <code>cold_<wbr \/>simple_<wbr \/>task<\/code>:<\/p>\n<pre>template &lt;typename T, typename... Args&gt;\r\nstruct std::experimental::coroutine_traits&lt;\r\n    async_helpers::cold_simple_task&lt;T&gt;, Args...&gt;\r\n{\r\n    using promise_type =\r\n        async_helpers::details::simple_promise&lt;T&gt;;\r\n};\r\n<\/pre>\n<p>There we have it, a single promise that supports multiple kinds of tasks. This is particularly handy when you have different kinds of tasks that differ only in how they await, since the awaiter isn&#8217;t even part of the promise at all.<\/p>\n<p>Next time, we&#8217;ll look at how coroutines interact with the <code>noexcept<\/code> keyword.<\/p>\n<p>\u00b9 The language specification says merely that <code>get_<wbr \/>return_<wbr \/>object()<\/code> &#8220;is used to initialize&#8221; the return object, but doesn&#8217;t say what kind of initialization is used. Is it <i>copy-initialization<\/i>, or is it <i>direct-initialization<\/i>? (It&#8217;s almost certainly not <i>list-initialized<\/i>.) Copy initialization considers only conversions, but direct initialization also considers the constructors of the destination. Different compilers have interpreted the standard differently.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Using the special initialization rule for <CODE>get_<WBR>return_<WBR>object<\/CODE>.<\/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-105146","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Using the special initialization rule for <CODE>get_<WBR>return_<WBR>object<\/CODE>.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105146","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=105146"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105146\/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=105146"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105146"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105146"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}