https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121219
Iain Sandoe <iains at gcc dot gnu.org> changed: What |Removed |Added ---------------------------------------------------------------------------- CC| |jason at gcc dot gnu.org --- Comment #2 from Iain Sandoe <iains at gcc dot gnu.org> --- So it seems that the presence of the return of the g_r_o_o_a_f is interfering with NVRO and that is breaking an expected guarantee (copy elision). I am somewhat concerned that there's no specific requirement that the g_r_o_o_a_f should return the same type as g_r_o. So it could be possible that one requires conversion and one does not. http://eel.is/c++draft/dcl.fct.def.coroutine#11 @jason - do we still have more tweaks needed to CWG2563? ==== To see the issue it is enough to include/exclude the g_r_o_o_a_f. struct Task { struct promise_type; using handle_type = std::coroutine_handle<promise_type>; struct promise_type { Task* task_; int result_; static void* operator new(std::size_t size) noexcept { void* p = ::operator new(size, std::nothrow); std::cerr << "operator new (no arg) " << size << " -> " << p << std::endl; return p; } static void operator delete(void* ptr) noexcept { return ::operator delete(ptr, std::nothrow); } #if 1 // change to 0 to fix crash static Task get_return_object_on_allocation_failure() noexcept { std::cerr << "get_return_object_on_allocation_failure" << std::endl; return Task(nullptr); } #endif auto get_return_object() { std::cerr << "get_return_object" << std::endl; return Task{handle_type::from_promise(*this)}; } auto initial_suspend() { std::cerr << "initial_suspend" << std::endl; return std::suspend_always{}; } auto final_suspend() noexcept { std::cerr << "final_suspend" << std::endl; return std::suspend_never{}; // Coroutine auto-destructs } ~promise_type() { if (task_) { std::cerr << "promise_type destructor: Clearing Task handle" << std::endl; task_->h_ = nullptr; } } void unhandled_exception() { std::cerr << "unhandled_exception" << std::endl; std::terminate(); } void return_value(int value) { std::cerr << "return_value: " << value << std::endl; result_ = value; if (task_) { task_->result_ = value; task_->completed_ = true; } } }; handle_type h_; int result_; bool completed_ = false; Task(handle_type h) : h_(h) { std::cerr << "Task constructor" << std::endl; if (h_) { h_.promise().task_ = this; // Link promise to Task } } ~Task() { std::cerr << "~Task destructor" << std::endl; // Only destroy handle if still valid (coroutine not completed) if (h_) { std::cerr << "Destroying coroutine handle" << std::endl; h_.destroy(); } } bool done() const { return completed_ || !h_ || h_.done(); } void resume() { std::cerr << "Resuming task" << std::endl; if (h_) h_.resume(); } int result() const { if (!done()) throw std::runtime_error("Result not available"); return result_; } };