-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Labels
Description
I'm not actually sure that the parameter is destroyed twice, but that's how the bug first appeared.
Consider:
#include <coroutine>
#include <utility>
#include <stdio.h>
// Boilerplate based on https://theshoemaker.de/posts/yet-another-cpp-coroutine-tutorial
class Task {
public:
struct FinalAwaiter {
bool await_ready() const noexcept { return false; }
template <typename P> auto await_suspend(std::coroutine_handle<P> handle) noexcept {
return handle.promise().continuation;
}
void await_resume() const noexcept { }
};
struct Promise {
std::coroutine_handle<> continuation;
Task get_return_object() {
return Task { std::coroutine_handle<Promise>::from_promise(*this) };
}
void unhandled_exception() noexcept { }
void return_void() noexcept { }
std::suspend_always initial_suspend() noexcept { return {}; }
FinalAwaiter final_suspend() noexcept { return {}; }
};
using promise_type = Promise;
Task() = default;
~Task() { if (handle_) { handle_.destroy(); } }
struct Awaiter {
std::coroutine_handle<Promise> handle;
bool await_ready() const noexcept { return !handle || handle.done(); }
auto await_suspend(std::coroutine_handle<> calling) noexcept {
handle.promise().continuation = calling;
return handle;
}
void await_resume() const noexcept { }
};
auto operator co_await() noexcept { return Awaiter{handle_}; }
void run() {
handle_.promise().continuation = std::noop_coroutine();
handle_.resume();
}
private:
explicit Task(std::coroutine_handle<Promise> handle) : handle_(handle) { }
std::coroutine_handle<Promise> handle_;
};
// The interesting part starts here.
namespace {
struct Cleanup {
Cleanup() : is_engaged(true) {}
Cleanup(Cleanup&& other) { is_engaged = true; other.is_engaged = false; }
~Cleanup() { if (is_engaged) { printf("CLEANUP\n"); } }
bool is_engaged;
};
}
Task hello(Cleanup c) {
printf("HELLO\n");
co_return;
}
Task foo(Cleanup c) { co_await hello(std::move(c)); }
int main() {
Task t = foo(Cleanup());
t.run();
}
$ clang++ /tmp/a.cc -std=c++20 -O0 && ./a.out
HELLO
CLEANUP
$ clang++ /tmp/a.cc -std=c++20 -O1 && ./a.out
HELLO
CLEANUP
CLEANUP
The expectation is that there is only one "engaged" Cleanup object, and so we should only see that printed once.
The anonymous namespace seems to be an important factor.