{"id":106160,"date":"2022-01-14T07:00:00","date_gmt":"2022-01-14T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106160"},"modified":"2022-01-14T06:50:15","modified_gmt":"2022-01-14T14:50:15","slug":"20220114-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220114-00\/?p=106160","title":{"rendered":"Resolving confusion over how to return from a C++ coroutine"},"content":{"rendered":"<p>A customer was having trouble writing a coroutine using C++\/WinRT. This function compiled successfully:<\/p>\n<pre>winrt::IAsyncOperation&lt;bool&gt; HelperFunction()\r\n{\r\n    \/* no other co_return statements *\/\r\n\r\n    co_return true;\r\n}\r\n<\/pre>\n<p>But once they added a condition, it stopped compiling successfully:<\/p>\n<pre>winrt::IAsyncOperation&lt;bool&gt; MainFunction()\r\n{\r\n    ...\r\n    if (condition) {\r\n        ...\r\n        co_return HelperFunction(); \/\/ Fails to compile\r\n    }\r\n\r\n    co_return false;\r\n}\r\n<\/pre>\n<p>The error message is<\/p>\n<pre style=\"white-space: pre-wrap;\">error C2664: 'void std::<wbr \/>experimental::<wbr \/>coroutine_traits&lt;<wbr \/>winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IAsyncOperation&lt;<wbr \/>bool&gt;&gt;::<wbr \/>promise_type::<wbr \/>return_value(<wbr \/>TResult &amp;&amp;) noexcept': cannot convert argument 1 from 'winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IAsyncOperation&lt;<wbr \/>bool&gt;' to 'TResult &amp;&amp;'\r\nwith\r\n[\r\n    TResult=bool\r\n]\r\nmessage : Reason: cannot convert from 'winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IAsyncOperation&lt;<wbr \/>bool&gt;' to 'TResult'\r\nwith\r\n[\r\n    TResult=bool\r\n]\r\nmessage : No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called\r\n<\/pre>\n<p>What&#8217;s going on here?<\/p>\n<p>The <code>co_return<\/code> statement takes the thing being co-returned and passes it to the promise&#8217;s <code>return_<wbr \/>value<\/code> method (or if you <code>co_return<\/code> nothing, calls the promise&#8217;s <code>return_<wbr \/>void<\/code> method with no parameters). Although the language imposes no semantics upon this action, the intent is that this is how you produce the asynchronous result of the coroutine: The asynchronous result of the coroutine is the thing that the caller gets when they <code>co_await<\/code> the coroutine.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Declaration<\/th>\n<td><code>IAsyncAction f()<\/code><\/td>\n<td><code>IAsyncOperation&lt;T&gt; f()<\/code><\/td>\n<td><code>fire_and_forget f()<\/code><\/td>\n<\/tr>\n<tr>\n<th>Return type<\/th>\n<td><code>IAsyncAction<\/code><\/td>\n<td><code>IAsyncOperation&lt;T&gt;<\/code><\/td>\n<td><code>fire_and_forget<\/code><\/td>\n<\/tr>\n<tr>\n<th>Using <code>return T<\/code><\/th>\n<td><code>return IAsyncAction(...);<\/code><\/td>\n<td><code>return IAsyncOperation&lt;T&gt;(...);<\/code><\/td>\n<td><code>return {};<\/code><\/td>\n<\/tr>\n<tr>\n<th>Result type<\/th>\n<td><code>void<\/code><\/td>\n<td><code>T<\/code><\/td>\n<td><code>void<\/code><\/td>\n<\/tr>\n<tr>\n<th>Using <code>co_return T<\/code><\/th>\n<td><code>co_return;<\/code><\/td>\n<td><code>co_return T(...);<\/code><\/td>\n<td><code>co_return;<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If you use the <code>return<\/code> keyword, then you must return the coroutine type. This follows the rules of the C++ language that you&#8217;re familiar with: If your function says that it returns something, then the thing you <code>return<\/code> needs to be that something (or something convertible to it).<\/p>\n<p>What&#8217;s new for coroutines is the <code>co_return<\/code> keyword. If you use the <code>co_return<\/code> keyword, then the thing you <code>co_return<\/code> needs to be the coroutine <i>result<\/i> (or something convertible to it).<\/p>\n<p>You have to pick a side: Either <code>return<\/code> everywhere in your function or <code>co_return<\/code> everywhere in your function. You can&#8217;t mix-and-match. That would result in <code>Main\u00adFunction()<\/code> being part-coroutine and part not-coroutine, which the language doesn&#8217;t support. You&#8217;re either a coroutine or you&#8217;re not.<\/p>\n<p>Writing <code>co_return HelperFunction();<\/code> is trying to return an <code>IAsyncOperation&lt;<wbr \/>bool&gt;<\/code> as the result of the coroutine. But the coroutine result isn&#8217;t a <code>IAsyncOperation&lt;<wbr \/>bool&gt;<\/code>. It&#8217;s just a <code>bool<\/code>.<\/p>\n<p>And that&#8217;s what the compiler error message is trying to say, with compiler-colored glasses: &#8220;Cannot convert <code>IAsyncOperation&lt;bool&gt;<\/code> to <code>bool<\/code>.&#8221; You <code>co_return<\/code>ed an <code>IAsyncOperation&lt;bool&gt;<\/code>, but the only thing that the <code>IAsyncOperation&lt;bool&gt;<\/code> knows how to <code>co_return<\/code> is a <code>bool<\/code>, and the compiler is unable to perform the conversion.<\/p>\n<p>What you need to do is <code>co_return<\/code> a <code>bool<\/code> somehow.<\/p>\n<p>The customer discovered on their own that adding a <code>co_await<\/code> fixed the problem:<\/p>\n<pre>winrt::IAsyncOperation&lt;bool&gt; MainFunction()\r\n{\r\n    ...\r\n    if (condition) {\r\n        co_return <span style=\"color: blue;\">co_await<\/span> HelperFunction(); \/\/ added co_await\r\n    }\r\n\r\n    co_return false;\r\n}\r\n<\/pre>\n<p>But the customer was unsure of themselves. &#8220;Why is <code>co_await<\/code> needed? Are there any unintended consequences?&#8221;<\/p>\n<p>The <code>co_await<\/code> keyword instructs the compiler to generate code to suspend the current coroutine <code>Main\u00adFunction()<\/code> and resume execution when <code>Helper\u00adFunction()<\/code> produces a result. Since <code>Helper\u00adFunction()<\/code> is itself a <code>IAsyncOperation&lt;bool&gt;<\/code>, that result will also be a <code>bool<\/code>. You can then <code>co_return<\/code> that <code>bool<\/code>, which makes it the result of the <code>Main\u00adFunction()<\/code> coroutine.<\/p>\n<p><b>Bonus chatter<\/b>: The customer also found, in their experimentation, that this version also compiled successfully:<\/p>\n<pre>winrt::IAsyncOperation&lt;bool&gt; MainFunction()\r\n{\r\n    if (condition) co_return true;\r\n    return false;\r\n}\r\n<\/pre>\n<p>How does this work? It seems to be breaking the rules above, because we are using <code>return<\/code> with the result type, and we&#8217;re mixing <code>return<\/code> and <code>co_return<\/code> within the same function body.<\/p>\n<p>Yes, this code should not compile.<\/p>\n<p>What you&#8217;re seeing is a backward compatibility behavior of the Visual C++ compiler: When coroutines were being developed, the original idea was to overload the <code>return<\/code>. If you <code>return<\/code>ed something that matched the declared return type, then it was treated as producing the return value of the function. But if you <code>return<\/code>ed something that matched the result type, then the function transformed into a coroutine, and you were producing the <i>result<\/i> of the coroutine.<\/p>\n<p>My guess is that this syntax was chosen to align with the C# and JavaScript languages, both of which overload the <code>return<\/code> statement in this way.<\/p>\n<p>Ultimately, however, the ambiguity was too much,\u00b9 and the coroutine specification that was ratified created new keywords to make explicit whether the function body was a classic function or a coroutine. The Visual C++ compiler retains the old syntax for backward compatibility with existing code that was written to the pre-ratified standard.<\/p>\n<p>It appears that an artifact of this backward compatibility is that the compiler accepts the reverse error:<\/p>\n<pre>winrt::IAsyncOperation&lt;bool&gt; MainFunction()\r\n{\r\n    co_return HelperFunction();\r\n}\r\n<\/pre>\n<p>This uses <code>co_return<\/code> with the return type instead of the result type. Somehow, the compiler accepts it even though it&#8217;s not required by backward compatibility. (My guess is that there&#8217;s some compatibility code that merges <code>return<\/code> and <code>co_return<\/code>, and while that takes care of the compatibility issue, it also makes the compiler accept other things inadvertently.<\/p>\n<p>It also seems that the <code>\/permissive-<\/code> flag doesn&#8217;t turn off this compatibility behavior.<\/p>\n<p>\u00b9 Consider a class that is designed to be the return type of a coroutine.<\/p>\n<pre>template&lt;typename T&gt;\r\nclass task\r\n{\r\n    \/* stuff required to be a coroutine return type *\/\r\n};\r\n\r\ntask&lt;int&gt; calculate()\r\n{\r\n    \/* do some calculations *\/\r\n    co_return value;\r\n}\r\n<\/pre>\n<p>This hypothetical <code>task<\/code> type supports being used as the return type of a coroutine, and our sketch of a <code>calculate()<\/code> function calculates a value and <code>co_return<\/code>s it.<\/p>\n<p>But suppose we added a new constructor:<\/p>\n<pre>template&lt;typename T&gt;\r\nclass task\r\n{\r\npublic:\r\n    \/* create a task that has already completed with a value *\/\r\n    task(T const&amp; resolved);\r\n\r\n    \/* existing stuff required to be a coroutine return type *\/\r\n};\r\n<\/pre>\n<p>This new constructor provides a way to create an already-completed task by passing the result directly to the constructor.<\/p>\n<p>Given this new constructor, the following code would become ambiguous under the pre-standardized version that used <code>return<\/code> for both normal return and coroutine return:<\/p>\n<pre>task&lt;int&gt; calculate()\r\n{\r\n    \/* do some calculations *\/\r\n    return value;\r\n}\r\n<\/pre>\n<p>Is this a plain non-coroutine function that returns a task with the <code>resolved<\/code> constructor? Or is this a coroutine function that produces a task from the coroutine promise via <code>return_value()<\/code>? Both interpretations would be valid here.<\/p>\n<p>Changing the keyword to <code>co_return<\/code> for coroutines removes this ambiguity.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You have a few options, but you have to stay with it.<\/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-106160","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You have a few options, but you have to stay with it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106160","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=106160"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106160\/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=106160"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106160"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106160"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}