co_await needs an after-resume check

TL;DR: Y y = co_await(???) f(x); should unroll into

auto e = f(x);
if (!e.await_ready()) {
      ... suspend ...
      if (e.await_suspend(h)) return;
resume:
      ... resume ...
}
if (???) co_return Cancelled{};  // <<< THIS
Y y = e.await_resume();

because otherwise I have to write boilerplate.


Here come the walls of code


Often we cannot write code like this

class C {
    X x_;
    Y y_;
    Future<int> coro() {
        x_ = co_await f();
        y_ = co_await g();
        co_return 42;
    }
};

because *this could be destroyed while the coroutine is suspended.

So we use weak_ptr

class C : public enable_shared_from_this<C> {
    X x_;
    Y y_;
    Future<int> coro() {
        auto weak_this = weak_from_this();
        X tmp_x = co_await f();
        auto this_pin = weak_this.lock();
        if (!this_pin) co_return Cancelled{};
        x_ = std::move(tmp_x);

        this_pin.reset();  // Don't hold the pin while the coroutine is suspended.
        Y tmp_y = co_await g();
        this_pin = weak_this.lock();
        if (!this_pin) co_return Cancelled{};
        y_ = std::move(tmp_y);

        co_return 42;
    }
};

Or we could use a completely different liveness check, but in the end it would be something like

Future<T> coro() {
    auto is_alive = ... this ...;  // Add the liveness indicator to the coroutine state.

    X tmp_x = co_await f();
    if (... !is_alive ...)  // Check is |this| is still valid. {
        // Return something else than |T| in |Future<T>|.
        co_return Cancelled{};                                    
    }
    x_ = std::move(tmp_x);
}

Theoretically, we could do this with await_transform and exceptions:

template<typename T>
class SpecialFuture {
    class promise_type {
        std::function<bool()> is_alive;
        std::shared_ptr<void> pin;

        template<typename C>
        auto await_transform(weak_ptr<C> weak_ptr) {
            is_alive = [=]{
                pin = weak_ptr.lock();
                return !!pin;
            };
            return suspend_never{};
        }

        struct Cancel{};

        template<typename F>
        auto await_transform(F future) {
            struct wrapper {
                F future;
                promise_type* promise;
                auto await_ready() { return future.await_ready(); };
                auto await_suspend(coroutine_handle<> h) {
                    promise->pin.reset();  // Don't hold the pin while the coroutine is suspended.
                    return future.await_suspend(h);
                };
                auto await_resume() {
                    if (!promise->is_alive()) throw Cancel{};
                    return future.await_resume();
                }
            };
            return wrapper{future, this};
        }

        void unhandled_exception() {
            try {
                throw;
            } catch (Cancel) {
                // OK
            } catch (...) {
                terminate();
            }
        }
    };
};

class C : public enable_shared_from_this<C> {
    X x_;
    Y y_;
    SpecialFuture<int> coro() {
        co_await weak_from_this();  // Initialize |p.is_alive|.

        x_ = co_await f();
        y_ = co_await g();
        co_return 42;
    }
};

But exceptions are not allowed in certain projects, and I wouldn't recommend to replace return with throw anyways.


And I have no idea how it should look like syntax-wise.