{"id":108402,"date":"2023-07-07T07:00:00","date_gmt":"2023-07-07T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108402"},"modified":"2023-06-19T07:40:43","modified_gmt":"2023-06-19T14:40:43","slug":"20230707-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230707-00\/?p=108402\/","title":{"rendered":"How to wait for multiple C++ coroutines to complete before propagating failure, finding the awaiter"},"content":{"rendered":"<p>Last time, <a title=\"How to wait for multiple C++ coroutines to complete before propagating failure, wrapping the awaitable\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230706-00\/?p=108398\"> we wrapped an object that was its own awaiter<\/a>. In general, though, we may have to find the awaiter.<\/p>\n<p>Fortunately, we already wrote a helper class that can find an awaiter. Let&#8217;s bring it back, but with a little change to the <code>get_awaiter<\/code> function.<\/p>\n<pre>class awaiter_finder\r\n{\r\n    template&lt;typename T&gt;\r\n    static void find_co_await_member(T&amp;&amp;, ...);\r\n    template&lt;typename T&gt;\r\n    static auto find_co_await_member(T&amp;&amp; value, int)\r\n    -&gt; <span style=\"border: solid 1px currentcolor;\">std::decay_t&lt;<\/span>decltype(static_cast&lt;T&amp;&amp;&gt;(value).operator co_await())<span style=\"border: solid 1px currentcolor;\">&gt;<\/span> {\r\n        return static_cast&lt;T&amp;&amp;&gt;(value).operator co_await();\r\n    }\r\n    template&lt;typename T&gt;\r\n    using member_awaiter = decltype(find_co_await_member(std::declval&lt;T&gt;(), 0));\r\n\r\n    template&lt;typename T&gt;\r\n    static void find_co_await_free(T&amp;&amp;, ...);\r\n    template&lt;typename T&gt;\r\n    static auto find_co_await_free(T&amp;&amp; value, int)\r\n    -&gt; <span style=\"border: solid 1px currentcolor;\">std::decay_t&lt;<\/span>decltype(operator co_await(static_cast&lt;T&amp;&amp;&gt;(value)))<span style=\"border: solid 1px currentcolor;\">&gt;<\/span> {\r\n    return operator co_await(static_cast&lt;T&amp;&amp;&gt;(value));\r\n    }\r\n    template&lt;typename T&gt;\r\n    using free_awaiter = decltype(find_co_await_free(std::declval&lt;T&gt;(), 0));\r\n\r\npublic:\r\n    template&lt;typename T&gt;\r\n    static <span style=\"border: solid 1px currentcolor;\">decltype(auto)<\/span> get_awaiter(T&amp;&amp; value)\r\n    {\r\n        if constexpr (!std::is_same_v&lt;member_awaiter&lt;T&gt;, void&gt;) {\r\n            return find_co_await_member(static_cast&lt;T&amp;&amp;&gt;(value), 0);\r\n        } else if constexpr (!std::is_same_v&lt;free_awaiter&lt;T&gt;, void&gt;) {\r\n            return find_co_await_free(static_cast&lt;T&amp;&amp;&gt;(value), 0);\r\n        } else {\r\n            <span style=\"border: solid 1px currentcolor;\">return std::forward&lt;T&gt;(value);<\/span>\r\n        }\r\n    }\r\n\r\n    template&lt;typename T&gt;\r\n    using type = decltype(get_awaiter(std::declval&lt;T&gt;()));\r\n};\r\n<\/pre>\n<p>In the case where the object is its own awaiter, we want <code>get_awaiter()<\/code> to return a reference to the object itself. This means that we need to change the return type of <code>get_awaiter()<\/code> to <code>decltype(auto)<\/code> so that it will infer a reference when we return <code>value<\/code>.<\/p>\n<p>But that means that it will also infer a reference from <code>find_<wbr \/>co_<wbr \/>await_<wbr \/>member<\/code> and <code>find_<wbr \/>co_<wbr \/>await_<wbr \/>free<\/code>, so we have to <code>std::<wbr \/>decay_t<\/code> those return types to remove any reference or cv-qualifiers.<\/p>\n<p>We can use this helper to teach our <code>wrapped_awaitable<\/code> how to find the awaiter.<\/p>\n<pre>template&lt;typename Inner&gt;\r\nstruct wrapped_awaitable\r\n{\r\n    wrapped_awaitable(Inner&amp; inner) :\r\n    <span style=\"border: solid 1px currentcolor;\">m_awaiter(awaiter_finder::get_awaiter(std::move(inner)))<\/span> {}\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">typename awaiter_finder::type&lt;Inner&gt; m_awaiter;<\/span>\r\n    std::exception_ptr m_eptr;\r\n\r\n    bool await_ready() try\r\n    { return <span style=\"border: solid 1px currentcolor;\">m_awaiter<\/span>.await_ready(); }\r\n    catch (...) {\r\n        m_eptr = std::current_exception();\r\n        return true;\r\n    }\r\n\r\n    template&lt;typename Handle&gt;\r\n    std::coroutine_handle&lt;&gt;\r\n    await_suspend(Handle handle) try {\r\n        using Ret = decltype(<span style=\"border: solid 1px currentcolor;\">m_awaiter<\/span>.await_suspend(handle));\r\n        if constexpr (std::is_same_v&lt;void, Ret&gt;) {\r\n            <span style=\"border: solid 1px currentcolor;\">m_awaiter<\/span>.await_suspend(handle);\r\n            return std::noop_coroutine();\r\n        } else if constexpr (std::is_same_v&lt;bool, Ret&gt;) {\r\n            return <span style=\"border: solid 1px currentcolor;\">m_awaiter<\/span>.await_suspend(handle) ? \r\n                static_cast&lt;std::coroutine_handle&lt;&gt;&gt;(\r\n                    std::noop_coroutine()) :\r\n                handle;\r\n        } else {\r\n            return <span style=\"border: solid 1px currentcolor;\">m_awaiter<\/span>.await_suspend(handle);\r\n        }\r\n    } catch (...) {\r\n        m_eptr = std::current_exception();\r\n        return handle;\r\n    }\r\n\r\n    std::exception_ptr await_resume() try {\r\n        if (m_eptr) return m_eptr;\r\n        <span style=\"border: solid 1px currentcolor;\">m_awaiter<\/span>.await_resume();\r\n        return nullptr;\r\n    } catch (...) {\r\n        return std::current_exception();\r\n    }\r\n};\r\n\r\ntemplate&lt;typename... T&gt;\r\nIAsyncAction when_all_complete(T... asyncs)\r\n{\r\n    std::exception_ptr eptr;\r\n\r\n    auto accumulate = [&amp;](std::exception_ptr e) {\r\n        if (eptr == nullptr) eptr = e;\r\n    };\r\n\r\n    (accumulate(co_await wrapped_awaitable(asyncs)), ...);\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>Now that we have a <code>wrapped_<wbr \/>awaitable<\/code> that completes with a <code>std::exception_ptr<\/code>, we have reduced the problem to the case way at the start of this series where we have a bunch of items, all of the same type, so we can put them in an <code>initializer_<wbr \/>list<\/code> and use standard algorithms with them.<\/p>\n<pre>template&lt;typename... T&gt;\r\nIAsyncAction when_all_complete(T... asyncs)\r\n{\r\n    auto results = { co_await wrapped_awaitable(asyncs))... };\r\n    auto it = std::find_if(results.begin(), results.end(),\r\n        [](auto&amp;&amp; eptr) { return eptr; });\r\n    if (it != results.end()) std::rethrow_exception(*it);\r\n}\r\n<\/pre>\n<p>However, this misses an edge case: If you call <code>when_<wbr \/>all_<wbr \/>complete<\/code> with no parameters, the compiler cannot deduce <code>results<\/code> from what ends up being an empty set of braces. So we&#8217;ll have to provide an explicit type for it.<\/p>\n<pre>template&lt;typename... T&gt;\r\nIAsyncAction when_all_complete(T... asyncs)\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">std::initializer_list&lt;std::exception_ptr&gt;<\/span>\r\n        results = { co_await wrapped_awaitable(asyncs))... };\r\n    auto it = std::find_if(results.begin(), results.end(),\r\n        [](auto&amp;&amp; eptr) { return eptr; });\r\n    if (it != results.end()) std::rethrow_exception(*it);\r\n}\r\n<\/pre>\n<p>Okay, we finally did it: We wrote a <code>when_<wbr \/>all_<wbr \/>complete<\/code> which runs all of the awaitables to completion.\u00b9 If any of the awaitables throws an exception, then the first such exception is reported.<\/p>\n<p>We&#8217;ll wrap up with some final thoughts next time.<\/p>\n<p>\u00b9 Now, if any of the awaitables throws in its awaiter because it is unable to await at all, then we won&#8217;t wait for completion after all because the awaiter failed at its job. But we have no way of knowing whether any exception that is thrown from the awaiter means &#8220;I awaited successfully, and it completed in a failure state&#8221;, or whether it means &#8220;I couldn&#8217;t even await!&#8221; We just have to do the best we can with the information we have. At least we fulfilled our end of the deal: We did wait for the awaitable to run to completion. If it decided to report failure before finishing its work, that&#8217;s the awaitable&#8217;s problem.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Making sure to wrap the right thing.<\/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-108402","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Making sure to wrap the right thing.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108402","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=108402"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108402\/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=108402"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108402"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108402"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}