背景
我有一个任务类型,可以同时使用co_return
和co_yield
。在LLVM中,任务按预期工作,并通过了一些早期测试。在MSVC和GCC中,代码以相同的方式失败(巧合吗?)。
简要问题
使用以下测试函数:
Task<int> test_yielding()
{
co_yield 1;
co_return 2;
}
从 Task 对象中检索到两个值。
auto a = co_await fn;
auto b = co_await fn;
预期a的值为1,b的值为2。
结果将与a + b == 3
进行测试。
上述测试通过,但是以下测试失败:
auto res = co_await fn + co_await fn
GCC和MSVC的res值都是4,这些值都来自于最后的co_return。据我理解,co_await fn的第一次和第二次调用,无论以何种顺序进行,其结果应该分别为1和2。在MSVC和GCC中,代码会失败,因为它们似乎会重新排序await_resume、return_value和yield_value。
Task<int> test_yielding()
{
co_yield 1;
co_return 2;
}
测试 1 (通过):
Title("Test co_yield + co_return lvalue");
auto fn = test_yielding();
auto a = co_await fn;
auto b = co_await fn;
ASSERT(a + b == 3);
测试2(失败):
Title("Test co_yield + co_return rvalue");
auto fn = test_yielding();
auto res =
(
co_await fn +
co_await fn
);
ASSERT(res == 3);
测试 MSVC 1 的结果 (通过):
---------------------------------
Title Test co_yield + co_return lvalue
---------------------------------
get_return_object: 02F01DA0
initial_suspend: 02F01DA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01DA0
AwaitAwaitable: await_suspend: 02F01DA0
SetCurrent: 02F01DA0
ContinueWith: 02F01DA0
yield_value: 02F01DA0
SetValue: 02F01DA0
YieldAwaitable: await_ready: 02F01DA0
YieldAwaitable: await_suspend: 02F01DA0
ContinueWith: 02F01DA0
AwaitAwaitable: await_resume: 02F01DA0
GetValue: 02F01DA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01DA0
AwaitAwaitable: await_suspend: 02F01DA0
SetCurrent: 02F01DA0
ContinueWith: 02F01DA0
YieldAwaitable: await_resume: 02F01DA0
return_value: 02F01DA0
SetValue: 02F01DA0
final_suspend: 02F01DA0
YieldAwaitable: await_ready: 02F01DA0
YieldAwaitable: await_suspend: 02F01DA0
ContinueWith: 02F01DA0
AwaitAwaitable: await_resume: 02F01DA0
GetValue: 02F01DA0
PASS test_task:323 a + b == 3
[ result = 3, expected = 3 ]
Destroy: 02F01DA0
测试 MSVC 2 的结果(失败):
---------------------------------
Title Test co_yield + co_return rvalue
---------------------------------
get_return_object: 02F01CA0
initial_suspend: 02F01CA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01CA0
AwaitAwaitable: await_suspend: 02F01CA0
SetCurrent: 02F01CA0
ContinueWith: 02F01CA0
yield_value: 02F01CA0
SetValue: 02F01CA0
YieldAwaitable: await_ready: 02F01CA0
YieldAwaitable: await_suspend: 02F01CA0
ContinueWith: 02F01CA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01CA0
AwaitAwaitable: await_suspend: 02F01CA0
SetCurrent: 02F01CA0
ContinueWith: 02F01CA0
YieldAwaitable: await_resume: 02F01CA0
return_value: 02F01CA0
SetValue: 02F01CA0
final_suspend: 02F01CA0
YieldAwaitable: await_ready: 02F01CA0
YieldAwaitable: await_suspend: 02F01CA0
ContinueWith: 02F01CA0
AwaitAwaitable: await_resume: 02F01CA0
GetValue: 02F01CA0
AwaitAwaitable: await_resume: 02F01CA0
GetValue: 02F01CA0
FAIL test_task:342 res == 3
[ result = 4, expected = 3 ]
Destroy: 02F01CA0
如果您查看工作中的MSVC FAIL和MSVC PASS之间的差异(地址已更正),则会出现以下情况:
![enter image description here](https://istack.dev59.com/GigOi.webp)
这清楚地表明以下行已被重新排序:
AwaitAwaitable: await_resume: 02901E20
GetValue: 02901E20
LLVM和GCC的源代码和结果在这里。
观察测试2中GCC失败和LLVM通过之间的差异:
在GCC中发生了非常相似的重新排序。
差异中突出显示的行是由以下源代码生成的:
template <typename Promise>
struct AwaitAwaitable
{
Promise & m_promise;
bool await_ready() const noexcept
{
WriteLine("AwaitAwaitable: ", __func__, ": ", &m_promise);
return false;
}
void await_suspend(default_handle handle) noexcept
{
WriteLine("AwaitAwaitable: ", __func__, ": ", &m_promise);
m_promise.SetCurrent( m_promise.Handle() );
m_promise.ContinueWith( handle );
}
auto await_resume() const noexcept
{
WriteLine("AwaitAwaitable: ", __func__, ": ", &m_promise);
return m_promise.GetValue();
}
};
有人知道这里发生了什么吗,是编译器/库/用户错误吗?
co_await fn
。由于两者都将结果存储在相同的Task<int>
中,因此它会被最后一个值(在本例中为2)覆盖。而auto a = co_await fn
则是在第一个co_await
之后复制结果。我认为这就是正在发生的事情。据我所知,(co_await fn + co_await fn)
行的结果未定义。 - freakishco_await
的任何关于顺序的内容。我想这目前是未定义行为。 - freakish