I am trying to understand why the example implementation in this paper needs to delete the move constructor of the Owner
awaiter (I get that it should obviously not be a movable type, but that is not what this question is about). I have tried to distil the situation into the following MCVE:
#include <cassert>
#include <coroutine>
#include <iostream>
#include <memory>
#include <utility>
template <typename T>
struct generator {
struct promise_type;
using handle = std::coroutine_handle<promise_type>;
struct promise_type {
T* ptr;
void return_void() {}
std::suspend_always initial_suspend() { return {}; }
generator get_return_object() {
return { handle::from_promise(*this) };
}
void unhandled_exception() {}
struct owner_await : std::suspend_always {
T copy;
owner_await(const T& val, T*& ptr)
: copy(val) { ptr = © }
// THE FOLLOWING LINE IS IMPORTANT:
owner_await(owner_await&&) = delete;
};
std::suspend_always yield_value(T&& val) {
ptr = &val;
return {};
}
owner_await yield_value(const T& val) {
return { val, ptr };
}
std::suspend_always final_suspend() noexcept { return {}; }
T&& get() { return std::move(*ptr); }
};
handle h;
generator(handle h) : h(h) {}
generator(generator&& other) : h(std::exchange(other.h, {})) {}
generator& operator=(generator other) {
using std::swap;
swap(h, other.h);
return *this;
}
~generator() { if (h) h.destroy(); }
void next() { h.resume(); }
T&& get() { return h.promise().get(); }
};
/*****************************************************************************/
generator<int> gen() {
int i = 1;
co_yield i;
}
int main() {
auto g = gen();
g.next();
std::cout << g.get() << '\n';
}
This code works as intended, but if I comment out the line after // THE FOLLOWING LINE IS IMPORTANT
, it stops working; namely, instead of the correct number I get garbage, and valgrind complains about an invalid read.
Note that the move constructor doesn't have to be deleted. The code also works if there is a user-defined move constructor, or if there is a user-defined destructor. In other words, it seems to work if and only if the owner_await
type is not trivially copyable.
My question is, is this really what's happening, and if so, why? What part of the standard says that a trivially copyable awaiter can be moved around, while other awaiters cannot?
Note: I have tried this both with gcc-12.2 and clang-15 with the same result.