I was working with gcc 10 and coroutines on wsl 1 on windows 10 and noticed a that the arguments passed to the function that returns the awaiter is destroyed by the time the coroutine is suspended
clang and msvc doesn't do this but keep the temporaries until the expression is completed
code demonstrating the problem:
#include <cstdio>
#include <thread>
#if defined __clang__ || defined _WIN32
#include <experimental/coroutine>
using coro_handle = std::experimental::coroutine_handle<>;
using suspend_never = std::experimental::suspend_never;
#else
#include <coroutine>
using coro_handle = std::coroutine_handle<>;
using suspend_never = std::suspend_never;
#endif // __clang__
struct arg_t
{
arg_t() { printf("arg_t()\n"); }
~arg_t()
{
printf("~arg_t()\n");
}
};
struct awaiter_t
{
awaiter_t() { printf("awaiter_t()\n"); }
~awaiter_t()
{
printf("~awaiter_t()\n");
}
bool await_ready() const noexcept
{
printf("await_ready()\n");
return false;
}
void await_suspend(coro_handle coro)
{
printf("await_suspend()\n");
std::thread([coro]() mutable { std::this_thread::sleep_for(std::chrono::seconds(3)); coro.resume(); }).detach();
}
void await_resume()
{
printf("await_resume()\n");
}
};
struct task_t
{
struct promise_type
{
task_t get_return_object() { return {}; }
suspend_never initial_suspend() noexcept { return {}; }
suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept { std::terminate(); }
void return_void() {}
};
task_t() = default;
};
awaiter_t make_awaiter(const arg_t& arg)
{
return awaiter_t{};
}
task_t test_awaiter()
{
co_await make_awaiter(arg_t{});
}
int main()
{
test_awaiter();
std::this_thread::sleep_for(std::chrono::seconds(4));
return 0;
}
gcc test: https://godbolt.org/z/cT4q8d
compiled with gcc flags : -std=c++20 -Wall -pthread -fcoroutines
output:
arg_t()
awaiter_t()
~arg_t() -> destroyed before await_ready !
await_ready()
await_suspend()
await_resume()
~awaiter_t()
clang test: https://godbolt.org/z/fcrzPM
compiled with clang flags : -std=c++20 -Wall -pthread -stdlib=libc++
output:
arg_t()
awaiter_t()
await_ready()
await_suspend()
await_resume()
~awaiter_t()
~arg_t() -> destroyed after the awaiter is destroyed
msvc also behaves like clang as I experimented here : Do temporaries passed to a function that returns awaitable remains valid after suspension point with co_await
is it a bug in gcc ? or the compiler is free to do whatever it likes here ?