{"id":105019,"date":"2021-03-30T07:00:00","date_gmt":"2021-03-30T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105019"},"modified":"2021-04-01T07:57:43","modified_gmt":"2021-04-01T14:57:43","slug":"20210330-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210330-00\/?p=105019","title":{"rendered":"C++ coroutines: Basic implementation of a promise type"},"content":{"rendered":"<p>Last time, we <a title=\"C++ coroutines: The mental model for coroutine promises \" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210329-00\/?p=105015\"> diagrammed out how the pieces of a coroutine fit together<\/a>. Today we&#8217;ll fill in the diagram with code.<\/p>\n<p>Fortunately, most of the hard work has already been done for us by the <code>result_<wbr \/>holder<\/code> class we already wrote. We just need to adapt it to the format required by the coroutine specification.<\/p>\n<pre>namespace std::experimental\r\n{\r\n    template &lt;typename T, typename... Args&gt;\r\n    struct coroutine_traits&lt;result_holder&lt;T&gt;, Args...&gt;\r\n    {\r\n        struct promise_type\r\n        {\r\n            result_holder&lt;T&gt; holder;\r\n\r\n            result_holder&lt;T&gt; get_return_object() const noexcept\r\n            {\r\n                return holder;\r\n            }\r\n\r\n            void return_value(T const&amp; v) const\r\n            {\r\n                holder.set_result(v);\r\n            }\r\n\r\n            void unhandled_exception() const noexcept\r\n            {\r\n                holder.set_exception(std::current_exception());\r\n            }\r\n\r\n            suspend_never initial_suspend() const noexcept\r\n            {\r\n                return{};\r\n            }\r\n\r\n            suspend_never final_suspend() const noexcept\r\n            {\r\n                return{};\r\n            }\r\n        };\r\n    };\r\n}\r\n<\/pre>\n<p>When the compiler encounters a function which contains a <code>co_await<\/code> or <code>co_return<\/code>, it realizes that it&#8217;s dealing with a coroutine. It collects the following:<\/p>\n<ul>\n<li>The return type of the function.<\/li>\n<li>The type of <code>*this<\/code>, if it&#8217;s defined as an instance member function.<\/li>\n<li>The types of the parameters, if any.<\/li>\n<\/ul>\n<p>It takes all of these types and looks for a <code>coroutine_<wbr \/>traits<\/code> specialization that matches it. For example, given<\/p>\n<pre>ReturnType FreeFunction(ArgType1&amp; arg1, ArgType2 arg2);\r\n<\/pre>\n<p>the compiler looks for the type<\/p>\n<pre>std::experimental::coroutine_traits&lt;\r\n    ReturnType, ArgType1&amp;, ArgType2&gt;\r\n<\/pre>\n<p>For an instance method<\/p>\n<pre>ReturnType <span style=\"border: solid 1px black;\">SomeClass<\/span>::Member(ArgType1&amp; arg1, ArgType2 arg2) <span style=\"border: solid 1px black;\">const<\/span>;\r\n<\/pre>\n<p>the compiler looks for<\/p>\n<pre>std::experimental::coroutine_traits&lt;\r\n    ReturnType, <span style=\"border: solid 1px black;\">SomeClass<\/span> <span style=\"border: solid 1px black;\">const<\/span>&amp;, ArgType1&amp;, ArgType2&gt;\r\n<\/pre>\n<p>In practice, few coroutines care about anything other than the return type, so you will generally see the second and subsequent template type parameters declared but ignored.\u00b9<\/p>\n<p>The specialized <code>coroutine_<wbr \/>traits<\/code> must have a nested type called <code>promise_<wbr \/>type<\/code>. This could be defined inline or it could be an alias (via <code>typedef<\/code> or <code>using<\/code>) for another type define elsewhere.<\/p>\n<p>The <code>promise_<wbr \/>type<\/code> is the promise object that is stored inside the coroutine state. The <code>get_<wbr \/>return_<wbr \/>object()<\/code> method is called to create the thing that is returned to the caller of the coroutine. In our case, we want to return a copy of our <code>result_<wbr \/>holder<\/code>: The <code>result_<wbr \/>holder<\/code> state becomes the way that the coroutine and the caller communicate with each other:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<th>Coroutine state<\/th>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<th>Caller<\/th>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black;\">bookkeeping<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>promise<\/td>\n<td style=\"margin-right: 1ex;\">\n<div style=\"border: solid 1px black; border-right: none;\">\u00a0<\/div>\n<\/td>\n<td style=\"border: solid 1px black; border-top: none;\"><code>holder<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px black;\"><code>result_<wbr \/>holder<\/code> state<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid 1px black;\"><code>holder<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black;\">stack frame<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>When the coroutine reaches its <code>co_return<\/code>, the compiler calls <code>p.return_value()<\/code> with the returned value, which we pass onward to the holder by calling <code>set_value<\/code>. (If there is no returned value, then the compiler uses <code>p.return_void()<\/code>.)\u00b2 That will in turn update the state, and the state will release any waiting coroutines, which resumes the caller.<\/p>\n<p>On the other hand, if an exception escapes the coroutine, then the compiler will call <code>p.unhandled_<wbr \/>exception()<\/code>, and we deal with the exception by stowing it in the <code>holder<\/code>. Again, this will update the state, and the state will release any waiting coroutines, which resumes the caller. And when the caller performs an <code>await_result<\/code> to obtain the result of the <code>co_await<\/code>, our <code>state<\/code> object rethrows the exception.<\/p>\n<p>So that&#8217;s the magic of how the result of the coroutine (either a return value or an exception) gets transferred from the coroutine back to the awaiter.<\/p>\n<p>Wait, what are these last two method <code>initial_<wbr \/>suspend<\/code> and <code>final_<wbr \/>suspend<\/code>? We&#8217;ll look at those next time.<\/p>\n<p>\u00b9 You could create fancy coroutines that change their implementation depending on what the parameters are. For example, you might define a marker type like<\/p>\n<pre>struct with_sugar_t {};\r\ninline constexpr with_sugar_t with_sugar{};\r\n<\/pre>\n<p>and then have a special version of the <code>result_<wbr \/>holder<\/code> coroutine promise that is used if the coroutine function is declared as<\/p>\n<pre>result_holder&lt;int&gt; Nicely(with_sugar_t);\r\n<\/pre>\n<p>Okay, so I don&#8217;t have a really good motivation for this feature, but it does exist.<\/p>\n<p>\u00b2 It is illegal to have both <code>return_<wbr \/>value<\/code> and <code>return_<wbr \/>void<\/code> in a promise type, even if one of them is removed by SFINAE:<\/p>\n<pre>            template&lt;typename Dummy = void&gt;\r\n            std::enable_if_t&lt;std::is_same_v&lt;T, void&gt;, Dummy&gt;\r\n                return_void() const\r\n            {\r\n                holder.set_result(); \r\n            }\r\n\r\n            template&lt;typename Dummy = void&gt;\r\n            std::enable_if_t&lt;!std::is_same_v&lt;T, void&gt;, Dummy&gt;\r\n                return_value(T const&amp; value) const\r\n            {\r\n                holder.set_result(value); \r\n            }\r\n<\/pre>\n<p>The reason is that in order for the compiler to perform substitution in order to determine which methods are callable, it needs to know what was passed to all of the <code>co_return<\/code> statements, so it can try substituting them into <code>return_<wbr \/>value<\/code>&#8216;s parameter and see which ones succeed. But it hasn&#8217;t started compiling the coroutine function body yet, so it doesn&#8217;t know what to try to substitute for <code>value<\/code>.<\/p>\n<p>This is a frustrating limitation because it prevents you from writing a single promise that covers both <code>void<\/code>-completing coroutines and value-completing coroutines. You always have to make two types, one for <code>return_<wbr \/>void<\/code> and another for <code>return_<wbr \/>value<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Filling in the diagram with 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-105019","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Filling in the diagram with code.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105019","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=105019"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105019\/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=105019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}