{"id":104155,"date":"2020-09-02T07:00:00","date_gmt":"2020-09-02T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104155"},"modified":"2020-09-09T06:09:20","modified_gmt":"2020-09-09T13:09:20","slug":"20200902-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200902-00\/?p=104155","title":{"rendered":"Synthesizing a when_all coroutine from pieces you already have"},"content":{"rendered":"<p>C++\/WinRT provides a helper function that takes a bunch of awaitable objects and produces an <code>IAsyncAction<\/code> that completes when all of the awaitable objects have completed.<\/p>\n<p>It has a very simple definition.<\/p>\n<pre>template &lt;typename... T&gt;\r\nWindows::Foundation::IAsyncAction when_all(T... async)\r\n{\r\n    (co_await async, ...);\r\n}\r\n<\/pre>\n<p>Let&#8217;s take this apart.<\/p>\n<p>The opening <code>template&lt;typename... T&gt;<\/code> says that this is a template that takes an arbitrary number of type parameters.<\/p>\n<p>The function prototype is for a function which takes a parameter list of <code>T... async<\/code>. This means that you can pass as many parameters as you like, of whatever type you prefer, and they are accepted by value. The parameter list is given the name <code>async<\/code>.<\/p>\n<p>The body is <code>(co_await async, ...)<\/code>. This is a <i>fold expression<\/i>. If <code>async...<\/code> represents the parameter list <code>async\u2081<\/code>, <code>async\u2082<\/code>, <code>async\u2083<\/code>, <code>async\u2084<\/code>, then<\/p>\n<pre>(co_await async, ...)\r\n<\/pre>\n<p>expands to<\/p>\n<pre>(co_await async\u2081, co_await async\u2082, co_await async\u2083, co_await async\u2084)\r\n<\/pre>\n<p>Usually, fold expressions are used with operators like <code>+<\/code> or <code>||<\/code>:<\/p>\n<pre>(v + ...)\r\n<\/pre>\n<p>expands to<\/p>\n<pre>(v\u2081 + v\u2082 + v\u2083 + v\u2084)\r\n<\/pre>\n<p>and<\/p>\n<pre>(is_even(v) || ...)\r\n<\/pre>\n<p>expands to<\/p>\n<pre>(is_even(v\u2081) || is_even(v\u2082) || is_even(v\u2083) || is_even(v\u2084))\r\n<\/pre>\n<p>for example.\u00b9 Here, we&#8217;re using the comma operator not for anything interesting; it&#8217;s just a way to execute a bunch of stuff.<\/p>\n<p>The end result of this all is that if you write <code>when_all(x, y, z)<\/code>, this becomes<\/p>\n<pre>Windows::Foundation::IAsyncAction when_all(X x, Y y Z z)\r\n{\r\n  (co_await x, co_await y, co_await z);\r\n}\r\n<\/pre>\n<p>This produce a coroutine which awaits <code>x<\/code>, then throws the result away; then awaits <code>y<\/code>, then throws the result away; and finally awaits <code>z<\/code>, then throws the result away. And then the coroutine is finished.<\/p>\n<p><b>Mid-article bonus chatter<\/b>: There are some flaws in the above function. <a title=\"Rough edges in the when_all coroutine, part 1: Empty parameter list\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200903-00\/?p=104160\"> We&#8217;ll look at them next time<\/a>. <b>End of bonus chatter<\/b>.<\/p>\n<p>A customer wanted to know how they could pass a <code>std::vector<\/code> of <code>IAsyncAction<\/code> objects to the <code>when_all<\/code> function.<\/p>\n<p>It reminds me of <a href=\"https:\/\/www.youtube.com\/watch?v=Ijae2WHdc9I\"> the old <i>Sesame Street<\/i> sketch<\/a> where Grover has no trouble counting blocks, but when asked to count some oranges, Grover freezes up. &#8220;I know how to count blocks, but I do not know how to count oranges!&#8221;<\/p>\n<p>I have to confess that as I child, I didn&#8217;t get the joke.<\/p>\n<p>Anyway, we saw how to count blocks (await every object in a parameter list). We just need to count oranges (await every object in a vector).<\/p>\n<pre>std::vector&lt;IAsyncAction&gt; actions = get_actions();\r\nfor (auto&amp;&amp; action : actions) co_await action;\r\n<\/pre>\n<p>We can try to wrap this up in a function:<\/p>\n<pre>template&lt;typename T&gt;\r\nIAsyncAction when_all(T const&amp; container)\r\n{\r\n  for (auto&amp;&amp; v : container) co_await v;\r\n}\r\n<\/pre>\n<p>This doesn&#8217;t work because there is an ambiguity in the case where there is one parameter. Are you trying to await all of the awaitables in a list of length 1? Or is the parameter a container, and you want to await all objects within it?<\/p>\n<p>I&#8217;ll say that if the single parameter has a method named <code>begin<\/code> whose return type is not <code>void<\/code>, then it&#8217;s a container. (I could try to do better by also accepting a free function <code>begin<\/code>, but I&#8217;m feeling lazy.)<\/p>\n<pre>template&lt;typename T&gt;\r\nauto when_all(T&amp;&amp; container) -&gt;\r\n  std::enable_if_t&lt;sizeof(container.begin()) &gt;= 0, IAsyncAction&gt;\r\n{\r\n  for (auto&amp;&amp; v : container) co_await v;\r\n}\r\n<\/pre>\n<p>I&#8217;m using <code>sizeof<\/code> as a way to create a constant <code>true<\/code> value from a dependent type, so it can be tested with <code>std::enable_if_t<\/code>. We know that the container&#8217;s iterator must be a complete type because we&#8217;re going to use it in the <code>for<\/code> loop.<\/p>\n<p>We might also want to support a range expressed as two input iterators.\u00b2<\/p>\n<pre>template&lt;typename Iter&gt;\r\nstd::enable_if_t&lt;\r\n  std::is_convertible_v&lt;\r\n    typename std::iterator_traits&lt;Iter&gt;::iterator_category,\r\n    std::input_iterator_tag&gt;, IAsyncAction&gt;\r\nwhen_all(Iter begin, Iter end)\r\n{\r\n  for (; begin != end; ++begin) co_await *begin;\r\n}\r\n<\/pre>\n<p>In all of these cases, you need to make sure to keep the container or range alive until after the <code>co_await when_all(...)<\/code> completes.<\/p>\n<p>Whatever way you come up with to express a collection of awaitable objects, you can write a function that accepts that collection and awaits each object in the collection.<\/p>\n<p>Go ahead and count oranges.<\/p>\n<p>\u00b9 More precisely, they expand to<\/p>\n<pre>(v\u2081 + (v\u2082 + (v\u2083 + v\u2084)))\r\n<\/pre>\n<p>and<\/p>\n<pre>(is_even(v\u2081) || (is_even(v\u2082) || (is_even(v\u2083) || is_even(v\u2084))))\r\n<\/pre>\n<p>If you want the left-associative version, then you need to put the ellipsis on the left.<\/p>\n<pre>(... + v)\r\n(... || is_even(v))\r\n<\/pre>\n<p>\u00b2 For extra flexibility, we could implicitly convert the second argument to match the first.<\/p>\n<pre>\/\/ C++17\r\nwhen_all(Iter begin, std::enable_if_t&lt;true, Iter&gt; end)\r\n\r\n\/\/ C++20\r\nwhen_all(Iter begin, std::type_identity_t&lt;Iter&gt; end)\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>You just need to await on all of them.<\/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-104155","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You just need to await on all of them.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104155","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=104155"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104155\/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=104155"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104155"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104155"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}