0

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 = &copy; }
            // 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.

Nikola Benes
  • 2,372
  • 1
  • 20
  • 33

0 Answers0