{"id":108377,"date":"2023-06-28T07:00:00","date_gmt":"2023-06-28T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108377"},"modified":"2023-06-19T06:30:13","modified_gmt":"2023-06-19T13:30:13","slug":"20230628-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230628-00\/?p=108377","title":{"rendered":"How to wait for multiple C++ coroutines to complete before propagating failure, false hope"},"content":{"rendered":"<p>Last time, <a title=\"How to wait for multiple C++ coroutines to complete before propagating failure, unhelpful lambda\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230627-00\/?p=108375\"> we tried to use a lambda in conjunction with a parameter pack fold expression in order to iterate over the parameter pack<\/a>. Unfortunately, a lambda creates a nested function call, and the lambda coroutine&#8217;s return type was <code>IAsyncAction<\/code>, which has a policy in C++\/WinRT of resuming in the same apartment context, even if the underlying coroutine completed in some other context. This erases any apartment-changing effects of the original awaitable, which is a behavior change from <code>when_all<\/code>.<\/p>\n<p>What&#8217;s a parameter that represents a variable-length thing that I can still iterate over? I know: <code>initializer_list<\/code>!<\/p>\n<pre>template&lt;typename... T&gt;\r\nIAsyncAction when_all_complete(T... asyncs)\r\n{\r\n    return when_all_complete_list({ asyncs... });\r\n}\r\n\r\ntemplate&lt;typename. T&gt;\r\nIAsyncAction when_all_complete_list(\r\n    std::initializer_list&lt;T&gt; asyncs)\r\n{\r\n    std::exception_ptr eptr;\r\n\r\n    for (auto&amp;&amp; async : asyncs) {\r\n        try {\r\n            co_await async;\r\n        } catch (...) {\r\n            if (!eptr) {\r\n                eptr = std::current_exception();\r\n            }\r\n        }\r\n    }\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>We take the parameter pack and pass it to a helper function <code>when_<wbr \/>all_<wbr \/>complete_<wbr \/>list<\/code>, which reinterprets it as a <code>std::<wbr \/>initializer_<wbr \/>list<\/code>. The nice thing aobut a <code>std::<wbr \/>initializer_<wbr \/>list<\/code> is that you can iterate over it, and in the loop body, we put our <code>try<\/code>\/<wbr \/><code>co_await<\/code>\/<wbr \/><code>catch<\/code> boilerplate.<\/p>\n<p>I thought I had done it. It passed a basic test like<\/p>\n<pre>IAsyncAction Sample(int s)\r\n{\r\n    co_await winrt::resume_after(std::chrono::seconds(s));\r\n    printf(\"Finished after %d seconds\\n\", s);\r\n}\r\n\r\nIAsyncAction test()\r\n{\r\n    co_await when_all_complete(\r\n        Sample(1), Sample(2), Sample(3)\r\n    );\r\n    printf(\"All complete\\n\");\r\n}\r\n<\/pre>\n<p>But then I realized that I had blown it.<\/p>\n<p>Do you see how I fooled myself?<\/p>\n<p>The conversion of the parameter pack to a <code>std::initializer_list<\/code> succeeds only if all of the types in the parameter pack are the same. And it so happens that in my basic test, all of the types are the same, namely <code>IAsyncAction<\/code>. If I had tried<\/p>\n<pre>    co_await when_all_complete(\r\n        winrt::resume_background(),\r\n        Sample(1), Sample(2), Sample(3)\r\n    );\r\n<\/pre>\n<p>then I would have gotten a compiler error complaining that it couldn&#8217;t make a <code>std::initializer_<wbr \/>list<\/code> out of a heterogeneous list.<\/p>\n<p>Well that was unfortunate.<\/p>\n<p>We&#8217;ll try again next time.<\/p>\n<p><b>Bonus chatter<\/b>: Another problem is that constructing the initializer list requires that the <code>T<\/code>&#8216;s all be copyable.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Trying to iterate over a pack.<\/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-108377","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Trying to iterate over a pack.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108377","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=108377"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108377\/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=108377"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108377"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108377"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}