Showing posts with label cpp20_coroutine. Show all posts
Showing posts with label cpp20_coroutine. Show all posts

Oct 4, 2026

[c++23] coroutine with deducing this

C++23 Deducing this: 

A standard C++20 IILE (Immediately Invoked Lambda Expression) coroutine [=](){...}() captures the temporary closure by reference, causing an immediate dangling reference once the statement evaluates. Using the explicit object parameter (this auto) forces the closure and its captures to be passed by value into the coroutine frame, binding their lifetime to the coroutine state.

operator->* Overload: 

Replaces the easily missed trailing (); of an IILE. The operator evaluates left-to-right, perfectly forwarding and immediately invoking the callable. This shifts the execution trigger to the head of the block for immediate visual parsing.

ASYNC Macro: 

Fuses the invocation trick and deducing this into a zero-overhead DSL. It enforces explicit captures (__VA_ARGS__) and strictly defines the promise return type (Coro), completely hiding the C++23 boilerplate.

Execution Flow: 

pool_task always suspends initially. The thread pool queue stores this initial handle. The queue must never automatically repush the handle after resumption; continuation is strictly deferred to the awaited operation's await_suspend logic to prevent catastrophic spin-loops.

class thread_pool {
    queue<std::coroutine_handle<>> work;
    // ...
};

void thread_pool::submit(auto awaitable) {
    // Used after free bug due to capture in anonymous type instance.
    auto task = [=]() -> pool_task {
        // always suspends initially
        co_await awaitable;
        // destroys itself in final suspend
    }();
    work.push(task.handle());
}

Deduce this with copy by value; NOT capture by value.
void thread_pool::submit(auto awaitable) {
    auto task = lambda_invoker() ->* [=](this auto) -> pool_task {
        co_await awaitable;
    };
    work.push(task.handle());
}

struct lambda_invoker {
    auto operator->*(auto&& fn) {
        return std::forward<decltype(fn)>(fn)();
    }
};

Use ->* operator with macro.
void thread_pool::submit(auto awaitable) {
    auto task = ASYNC(pool_task, awaitable) {
        co_await awaitable;
    };
    work.push(task.handle());
}

struct lambda_invoker {
    auto operator->*(auto&& fn) {
        return std::forward<decltype(fn)>(fn)();
    }
};

#define ASYNC(Coro, ...) \
    lambda_invoker() ->* [__VA_ARGS__](this auto) -> Coro
    

Oct 2, 2026

[coroutine] c9 idiom of cancellation.

1. Who creates the coroutine?
             ↓
2. Who owns the coroutine_handle?
             ↓
3. Who resumes it?
             ↓
4. What event causes the resume?
             ↓
5. Who destroys the frame?

// Minimal single-threaded model of C9-style cancellation.
//
// Key difference from the stop_token version: a cancelled coroutine is NEVER
// resumed. The cancel callback destroys every frame of the logical thread
// (leaf -> entry), running destructors, and then resumes an "on_exit" handle
// so the owner learns the thread is gone. No code checks a token.
//
// Name map to google3/util/c9/internal:
//   PromiseLink      -> CoroutinePromiseLink (waiter link, co_thread)
//   CoThread         -> CoThread (cancelled_ AsyncNotification, on_exit)
//   SuspensionToken  -> SuspensionToken (BecameReady/Cancelled/AlreadyCancelled,
//                       DestroyCallStack)
//   Scheduler        -> ThreadState ready queue + Schedule/CancelWaiter
//   Yield / CondVar  -> leaf awaitables like c9::Event::Awaitable

#include <algorithm>
#include <coroutine>
#include <deque>
#include <exception>
#include <functional>
#include <iostream>
#include <optional>
#include <type_traits>
#include <utility>

struct FrameCounter {
  static inline int live = 0;
  FrameCounter() { ++live; }
  ~FrameCounter() { --live; }
};

class CoThread;

// Base of every Co<T> promise. The `waiter` pointers form the intrusive
// "coroutine call stack" that cancellation walks and destroys.
struct PromiseLink {
  std::coroutine_handle<> self;    // this frame
  PromiseLink* waiter = nullptr;   // caller frame; nullptr for the entry frame
  CoThread* co_thread = nullptr;   // logical thread this frame belongs to
};

// One logical thread of execution = one chain of nested co_awaits.
class CoThread {
 public:
  explicit CoThread(std::coroutine_handle<> on_exit) : on_exit_(on_exit) {}

  std::coroutine_handle<> on_exit() const { return on_exit_; }

  // Called by the leaf awaitable while suspending. False = already cancelled,
  // so the leaf must not park; it must destroy the stack instead.
  bool Register(std::function<void()> on_cancel) {
    if (cancelled_) return false;
    on_cancel_ = std::move(on_cancel);
    return true;
  }
  void Unregister() { on_cancel_ = nullptr; }

  // Request cancellation. If a leaf is parked, its callback runs now and
  // destroys the whole chain. If the chain is currently running, nothing
  // happens here; it dies at its next suspension point (Register -> false).
  void Cancel() {
    cancelled_ = true;
    if (on_cancel_) std::exchange(on_cancel_, nullptr)();
  }

 private:
  std::coroutine_handle<> on_exit_;
  bool cancelled_ = false;
  std::function<void()> on_cancel_;
};

// Handed to a leaf awaitable at suspension. Exactly one of BecameReady,
// Cancelled, AlreadyCancelled is called for each suspension.
class SuspensionToken {
 public:
  explicit SuspensionToken(PromiseLink* leaf) : leaf_(leaf) {}

  bool operator==(const SuspensionToken&) const = default;

  bool RegisterForCancellation(std::function<void()> on_cancel) {
    return co_thread().Register(std::move(on_cancel));
  }

  // Normal wakeup: stop listening for cancel; caller resumes the leaf.
  std::coroutine_handle<> BecameReady() {
    co_thread().Unregister();
    return leaf_->self;
  }

  // Cancel callback won the race: destroy the chain, tell the owner. The leaf
  // coroutine is never resumed.
  void Cancelled() {
    std::coroutine_handle<> on_exit = co_thread().on_exit();  // read first
    DestroyCallStack();                                       // frees leaf_
    on_exit.resume();
  }

  // Register failed (cancelled before we could park): destroy the chain and
  // return on_exit for the caller to symmetric-transfer into.
  std::coroutine_handle<> AlreadyCancelled() {
    std::coroutine_handle<> on_exit = co_thread().on_exit();
    DestroyCallStack();
    return on_exit;
  }

 private:
  CoThread& co_thread() { return *leaf_->co_thread; }

  // Callee before caller, iteratively, so OS stack depth stays O(1).
  void DestroyCallStack() {
    for (PromiseLink* p = leaf_; p != nullptr;) {
      PromiseLink* next = p->waiter;
      p->self.destroy();  // runs this frame's destructors (RAII)
      p = next;
    }
  }

  PromiseLink* leaf_;
};

// Ready queue of wakeups. Holds tokens, not raw handles, so BecameReady runs
// (unregistering the cancel callback) right before resume.
class Scheduler {
 public:
  void Schedule(SuspensionToken t) { ready_.push_back(t); }

  // Pull a scheduled wakeup back out so the frame can be destroyed instead.
  // False = already dequeued and running; cancellation then takes effect at
  // the coroutine's next suspension point.
  bool CancelWaiter(const SuspensionToken& t) {
    return std::erase(ready_, t) > 0;
  }

  void Run() {
    while (!ready_.empty()) {
      SuspensionToken t = ready_.front();
      ready_.pop_front();
      t.BecameReady().resume();
    }
  }

 private:
  std::deque<SuspensionToken> ready_;
};

// Awaitable child coroutine. Lazy start, symmetric transfer, frame destroys
// itself at final_suspend (result is written into the parent's frame first).
template <typename T>
class Co {
  static constexpr bool kVoid = std::is_void_v<T>;
  struct Empty {};
  using Slot = std::conditional_t<kVoid, Empty, std::optional<T>>;

  template <typename U>
  struct ReturnValue {
    std::optional<U>* result = nullptr;  // lives in the awaiting parent
    void return_value(U v) { result->emplace(std::move(v)); }
  };
  struct ReturnVoid {
    void return_void() {}
  };

 public:
  struct promise_type
      : PromiseLink,
        FrameCounter,
        std::conditional_t<kVoid, ReturnVoid, ReturnValue<T>> {
    Co get_return_object() {
      auto h = std::coroutine_handle<promise_type>::from_promise(*this);
      this->self = h;
      return Co(h);
    }
    std::suspend_always initial_suspend() noexcept { return {}; }
    auto final_suspend() noexcept {
      struct Final {
        bool await_ready() noexcept { return false; }
        std::coroutine_handle<> await_suspend(
            std::coroutine_handle<promise_type> h) noexcept {
          PromiseLink* waiter = h.promise().waiter;
          std::coroutine_handle<> on_exit = h.promise().co_thread->on_exit();
          h.destroy();  // frame gone; only locals used from here on
          return waiter != nullptr ? waiter->self : on_exit;
        }
        void await_resume() noexcept {}
      };
      return Final{};
    }
    void unhandled_exception() { std::terminate(); }
  };

  explicit Co(std::coroutine_handle<promise_type> h) : h_(h) {}
  Co(Co&& o) noexcept : h_(std::exchange(o.h_, {})) {}
  ~Co() {
    if (h_) h_.destroy();  // only if never started
  }

  bool await_ready() const noexcept { return false; }
  template <typename P>
  std::coroutine_handle<> await_suspend(std::coroutine_handle<P> parent) {
    promise_type& p = h_.promise();
    p.waiter = &parent.promise();
    p.co_thread = parent.promise().co_thread;
    if constexpr (!kVoid) p.result = &result_;
    return std::exchange(h_, {});  // frame now owns itself
  }
  T await_resume() {
    if constexpr (!kVoid) return std::move(*result_);
  }

  // For bridges only: hand the entry frame to a new CoThread.
  std::coroutine_handle<promise_type> Release() {
    return std::exchange(h_, {});
  }

 private:
  std::coroutine_handle<promise_type> h_;
  Slot result_;
};

// Tiny coroutine used as a CoThread's on_exit handle.
struct OnExit {
  struct promise_type : FrameCounter {
    OnExit get_return_object() {
      return {std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }
  };
  std::coroutine_handle<> handle;
};

OnExit ReportExit(const char* name) {
  std::cout << name << ": thread exited\n";
  co_return;
}

// Bridge: start `co` as the entry frame of a fresh CoThread (what C9 bridges,
// RunConcurrently and WithDeadline do for their children).
void Spawn(Scheduler& sched, CoThread& ct, Co<void> co) {
  auto h = co.Release();
  h.promise().co_thread = &ct;  // waiter stays nullptr: entry frame
  sched.Schedule(SuspensionToken(&h.promise()));
}

// ---- Leaf awaitables (the only place cancellation is "implemented") ----

// Re-queue the current coroutine (simulates work). Cancellable.
class Yield {
 public:
  explicit Yield(Scheduler& s) : sched_(s) {}
  bool await_ready() const noexcept { return false; }
  template <typename P>
  std::coroutine_handle<> await_suspend(std::coroutine_handle<P> h) {
    token_.emplace(&h.promise());
    if (!token_->RegisterForCancellation([this] { Cancel(); })) {
      return token_->AlreadyCancelled();
    }
    sched_.Schedule(*token_);
    return std::noop_coroutine();
  }
  void await_resume() const noexcept {}

 private:
  void Cancel() {
    // Copy out: Cancelled() destroys the frame that holds *this.
    Scheduler& sched = sched_;
    SuspensionToken token = *token_;
    if (sched.CancelWaiter(token)) token.Cancelled();
  }
  Scheduler& sched_;
  std::optional<SuspensionToken> token_;
};

// Condition variable. Note Wait() takes no token: cancellation is implicit.
class CondVar {
 public:
  explicit CondVar(Scheduler& sched) : sched_(sched) {}

  class Awaiter {
   public:
    explicit Awaiter(CondVar& cv) : cv_(cv) {}
    bool await_ready() const noexcept { return false; }
    template <typename P>
    std::coroutine_handle<> await_suspend(std::coroutine_handle<P> h) {
      token_.emplace(&h.promise());
      if (!token_->RegisterForCancellation([this] { Cancel(); })) {
        return token_->AlreadyCancelled();
      }
      cv_.waiters_.push_back(*token_);
      return std::noop_coroutine();
    }
    void await_resume() const noexcept {}

   private:
    void Cancel() {
      CondVar& cv = cv_;
      SuspensionToken token = *token_;
      // Either still parked here, or NotifyOne already moved it to the ready
      // queue. Pull it back from wherever it is, then destroy.
      if (std::erase(cv.waiters_, token) > 0 || cv.sched_.CancelWaiter(token)) {
        token.Cancelled();
      }
    }
    CondVar& cv_;
    std::optional<SuspensionToken> token_;
  };

  Awaiter Wait() { return Awaiter(*this); }

  void NotifyOne() {
    if (waiters_.empty()) return;
    sched_.Schedule(waiters_.front());
    waiters_.pop_front();
  }

 private:
  Scheduler& sched_;
  std::deque<SuspensionToken> waiters_;
};

// ---- User code: no token anywhere ----

class Channel {
 public:
  explicit Channel(Scheduler& sched) : cv_(sched) {}
  void Push(int v) {
    items_.push_back(v);
    cv_.NotifyOne();
  }
  Co<int> Pop() {
    while (items_.empty()) co_await cv_.Wait();
    int v = items_.front();
    items_.pop_front();
    co_return v;
  }

 private:
  std::deque<int> items_;
  CondVar cv_;
};

Co<void> Consumer(Scheduler& sched, Channel& ch, int id) {
  struct Cleanup {
    int id;
    ~Cleanup() { std::cout << "consumer " << id << " cleanup (RAII)\n"; }
  } cleanup{id};

  for (;;) {  // no exit path: cancellation destroys this frame instead
    int item = co_await ch.Pop();
    std::cout << "consumer " << id << " processes " << item << "\n";
    co_await Yield(sched);
  }
}

Co<void> Producer(Scheduler& sched, Channel& ch) {
  for (int i = 0;; ++i) {
    std::cout << "producer pushes " << i << "\n";
    ch.Push(i);
    co_await Yield(sched);
  }
}

Co<void> StopAfter(Scheduler& sched, std::deque<CoThread*> threads, int ticks) {
  for (int i = 0; i < ticks; ++i) co_await Yield(sched);
  std::cout << "=== cancel requested ===\n";
  for (CoThread* t : threads) t->Cancel();  // callbacks destroy the stacks
}

int main() {
  Scheduler sched;
  Channel ch(sched);

  CoThread c1(ReportExit("consumer 1").handle);
  CoThread c2(ReportExit("consumer 2").handle);
  CoThread prod(ReportExit("producer").handle);
  CoThread stopper(ReportExit("stopper").handle);

  Spawn(sched, c1, Consumer(sched, ch, 1));
  Spawn(sched, c2, Consumer(sched, ch, 2));
  Spawn(sched, prod, Producer(sched, ch));
  Spawn(sched, stopper, StopAfter(sched, {&c1, &c2, &prod}, 3));

  sched.Run();

  std::cout << "live coroutine frames after Run(): " << FrameCounter::live
            << "\n";
  return FrameCounter::live == 0 ? 0 : 1;
}

Sep 29, 2026

[coroutine] event/notify example


#include <coroutine>
#include <deque>
#include <exception>
#include <iostream>
#include <utility>

// Single-threaded scheduler: a queue of coroutines ready to resume.
class Scheduler {
 public:
  void Schedule(std::coroutine_handle<> h) {
    std::cout << "  [queue push_back " << h.address() << "]\n";
    ready_.push_back(h);
  }

  void Run() {
    while (!ready_.empty()) {
      std::coroutine_handle<> h = ready_.front();
      ready_.pop_front();
      std::cout << "  [queue pop_front -> resume " << h.address() << "]\n";
      h.resume();
    }
  }

 private:
  std::deque<std::coroutine_handle<>> ready_;
};

// Coroutine condition variable. Wait() parks the handle;
// Notify*() moves parked handles to the scheduler's ready queue.
class CondVar {
 public:
  explicit CondVar(Scheduler& sched) : sched_(sched) {}

  auto Wait() {
    struct Awaiter {
      CondVar& cv;
      bool await_ready() const noexcept { return false; }
      void await_suspend(std::coroutine_handle<> h) {
        std::cout << "  [cv parks " << h.address() << "]\n";
        cv.waiters_.push_back(h);
      }
      void await_resume() const noexcept {}
    };
    return Awaiter{*this};
  }

  void NotifyOne() {
    if (waiters_.empty()) return;
    sched_.Schedule(waiters_.front());
    waiters_.pop_front();
  }

  void NotifyAll() {
    while (!waiters_.empty()) NotifyOne();
  }

 private:
  Scheduler& sched_;
  std::deque<std::coroutine_handle<>> waiters_;
};

// Fire-and-forget top-level coroutine (unchanged from the original).
struct Task {
  struct promise_type {
    Task get_return_object() { return {}; }
    std::suspend_never initial_suspend() noexcept { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }
  };
};

// Awaitable child coroutine: lazy start + symmetric transfer both ways.
struct Co {
  struct promise_type {
    std::coroutine_handle<> continuation;  // parent to resume when done

    Co get_return_object() {
      return Co{std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    // Don't run until the parent co_awaits us (so continuation is set).
    std::suspend_always initial_suspend() noexcept { return {}; }

    auto final_suspend() noexcept {
      struct Final {
        bool await_ready() noexcept { return false; }
        std::coroutine_handle<> await_suspend(
            std::coroutine_handle<promise_type> h) noexcept {
          return h.promise().continuation;  // jump straight back to parent
        }
        void await_resume() noexcept {}
      };
      return Final{};
    }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }
  };

  explicit Co(std::coroutine_handle<promise_type> h) : h_(h) {}
  Co(Co&& o) noexcept : h_(std::exchange(o.h_, {})) {}
  ~Co() {
    if (h_) h_.destroy();  // frame lives until the co_await expression ends
  }

  bool await_ready() const noexcept { return false; }
  std::coroutine_handle<> await_suspend(std::coroutine_handle<> parent) {
    h_.promise().continuation = parent;
    return h_;  // jump straight into child; no queue
  }
  void await_resume() const noexcept {}

  std::coroutine_handle<promise_type> h_;
};

bool data_ready = false;

Co WaitForData(CondVar& cv, int id) {
  while (!data_ready) {  // Predicate loop, same as std::condition_variable.
    std::cout << "child " << id << " waits\n";
    co_await cv.Wait();
  }
  std::cout << "child " << id << " sees data\n";
}

Task Consumer(CondVar& cv, int id) {
  std::cout << "consumer " << id << " calls child\n";
  co_await WaitForData(cv, id);  // nested co_await
  std::cout << "consumer " << id << " woke up\n";
}

Task Producer(CondVar& cv) {
  data_ready = true;
  std::cout << "producer notifies\n";
  cv.NotifyAll();
  co_return;
}

int main() {
  Scheduler sched;
  CondVar cv(sched);
  Consumer(cv, 1);
  Consumer(cv, 2);
  Producer(cv);
  std::cout << "--- sched.Run() ---\n";
  sched.Run();
}


In a C++20 coroutine, a suspension point is any location where the coroutine can pause execution, save its local state to the heap-allocated coroutine frame, and return control to its caller or resumer.

There are four types of suspension points, split between those you write explicitly in the function body and those the compiler injects automatically around your code.


Where They Live: Compiler Transformation

When write a coroutine, the C++20 compiler rewrites its body into a state machine wrapped in boilerplate that invokes your promise_type. Every coroutine contains at least the two implicit suspension points, plus any explicit ones we write:



// What the compiler generates behind the scenes for your coroutine:
{
    promise_type promise;
    auto return_object = promise.get_return_object();

    // --- 1. IMPLICIT: Initial Suspension Point ---
    co_await promise.initial_suspend();

    try {
        // --- YOUR COROUTINE BODY STARTS HERE ---
        
        // --- 2. EXPLICIT: co_await Suspension Point ---
        auto data = co_await async_read();

        // --- 3. EXPLICIT: co_yield Suspension Point ---
        co_yield data; // Rewritten as: co_await promise.yield_value(data);

        // co_return is NOT a suspension point; it sets the result and jumps to final_suspend
        co_return;     // Calls promise.return_void() and goto final_suspend;
        
        // --- YOUR COROUTINE BODY ENDS HERE ---
    } catch (...) {
        promise.unhandled_exception();
    }

final_suspend:
    // --- 4. IMPLICIT: Final Suspension Point ---
    co_await promise.final_suspend();
} // Coroutine frame is automatically destroyed here ONLY if final_suspend does not suspend

Does a Suspension Point Always Suspend?

Hitting a suspension point means the coroutine may suspend, not that it must suspend. At every co_await (including those generated by co_yield, initial_suspend, and final_suspend), the compiler queries an Awaiter object using a 3-step protocol:

  1. awaiter.await_ready(): Checked first as a fast-path optimization.

    • If it returns true, the result is already available. The coroutine does not suspend and immediately calls await_resume().

    • If it returns false, the coroutine prepares to suspend by saving its instruction pointer and local registers into the coroutine frame.

  2. awaiter.await_suspend(handle): Called right after the coroutine state is saved. Its return type controls what happens next:

    • void: Truly suspends the coroutine and returns control to the caller/resumer.

    • bool: Returning true suspends and returns to the caller; returning false aborts the suspension and immediately resumes the coroutine on the current thread.

    • std::coroutine_handle: Suspends the current coroutine and immediately resumes the returned handle via symmetric transfer (without growing the call stack).

  3. awaiter.await_resume(): Called once the coroutine is resumed (or immediately if await_ready() returned true). Its return value becomes the result of the co_await expression.


#include <coroutine>
#include <deque>
#include <exception>
#include <iostream>
#include <optional>
#include <stop_token>
#include <utility>

// Counts live coroutine frames so main() can prove nothing leaked.
struct FrameCounter {
  static inline int live = 0;
  FrameCounter() { ++live; }
  ~FrameCounter() { --live; }
};<

// Single-threaded scheduler: a queue of coroutines ready to resume.
class Scheduler {
 public:
  void Schedule(std::coroutine_handle<> h) { ready_.push_back(h); }

  // Returns when no coroutine is runnable. After a stop request, that means
  // every task has exited (graceful shutdown), unless one is stuck on
  // something that ignores the stop token.
  void Run() {
    while (!ready_.empty()) {
      std::coroutine_handle<> h = ready_.front();
      ready_.pop_front();
      h.resume();
    }
  }

 private:
  std::deque<std::coroutine_handle<>> ready_;
};

// Re-queues the current coroutine so others get a turn (simulates work).
auto Yield(Scheduler& sched) {
  struct Awaiter {
    Scheduler& sched;
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) { sched.Schedule(h); }
    void await_resume() const noexcept {}
  };
  return Awaiter{sched};
}

// Coroutine condition variable with cancellation.
// Wait(token) parks the handle. It is woken by NotifyOne/NotifyAll OR by
// token's stop request, whichever comes first. Callers must re-check their
// predicate and the token after waking (same as std::condition_variable_any).
class CondVar {
 public:
  explicit CondVar(Scheduler& sched) : sched_(sched) {}

  class WaitAwaiter {
   public:
    WaitAwaiter(CondVar& cv, std::stop_token token)
        : cv_(cv), token_(std::move(token)) {}

    // Already stopped: don't suspend at all.
    bool await_ready() const noexcept { return token_.stop_requested(); }

    void await_suspend(std::coroutine_handle<> h) {
      h_ = h;
      cv_.waiters_.push_back(h);
      // Runs OnStop inline if stop was already requested. The callback is
      // deregistered when this awaiter is destroyed (end of co_await).
      on_stop_.emplace(token_, OnStop{this});
    }

    void await_resume() const noexcept {}

   private:
    struct OnStop {
      WaitAwaiter* self;
      void operator()() const noexcept { self->cv_.CancelWait(self->h_); }
    };

    CondVar& cv_;
    std::stop_token token_;
    std::coroutine_handle<> h_;
    std::optional<std::stop_callback<OnStop>> on_stop_;
  };

  WaitAwaiter Wait(std::stop_token token) {
    return WaitAwaiter(*this, std::move(token));
  }

  void NotifyOne() {
    if (waiters_.empty()) return;
    sched_.Schedule(waiters_.front());
    waiters_.pop_front();
  }

  void NotifyAll() {
    while (!waiters_.empty()) NotifyOne();
  }

 private:
  // Wakes h only if it is still parked here. If NotifyOne already moved it
  // to the ready queue, do nothing; scheduling it twice would resume a
  // coroutine that is not suspended (UB).
  void CancelWait(std::coroutine_handle<> h) {
    for (auto it = waiters_.begin(); it != waiters_.end(); ++it) {
      if (*it == h) {
        waiters_.erase(it);
        sched_.Schedule(h);
        return;
      }
    }
  }

  Scheduler& sched_;
  std::deque<std::coroutine_handle<>> waiters_;
};

// Fire-and-forget top-level coroutine. Frame self-destroys at the end.
struct Task {
  struct promise_type : FrameCounter {
    Task get_return_object() { return {}; }
    std::suspend_never initial_suspend() noexcept { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }
  };
};

// Awaitable child coroutine returning T: lazy start + symmetric transfer.
template <typename T>
class Co {
 public:
  struct promise_type : FrameCounter {
    std::coroutine_handle<> continuation;  // parent to resume when done
    std::optional<T> result;

    Co get_return_object() {
      return Co(std::coroutine_handle<promise_type>::from_promise(*this));
    }
    std::suspend_always initial_suspend() noexcept { return {}; }
    auto final_suspend() noexcept {
      struct Final {
        bool await_ready() noexcept { return false; }
        std::coroutine_handle<> await_suspend(
            std::coroutine_handle<promise_type> h) noexcept {
          return h.promise().continuation;  // jump straight back to parent
        }
        void await_resume() noexcept {}
      };
      return Final{};
    }
    void return_value(T v) { result.emplace(std::move(v)); }
    void unhandled_exception() { std::terminate(); }
  };

  explicit Co(std::coroutine_handle<promise_type> h) : h_(h) {}
  Co(Co&& o) noexcept : h_(std::exchange(o.h_, {})) {}
  ~Co() {
    if (h_) h_.destroy();
  }

  bool await_ready() const noexcept { return false; }
  // Parent 就是之後的 await_resume resume 的handle.
  // await_suspend 吃的handle就是await_resume resume的handle.
  std::coroutine_handle<> await_suspend(std::coroutine_handle<> parent) {
    h_.promise().continuation = parent;
    return h_;  // jump straight into child; no queue
  }
  
  // await_suspend 吃的handle就是await_resume resume的handle.
  // co_await 回傳值在此. 
  // 若 coroutune function return的type 也為 waitable
  // 其promise_type::void return_value(T v) 會將return value儲存至
  // promise_type, 其final_suspend() return waitable type會resume parent 
  // 跳至此await_resume().
  T await_resume() { return std::move(*h_.promise().result); }

 private:
  std::coroutine_handle<promise_type> h_;
};

// Unbounded work queue.
class Channel {
 public:
  explicit Channel(Scheduler& sched) : cv_(sched) {}

  void Push(int v) {
    items_.push_back(v);
    cv_.NotifyOne();
  }

  // Graceful semantics: keeps handing out queued items even after stop, and
  // returns nullopt only when the queue is empty AND stop was requested.
  Co<std::optional<int>> Pop(std::stop_token token) {
    while (items_.empty()) {
      if (token.stop_requested()) co_return std::nullopt;
      co_await cv_.Wait(token);  // nested co_await; parks THIS child frame
    }
    int v = items_.front();
    items_.pop_front();
    co_return v;
  }

 private:
  std::deque<int> items_;
  CondVar cv_;
};

Task Consumer(Scheduler& sched, Channel& ch, int id, std::stop_token token) {
  struct Cleanup {  // proves destructors run on shutdown
    int id;
    ~Cleanup() { std::cout << "consumer " << id << " cleanup (RAII)\n"; }
  } cleanup{id};

  while (std::optional<int> item = co_await ch.Pop(token)) {
    std::cout << "consumer " << id << " processes " << *item << "\n";
    co_await Yield(sched);  // simulate work
  }
  std::cout << "consumer " << id << " exits\n";
}

Task Producer(Scheduler& sched, Channel& ch, std::stop_token token) {
  for (int i = 0; !token.stop_requested(); ++i) {
    std::cout << "producer pushes " << i << "\n";
    ch.Push(i);
    co_await Yield(sched);
  }
  std::cout << "producer exits (stop requested)\n";
}

// Stands in for a SIGTERM handler / deadline: requests stop after `ticks`.
Task StopAfter(Scheduler& sched, std::stop_source& stop, int ticks) {
  for (int i = 0; i < ticks; ++i) co_await Yield(sched);
  std::cout << "=== shutdown requested ===\n";
  stop.request_stop();  // wakes parked waiters via their stop_callbacks
}

int main() {
  Scheduler sched;
  Channel ch(sched);
  std::stop_source stop;

  Consumer(sched, ch, 1, stop.get_token());
  Consumer(sched, ch, 2, stop.get_token());
  Producer(sched, ch, stop.get_token());
  StopAfter(sched, stop, 3);

  sched.Run();  // returns once every task has exited

  std::cout << "live coroutine frames after Run(): " << FrameCounter::live
            << "\n";
  return FrameCounter::live == 0 ? 0 : 1;
}

// Minimal single-threaded model of C9-style cancellation.
//
// Key difference from the stop_token version: a cancelled coroutine is NEVER
// resumed. The cancel callback destroys every frame of the logical thread
// (leaf -> entry), running destructors, and then resumes an "on_exit" handle
// so the owner learns the thread is gone. No code checks a token.
//
// Name map to google3/util/c9/internal:
//   PromiseLink      -> CoroutinePromiseLink (waiter link, co_thread)
//   CoThread         -> CoThread (cancelled_ AsyncNotification, on_exit)
//   SuspensionToken  -> SuspensionToken (BecameReady/Cancelled/AlreadyCancelled,
//                       DestroyCallStack)
//   Scheduler        -> ThreadState ready queue + Schedule/CancelWaiter
//   Yield / CondVar  -> leaf awaitables like c9::Event::Awaitable

#include <algorithm>
#include <coroutine>
#include <deque>
#include <exception>
#include <functional>
#include <iostream>
#include <optional>
#include <type_traits>
#include <utility>

struct FrameCounter {
  static inline int live = 0;
  FrameCounter() { ++live; }
  ~FrameCounter() { --live; }
};

class CoThread;

// Base of every Co<T> promise. The `waiter` pointers form the intrusive
// "coroutine call stack" that cancellation walks and destroys.
struct PromiseLink {
  std::coroutine_handle<> self;    // this frame
  PromiseLink* waiter = nullptr;   // caller frame; nullptr for the entry frame
  CoThread* co_thread = nullptr;   // logical thread this frame belongs to
};

// One logical thread of execution = one chain of nested co_awaits.
class CoThread {
 public:
  explicit CoThread(std::coroutine_handle<> on_exit) : on_exit_(on_exit) {}

  std::coroutine_handle<> on_exit() const { return on_exit_; }

  // Called by the leaf awaitable while suspending. False = already cancelled,
  // so the leaf must not park; it must destroy the stack instead.
  bool Register(std::function<void()> on_cancel) {
    if (cancelled_) return false;
    on_cancel_ = std::move(on_cancel);
    return true;
  }
  void Unregister() { on_cancel_ = nullptr; }

  // Request cancellation. If a leaf is parked, its callback runs now and
  // destroys the whole chain. If the chain is currently running, nothing
  // happens here; it dies at its next suspension point (Register -> false).
  void Cancel() {
    cancelled_ = true;
    if (on_cancel_) std::exchange(on_cancel_, nullptr)();
  }

 private:
  std::coroutine_handle<> on_exit_;
  bool cancelled_ = false;
  std::function<void()> on_cancel_;
};

// Handed to a leaf awaitable at suspension. Exactly one of BecameReady,
// Cancelled, AlreadyCancelled is called for each suspension.
class SuspensionToken {
 public:
  explicit SuspensionToken(PromiseLink* leaf) : leaf_(leaf) {}

  bool operator==(const SuspensionToken&) const = default;

  bool RegisterForCancellation(std::function<void()> on_cancel) {
    return co_thread().Register(std::move(on_cancel));
  }

  // Normal wakeup: stop listening for cancel; caller resumes the leaf.
  std::coroutine_handle<> BecameReady() {
    co_thread().Unregister();
    return leaf_->self;
  }

  // Cancel callback won the race: destroy the chain, tell the owner. The leaf
  // coroutine is never resumed.
  void Cancelled() {
    std::coroutine_handle<> on_exit = co_thread().on_exit();  // read first
    DestroyCallStack();                                       // frees leaf_
    on_exit.resume();
  }

  // Register failed (cancelled before we could park): destroy the chain and
  // return on_exit for the caller to symmetric-transfer into.
  std::coroutine_handle<> AlreadyCancelled() {
    std::coroutine_handle<> on_exit = co_thread().on_exit();
    DestroyCallStack();
    return on_exit;
  }

 private:
  CoThread& co_thread() { return *leaf_->co_thread; }

  // Callee before caller, iteratively, so OS stack depth stays O(1).
  void DestroyCallStack() {
    for (PromiseLink* p = leaf_; p != nullptr;) {
      PromiseLink* next = p->waiter;
      p->self.destroy();  // runs this frame's destructors (RAII)
      p = next;
    }
  }

  PromiseLink* leaf_;
};

// Ready queue of wakeups. Holds tokens, not raw handles, so BecameReady runs
// (unregistering the cancel callback) right before resume.
class Scheduler {
 public:
  void Schedule(SuspensionToken t) { ready_.push_back(t); }

  // Pull a scheduled wakeup back out so the frame can be destroyed instead.
  // False = already dequeued and running; cancellation then takes effect at
  // the coroutine's next suspension point.
  bool CancelWaiter(const SuspensionToken& t) {
    return std::erase(ready_, t) > 0;
  }

  void Run() {
    while (!ready_.empty()) {
      SuspensionToken t = ready_.front();
      ready_.pop_front();
      t.BecameReady().resume();
    }
  }

 private:
  std::deque<SuspensionToken> ready_;
};

// Awaitable child coroutine. Lazy start, symmetric transfer, frame destroys
// itself at final_suspend (result is written into the parent's frame first).
template <typename T>
class Co {
  static constexpr bool kVoid = std::is_void_v<T>;
  struct Empty {};
  using Slot = std::conditional_t<kVoid, Empty, std::optional<T>>;

  template <typename U>
  struct ReturnValue {
    std::optional<U>* result = nullptr;  // lives in the awaiting parent
    void return_value(U v) { result->emplace(std::move(v)); }
  };
  struct ReturnVoid {
    void return_void() {}
  };

 public:
  struct promise_type
      : PromiseLink,
        FrameCounter,
        std::conditional_t<kVoid, ReturnVoid, ReturnValue<T>> {
    Co get_return_object() {
      auto h = std::coroutine_handle<promise_type>::from_promise(*this);
      this->self = h;
      return Co(h);
    }
    std::suspend_always initial_suspend() noexcept { return {}; }
    auto final_suspend() noexcept {
      struct Final {
        bool await_ready() noexcept { return false; }
        std::coroutine_handle<> await_suspend(
            std::coroutine_handle<promise_type> h) noexcept {
          PromiseLink* waiter = h.promise().waiter;
          std::coroutine_handle<> on_exit = h.promise().co_thread->on_exit();
          h.destroy();  // frame gone; only locals used from here on
          return waiter != nullptr ? waiter->self : on_exit;
        }
        void await_resume() noexcept {}
      };
      return Final{};
    }
    void unhandled_exception() { std::terminate(); }
  };

  explicit Co(std::coroutine_handle<promise_type> h) : h_(h) {}
  Co(Co&& o) noexcept : h_(std::exchange(o.h_, {})) {}
  ~Co() {
    if (h_) h_.destroy();  // only if never started
  }

  bool await_ready() const noexcept { return false; }
  template <typename P>
  std::coroutine_handle<> await_suspend(std::coroutine_handle<P> parent) {
    promise_type& p = h_.promise();
    p.waiter = &parent.promise();
    p.co_thread = parent.promise().co_thread;
    if constexpr (!kVoid) p.result = &result_;
    return std::exchange(h_, {});  // frame now owns itself
  }
  T await_resume() {
    if constexpr (!kVoid) return std::move(*result_);
  }

  // For bridges only: hand the entry frame to a new CoThread.
  std::coroutine_handle<promise_type> Release() {
    return std::exchange(h_, {});
  }

 private:
  std::coroutine_handle<promise_type> h_;
  Slot result_;
};

// Tiny coroutine used as a CoThread's on_exit handle.
struct OnExit {
  struct promise_type : FrameCounter {
    OnExit get_return_object() {
      return {std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }
  };
  std::coroutine_handle<> handle;
};

OnExit ReportExit(const char* name) {
  std::cout << name << ": thread exited\n";
  co_return;
}

// Bridge: start `co` as the entry frame of a fresh CoThread (what C9 bridges,
// RunConcurrently and WithDeadline do for their children).
void Spawn(Scheduler& sched, CoThread& ct, Co<void> co) {
  auto h = co.Release();
  h.promise().co_thread = &ct;  // waiter stays nullptr: entry frame
  sched.Schedule(SuspensionToken(&h.promise()));
}

// ---- Leaf awaitables (the only place cancellation is "implemented") ----

// Re-queue the current coroutine (simulates work). Cancellable.
class Yield {
 public:
  explicit Yield(Scheduler& s) : sched_(s) {}
  bool await_ready() const noexcept { return false; }
  template <typename P>
  std::coroutine_handle<> await_suspend(std::coroutine_handle<P> h) {
    token_.emplace(&h.promise());
    if (!token_->RegisterForCancellation([this] { Cancel(); })) {
      return token_->AlreadyCancelled();
    }
    sched_.Schedule(*token_);
    return std::noop_coroutine();
  }
  void await_resume() const noexcept {}

 private:
  void Cancel() {
    // Copy out: Cancelled() destroys the frame that holds *this.
    Scheduler& sched = sched_;
    SuspensionToken token = *token_;
    if (sched.CancelWaiter(token)) token.Cancelled();
  }
  Scheduler& sched_;
  std::optional<SuspensionToken> token_;
};

// Condition variable. Note Wait() takes no token: cancellation is implicit.
class CondVar {
 public:
  explicit CondVar(Scheduler& sched) : sched_(sched) {}

  class Awaiter {
   public:
    explicit Awaiter(CondVar& cv) : cv_(cv) {}
    bool await_ready() const noexcept { return false; }
    template <typename P>
    std::coroutine_handle<> await_suspend(std::coroutine_handle<P> h) {
      token_.emplace(&h.promise());
      if (!token_->RegisterForCancellation([this] { Cancel(); })) {
        return token_->AlreadyCancelled();
      }
      cv_.waiters_.push_back(*token_);
      return std::noop_coroutine();
    }
    void await_resume() const noexcept {}

   private:
    void Cancel() {
      CondVar& cv = cv_;
      SuspensionToken token = *token_;
      // Either still parked here, or NotifyOne already moved it to the ready
      // queue. Pull it back from wherever it is, then destroy.
      if (std::erase(cv.waiters_, token) > 0 || cv.sched_.CancelWaiter(token)) {
        token.Cancelled();
      }
    }
    CondVar& cv_;
    std::optional<SuspensionToken> token_;
  };

  Awaiter Wait() { return Awaiter(*this); }

  void NotifyOne() {
    if (waiters_.empty()) return;
    sched_.Schedule(waiters_.front());
    waiters_.pop_front();
  }

 private:
  Scheduler& sched_;
  std::deque<SuspensionToken> waiters_;
};

// ---- User code: no token anywhere ----

class Channel {
 public:
  explicit Channel(Scheduler& sched) : cv_(sched) {}
  void Push(int v) {
    items_.push_back(v);
    cv_.NotifyOne();
  }
  Co<int> Pop() {
    while (items_.empty()) co_await cv_.Wait();
    int v = items_.front();
    items_.pop_front();
    co_return v;
  }

 private:
  std::deque<int> items_;
  CondVar cv_;
};

Co<void> Consumer(Scheduler& sched, Channel& ch, int id) {
  struct Cleanup {
    int id;
    ~Cleanup() { std::cout << "consumer " << id << " cleanup (RAII)\n"; }
  } cleanup{id};

  for (;;) {  // no exit path: cancellation destroys this frame instead
    int item = co_await ch.Pop();
    std::cout << "consumer " << id << " processes " << item << "\n";
    co_await Yield(sched);
  }
}

Co<void> Producer(Scheduler& sched, Channel& ch) {
  for (int i = 0;; ++i) {
    std::cout << "producer pushes " << i << "\n";
    ch.Push(i);
    co_await Yield(sched);
  }
}

Co<void> StopAfter(Scheduler& sched, std::deque<CoThread*> threads, int ticks) {
  for (int i = 0; i < ticks; ++i) co_await Yield(sched);
  std::cout << "=== cancel requested ===\n";
  for (CoThread* t : threads) t->Cancel();  // callbacks destroy the stacks
}

int main() {
  Scheduler sched;
  Channel ch(sched);

  CoThread c1(ReportExit("consumer 1").handle);
  CoThread c2(ReportExit("consumer 2").handle);
  CoThread prod(ReportExit("producer").handle);
  CoThread stopper(ReportExit("stopper").handle);

  Spawn(sched, c1, Consumer(sched, ch, 1));
  Spawn(sched, c2, Consumer(sched, ch, 2));
  Spawn(sched, prod, Producer(sched, ch));
  Spawn(sched, stopper, StopAfter(sched, {&c1, &c2, &prod}, 3));

  sched.Run();

  std::cout << "live coroutine frames after Run(): " << FrameCounter::live
            << "\n";
  return FrameCounter::live == 0 ? 0 : 1;
}

Aug 1, 2026

[C++][coroutine] Symmetric Transfer - what problem it solves

The Core Problem: Stack Overflow via Asymmetric Transfer

In early C++ Coroutines (Coroutines TS), resuming a coroutine meant calling .resume() inside `await_suspend()`. When coroutines synchronously complete in a loop or tail-recurse, every .resume()  call pushes a new C++ stack frame without popping the old one, leading to stack overflow.

// ASYMMETRIC TRANSFER (Naive Approach)

void await_suspend(std::coroutine_handle<> h) {
    // Calling .resume() pushes a new stack frame.
    // If done in a deep loop or recursive chain, stack space explodes!
    other_coro_.resume(); 
}


Stack Frame Accumulation:

[ loop_coroutine$resume ]

  └─> [ task::awaiter::await_suspend ]

        └─> [ child_coroutine$resume ]

              └─> [ final_awaiter::await_suspend ]

                    └─> [ loop_coroutine$resume ]  <-- STACK OVERFLOW!



The Solution: Symmetric Transfer

Symmetric transfer allows `await_suspend()` to return a std::coroutine_handle<> instead of void.


Returning a handle suspends the current coroutine frame, pops the current stack frame, and transfers execution directly to the returned handle via a tail call.

Stack usage remains O(1) regardless of how many synchronous suspension/resumes occur.

// SYMMETRIC TRANSFER (Modern C++20)

std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
    // Return the handle to transfer control directly.
    // The compiler generates a tail-call: pops current stack frame, then resumes target.
    return other_coro_; 

}


Key Implementations

A. The Awaiter (`task::operator co_await`)

When `co_await child_task;` executes, transfer control directly to the child's handle:

struct task_awaiter {
    std::coroutine_handle<promise_type> child_coro_;
    bool await_ready() noexcept { return false; }

    // Symmetric Transfer: Returns child handle to resume
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting_coro) noexcept {
        // 1. Store caller as continuation in child's promise
        child_coro_.promise().continuation = awaiting_coro;
        
        // 2. Return child handle -> tail-call into child_coro_
        return child_coro_; 

    }
    void await_resume() noexcept {}
};


B. The Final Suspend (`promise_type::final_suspend`)

When a child coroutine finishes at `co_return`, transfer control back to its continuation (the caller):

struct final_awaiter {
    bool await_ready() noexcept { return false; }

    // Symmetric Transfer: Returns caller's handle to resume
    std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> me) noexcept {
        // Returns parent handle -> tail-call back to parent coroutine
        return me.promise().continuation; 
    }
    void await_resume() noexcept {}

};

struct promise_type {
    std::coroutine_handle<> continuation{std::noop_coroutine()};
    final_awaiter final_suspend() noexcept { return {}; }
    // ...

};

Summary Matrix



[C++] coroutine memory layout


Dec 27, 2025

[C++] coroutine cheat sheet - 3

Reference:
[C++] Object Lifetimes reading minute



When HALO (Heap Allocation Elision Optimization) could happen.

  1. The Lifetime is Strictly Nested
    The compiler must be able to prove that the coroutine's lifetime ends before the caller's execution finishes.
    If the coroutine object (the "handle" or "task") is returned and its destruction point cannot be determined at compile-time, the compiler must play it safe and use the heap.
  2. The Coroutine State Size is Known
    The compiler needs to know exactly how much space the coroutine requires (including captured variables and promise objects) at the call site.
    This usually requires the coroutine body to be visible to the compiler
    (i.e., in the same translation unit or available via Link Time Optimization).
  3. Use of std::get_return_object_on_allocation_failure (Optional but relevant)
    If our promise_type defines this static member function, it signals to the compiler how to handle allocation failures.
    While this doesn't force an opt-out, it changes the allocation strategy to be more robust.

The HALO Optimization Process:

The optimization works roughly like this:

  • Analysis: The compiler looks at the co_await and destruction points of the coroutine object.
  • Inlining: It attempts to inline the coroutine logic into the caller.
  • Elision: If the compiler sees that the coroutine state does not "escape" the function, it replaces operator new with a local stack allocation.

How to Encourage the Compiler to Opt-Out

Since we cannot explicitly keywords like noheap, we have to "help" the compiler's optimizer:
  • Keep the coroutine local: Avoid passing the coroutine handle to other threads or storing it in global containers.
  • Enable High Optimization: HALO typically requires -O2 or -O3 (GCC/Clang) or /O2 (MSVC).
  • Inline the Coroutine: Define the coroutine in a header or the same file where it is called so the compiler can see the full lifecycle.
Task my_coroutine() {
    co_return 42;
}

void caller() {
    auto t = my_coroutine(); // Compiler can see 't' lives only here
    // ... do something ...
} // 't' is destroyed here; Heap allocation likely elided. 


Limitations

  • Dynamic Dispatch: If we call a coroutine through a virtual function or a function pointer, the compiler usually cannot perform HALO.
  • Tail Calls: Complex chains of coroutines can sometimes make it difficult for the compiler to prove nested lifetimes.

The "Manual" Opt-Out (Custom Allocators)

If we cannot rely on the compiler's optimization (e.g., in embedded systems), 
we can manually opt-out of the default heap by overloading operator new in our promise_type.

Using a Static Buffer or Arena:

We can provide a custom operator new that pulls from a pre-allocated memory pool or a stack-based arena, effectively bypassing the system heap.
struct promise_type {
    // Overloading new allows we to use a custom allocator
    void* operator new(std::size_t size) {
        return my_custom_arena.allocate(size);
    }
    void operator delete(void* ptr) {
        my_custom_arena.deallocate(ptr);
    }
    // ... other promise members
};


How to Verify if Elision Happened

Since HALO is an optimization, it can be fragile. We can verify it using these tricks:
  • Print in Custom New: Add a printf inside our promise_type::operator new. If it doesn't print during execution at -O3, the compiler successfully elided the call.
  • Compiler Explorer (Assembly): Check for the absence of call operator new or malloc in the generated assembly.
  • Clang-Specific Attributes: Clang is experimenting with attributes like [[clang::coro_inplace_task]] to make this elision more deterministic, though this is not standard C++20. (reference: Language Extension for better, more deterministic HALO for C++ Coroutines)
#include <coroutine>
#include <iostream>
#include <array>

struct StaticTask {
    struct promise_type {
        // 1. Intercept the arguments of the coroutine function
        // This allows 'operator new' to see the buffer passed to the coroutine
        void* operator new(std::size_t size, std::span<std::byte> buffer) {
            if (size > buffer.size()) {
                throw std::bad_alloc();
            }
            std::cout << "Allocating " << size << " bytes from stack buffer\n";
            return buffer.data();
        }

        // Must provide a matching delete (even if it does nothing)
        void operator delete(void*, std::size_t) {}

        StaticTask get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; }
        std::initial_suspend initial_suspend() { return {}; }
        std::final_suspend final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;
};

// Usage
StaticTask my_coro(std::span<std::byte> storage) {
    std::cout << "Coroutine running!\n";
    co_return;
}

int main() {
    std::array<std::byte, 1024> stack_space; 
    auto task = my_coro(stack_space); // Passes the buffer to 'operator new'
}

In C++20, the operator new for a coroutine is uniquely powerful because the compiler performs a special lookup.
It doesn't just look for a standard void* operator new(size_t); it looks for an overload that matches the entire signature of our coroutine function.

Why it takes the buffer as an argument
When we call a coroutine like my_coro(some_buffer), the compiler needs to allocate space for the "coroutine frame" 
(which holds local variables and the promise).

To give us total control, the C++ standard says:

The compiler will first try to find an operator new in our promise_type that takes (std::size_t, Args...),
where Args... are the exact types passed to the coroutine function.

If it finds this "matching" version, it calls it and passes the arguments we provided in the function call.

This is the "magic hook" that allows us to pass a specific memory source (like a stack-based span or a custom Arena&) directly into the allocation logic.

The Lookup Mechanics

The compiler follows this priority list when it sees a coroutine call:

Priority   Signature the compiler looks for Description
  1. (Best)  operator new(size_t, P1, P2...)
    Takes the size plus all coroutine arguments (P1,P2).
  2. operator new(size_t)                   
    The standard class-specific allocator.
  3. ::operator new(size_t)                 
    Global heap allocation. (Fallback)
Note: For member functions, the first argument after size_t is actually the this pointer, followed by the function arguments.



struct promise_type {
    std::span<std::byte> m_buffer;

    // 1. Used to POSITION the memory (Allocation)
    void* operator new(std::size_t size, std::span<std::byte> buffer) {
        if (size > buffer.size()) throw std::bad_alloc();
        return buffer.data();
    }

    // 2. Used to INITIALIZE the promise (Construction)
    // The compiler sees that the coroutine was called with a span,
    // so it looks for a constructor that accepts it.
    promise_type(std::span<std::byte> buffer) : m_buffer(buffer) {
        std::cout << "Promise initialized with buffer of size " << m_buffer.size() << "\n";
    }

    // ... rest of promise_type members ...
};

[[clang::coro_await_elidable_argument]] 

attribute is a specialized Clang-specific optimization hint designed to reduce the overhead of asynchronous programming. It is primarily used to enable HALO (Heap Allocation Elision Optimization) for coroutines.

This attribute is applied to a parameter of a function (usually an operator await). It tells the compiler that the coroutine being passed as an argument is a temporary that will not outlive the current function. This gives the compiler a "green light" to:
  • Elide the heap allocation: Instead of putting the coroutine frame on the heap, it puts it on the caller's stack.
  • Inline the coroutine: It allows for better devirtualization and inlining of the coroutine's lifecycle.

Sep 28, 2025

[C++] coroutine cheat sheet - 2

Reference:
[C++] coroutine cheat sheet - 1
https://reductor.dev/cpp/2023/08/10/the-downsides-of-coroutines.html#how-normal-functions-and-the-stack-work

Rule

  1. Do not resume caller's handler inside a mem_fn that returns value/handler.
    otherwise the return will be suspended in the callee.
    reference:
    https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer
    A pattern, however, the caller's awaitable::await_suspend() could call callee's handler to resume,
    while callee's promise::final_suspend() could call caller handler to resume() and back to
    caller's awaitable::await_suspend().
  2. Control the suspend from mem_fn that it's signature returns awaitable type.
  3. Store {yeild values/caller's handler} inside the promise instance.
  4. Make sure promise::final_suspend() is run before caller resume.
    This indicates, the caller::handler::resume() can only be called
    inside the promise::final_suspend() returned awaitable::await_suspend() and awaitable::await_ready() should return false to trigger the run of awaitable::await_suspend()
  5. If promise::final_suspend() returns std::suspend_never{}, the coroutine heap is destroyed automatically/immidiately.
    If promise::final_suspend() returns other then std::suspend_never{} and
    has the awaitable::await_ready() returns false, the user defined coroutine
    handler's destructor should call detroy() manually to release the heap.
  6. Thread local variables should not be used in coroutines to prevent buggy logic.
    https://rules.sonarsource.com/cpp/RSPEC-6367
  7. std::coroutine_handle<Promise>::operator(),  std::coroutine_handle<Promise>::resume
    The behavior is undefined if *this does not refer to suspended coroutine, or the coroutine is not a no-op coroutine and suspended at its final suspend point. A concurrent resumption of the coroutine may result in a data race.
Coroutine defined as:
task<void> func()
{
  int mylocal = 10;
  co_return;
}
Compiler generated coroutine code would be like:
// Struct representing the coroutine state
struct func_frame
{
  task<void>::promise_type __promise;
  int __step = 0;

  decltype(__promise.initial_suspend()) __initial_suspend_awaiter;
  decltype(__promise.final_suspend()) __final_suspend_awaiter;

  // Structure to hold local and temporary variables
  struct 
  {
    // Local and temporary variables reside here
    int mylocal;
  } local_and_temps;

  // Function to resume coroutine execution
  void resume()
  {
    switch(__step)
    {
      case 0:
        // co_await __promise.initial_suspend();
        __initial_suspend_awaiter = __promise.initial_suspend();
        if (!__initial_suspend_awaiter.await_ready())
        {
          __step = 1;
          __initial_suspend_awaiter.await_suspend();
          return;
        }
      case 1:
        __initial_suspend_awaiter.await_resume();
        // .. func body
        mylocal = 10;
        // co_return
        __promise.return_void();
        // co_await __promise.final_suspend();
        __final_suspend_awaiter = __promise.final_suspend();
        if (!__final_suspend_awaiter.await_ready())
        {
          __final_suspend_awaiter.await_suspend();
          return;
        }
        delete this;
    }
  }
};

// Coroutine function transformed into a coroutine frame
task<void> func()
{
  func_frame * frame = task<void>::promise_type::operator new(func_frame);
  task<void> ret = frame->__promise.get_return_object()
  frame->resume();
  return ret;
}
Which involves multiple function calls:
  • func()
  • promise_type::operator new()
  • promise_type::promise_type()
  • promise_type::get_return_value()
  • promise_type::initial_suspend()
  • initial_suspend::initial_suspend()
  • initial_suspend::await_ready()
  • initial_suspend::await_suspend() [opt]
  • initial_suspend::await_resume()
  • promise_type::return_void()
  • final_suspend::final_suspend()
  • final_suspend::await_ready()
  • final_suspend::await_suspend() [opt]
  • promise_type::operator delete()

Code explains:
#include <coroutine>
#include <iostream>

/**
Rule:
1. Do not resume caller's handler inside a mem_fn that returns value/handler.
  otherwise the return will be suspended in the callee.
2. Control the suspend from mem_fn that it's signature returns awaitable type.
3. store {yeild values/caller's handler} inside the promise instance.
4. make sure promise::final_suspend() is run before caller resume.
  this indicates, the caller::handler::resume() can only be called
  inside the promise::final_suspend() returned awaitable::await_suspend() and
  awaitable::await_ready() should return false to trigger the run of
  awaitable::await_suspend()
5. If promise::final_suspend() returns std::suspend_never{}, the coroutine heap
  is destroyed automatically.
  If promise::final_suspend() returns other then std::suspend_never{} and
  has the awaitable::await_ready() returns false, the user defined coroutine
handler's destructor should call detroy() manually to release the heap.
*/
using namespace std;

enum class TaskType : char {
  foo,
  bar,
};

template <TaskType ttype> struct Task;

// promise_type
template <TaskType ttype> struct promise_type {
  Task<ttype> get_return_object();
  std::suspend_always initial_suspend();

  auto final_suspend() noexcept;

  void return_value(int);
  void unhandled_exception();
  TaskType get_ttype() { return ttype; }
  int ret_val;
  std::coroutine_handle<promise_type<TaskType::bar>> promise_type_handle_;
};
// End promise_type

template <TaskType ttype>
struct Task : std::coroutine_handle<promise_type<ttype>> {
  using promise_type = promise_type<ttype>;
  // Awaitable interface definition
  bool await_ready() {
    std::cout << "await_ready: false\n";
    return false;
  }

  std::coroutine_handle<promise_type>
  await_suspend(std::coroutine_handle<::promise_type<TaskType::bar>> handler) {
    std::cout << "await_suspend receive handler pnted to type: "
              << (int)handler.promise().get_ttype()
              << " and this task's type: " << (int)this->promise().get_ttype()
              << "\n";

    // using this due to 13.4.2 Dependent Base Classes
    this->promise().promise_type_handle_ = handler;
    return *this;
  }

  int await_resume() {
    std::cout << "await_resume; type: " << (int)ttype << "\n";
    return this->promise().ret_val;
  }
  // End Awaitable interface definition

  ~Task() {
    std::cout << "Task destructor called, type: " << (int)ttype << "\n";
    this->destroy();
  }

  TaskType get_ttype() { return ttype; }

  // If has default Task constructor, this `return
  // {Task<ttype>::from_promise(*this)};` won't compile.
  // Task() { std::cout << "task construct type: " << (int)ttype << "\n"; }

}; // Task

// promise_type mem_fn definition
template <TaskType ttype> Task<ttype> promise_type<ttype>::get_return_object() {
  std::cout << "get_return_object type: " << (int)ttype << "\n";
  return {Task<ttype>::from_promise(*this)};
};

template <TaskType ttype>
std::suspend_always promise_type<ttype>::initial_suspend() {
  std::cout << "initial_suspend always\n";
  return {};
}

template <TaskType ttype> auto promise_type<ttype>::final_suspend() noexcept {
  std::cout << "final_suspend, this promise type: " << (int)ttype << "\n";
  // Bar's handler
  // if (promise_type_handle_) {
  //   std::cout << "resume bar's handler\n";
  //   promise_type_handle_.resume();
  // }
  struct tmp_awaitable {
    bool await_ready() noexcept {
      std::cout << "tmp_awaitable await_ready false\n";
      return false;
    }

    std::coroutine_handle<> await_suspend(
        std::coroutine_handle<::promise_type<TaskType::foo>> handler) noexcept {
      std::cout << "tmp_awaitable await_suspend\n";
      if (handler.promise().promise_type_handle_) {
        return handler.promise().promise_type_handle_;
      }
      std::cout << "tmp_awaitable await_suspend after resume\n";
      return std::noop_coroutine();
    }

    void await_resume() noexcept {
      std::cout << "tmp_awaitable await_resume\n";
    }
  };

  if constexpr (ttype == TaskType::foo) {
    std::cout << "final_suspend type: " << (int)ttype << " suspend never\n";
    // return std::suspend_never{};
    return tmp_awaitable{};
  } else {
    std::cout << "final_suspend type: " << (int)ttype << " suspend always\n";
    return std::suspend_always{};
  }
}

template <TaskType ttype> void promise_type<ttype>::return_value(int val) {
  std::cout << "return_val: " << val << "\n";
  ret_val = val;
  // promise_type_handle_.resume();
}

template <TaskType ttype> void promise_type<ttype>::unhandled_exception() {
  std::cout << "unhandled_exception\n";
}
// End promise_type mem_fn definition

Task<TaskType::foo> foo() {
  std::cout << "foo func start\n";
  co_return 42;
}

Task<TaskType::bar> bar() {
  std::cout << "bar func start\n";
  // co_return;
  // co_await foo();
  int val = co_await foo(); // Foo's task destructs here after co_await returns;
  std::cout << "bar func end val: " << val << "\n";
}

int main() {
  std::cout << "call bar\n";
  auto btask = bar();
  std::cout << "get btask and resume bar handle: " << (int)btask.get_ttype()
            << "\n";
  btask.resume();
  std::cout << "main-end\n";
  /*
        start call bar
        initial_suspend
        main-end
   */
}

Aug 3, 2025

[C++] coroutine cheat sheet

Key components:

  • Return type
  • Promise type
  • Awaitable type
  • std::coroutine_handle<>

Clang Attributes:

task<int> coro(const int& a) { co_return a + 1; }
task<int> dangling_refs(int a) {
  // `coro` captures reference to a temporary. `foo` would now contain a dangling reference to `a`.
  auto foo = coro(1);
  // `coro` captures reference to local variable `a` which is destroyed after the return.
  return coro(a);
}

template <typename T> struct [[clang::coro_return_type, clang::coro_lifetimebound]] Task {
  using promise_type = some_promise_type;
};

Task<int> coro(const int& a) { co_return a + 1; }
[[clang::coro_wrapper]] Task<int> coro_wrapper(const int& a, const int& b) {
  return a > b ? coro(a) : coro(b);
}
Task<int> temporary_reference() {
  auto foo = coro(1); // warning: capturing reference to a temporary which would die after the expression.

  int a = 1;
  auto bar = coro_wrapper(a, 0); // warning: `b` captures reference to a temporary.

  co_return co_await coro(1); // fine.
}
[[clang::coro_wrapper]] Task<int> stack_reference(int a) {
  return coro(a); // warning: returning address of stack variable `a`.
}

Important performance to avoid temporary object being created during pass by value
but resides in the register
https://vsdmars.blogspot.com/2021/01/c-trivialabi-and-borrow-ownership.html

Attribute trivial_abi has no effect in the following cases:
  • The class directly declares a virtual base or virtual methods.
  • Copy constructors and move constructors of the class are all deleted.
  • The class has a base class that is non-trivial for the purposes of calls.
  • The class has a non-static data member whose type is non-trivial for the purposes of calls, which includes:
    • classes that are non-trivial for the purposes of calls
    • __weak-qualified types in Objective-C++
    • arrays of any of the above

Concept:

`co_yield value` is a syntax sugar for `co_await awaitable`.
thus value is stored in promise_type instance directly instead of having to go through
`Awaitable{value}` and store the value into promise_type instance through `Awaitable::await_suspend(handler)`

Caller could retrieve the value through
`ReturnType::handler_::promise.value_;`

std::suspend_always {
  // always suspend.
  bool await_ready() { return false;}
  // noop
  void await_suspend()
  // noop, the value is stored in promise_type.value_.
  void await_resume()
} // https://en.cppreference.com/w/cpp/coroutine/suspend_always.html

std::suspend_never {
  // never suspend.
  bool await_ready() { return true;}
  // noop
  void await_suspend()
  // noop, the value is stored in promise_type.value_.
  void await_resume()
}; // https://en.cppreference.com/w/cpp/coroutine/suspend_never.html

// Controls caller's behavior. Thus await_suspend is passing in caller's
// coroutine handler. However, the Awaitable instance is created by the
// callee, which it could store the callee's coroutine handler.
strict Awaitable {
  T value_;
  bool await_ready() { return false;}
  void await_suspend(std::coroutine_handle<promise_type> handler) {
    // could store the result into handler.promise().RESULT.
    //    or store the value_ into handler.promise().RESULT.
    // Handler can be passed into new thread.
    // When handler calls .resume(), it starts execute from where previous
    // suspended.
    // These await_* functions controlls how the caller, i.e. not the callee that
    // provides this Awaitable but the caller, behave.
    // if returns false, indicates the caller will continue and
    // await_resume is called immidiately and the result is returned to the caller.
    // if returns true, the caller is suspended and the callee only resume
    // if callee's handler::resume() is called. otherwise, callee and caller both
    // are suspended and return back to main()/root caller function(i.e. the caller function
    // that is not a coroutine.
  }

  // handler.resume() from Awaitable suspend comes here.
  // can return value to the caller inside the coroutine.
  // the value can be obtained from handler.promise().RESULT.
  void await_resume();
  T await_resume() {return T{}; };

  Awaitable(T value) : value_(value) {}
};



struct ReturnType {
  struct promise_type {
    T value_;
    promise_type(T...); // optional

    ReturnType get_return_object() {
      return {};
      // or
      return {coroutine::from_promise(*this)};
    };

    std::suspend_always initial_suspend() {}

    template<std::convertible_to<T> From> // C++20 concept
        std::suspend_always yield_value(From&& from) {
            value_ = std::forward<From>(from); // caching the result in promise
            return {};
    }

    // above start; below shutdown

    void return_value(T/T&&/const T&); // store value T inside the ReturnType, caller retrieve the value from ReturnType. 
    void return_void();

    void unhandled_exception();
    
    // 1) if returns suspend_always; the caller has the chance to control how coroutine is going
    //     to be clean-up, e.g. release lock, release resources etc. and then call .destroy()
    //     manually, usually in the ReturnType's destructor. After .destroy() is called,
    //     std::coroutine_handle::operator bool continue to return true, this is due
    //     to coroutine_handle does initial with a coroutine(that it points to).
    //     And If std::coroutine_handle::destroy()
    //     is called, std::coroutine_handle::done() will have undefined behavior and should not be called.
    // 2) if returns suspend_never; the coroutine is destroyed immediately, and calling 
    //    std::coroutine_handle::done() is UB since frame is destroyed.
    // 3) if suspend_always, the coroutine is suspended and std::coroutine_handle::done() is True.
    std::suspend_always final_suspend() noexcept;
  };

  ReturnType(std::coroutine_handle<promise_type> handler) : handler_(handler) {}

  ReturnType() = default;
};

Jul 29, 2025

[C++] Coroutine examples

  1. C++ coroutine has two scopes: caller scope, coroutine scope (if you familiar with Python yield/send, same concept but more subtle with object lifetime and more flexible. Or Golang goroutine/channel [stackful], or C++ fiber[stackful])
  2. Suspend suspends the coroutine and back to the caller, caller uses handler to resume the coroutine
  3. Underneath using JMP instead of CALL since it's stackless coroutine.
  4. Beware that even `void coroutine(const int& arg)` that has co_await/co_yield/co_return, when it suspends, the binding arg has been stack rewinded thus destroyed.
    i.e.
    If a coroutine has a parameter passed by reference, resuming the coroutine after the lifetime of the entity referred to by that parameter has ended is likely to result in undefined behavior.

    The C++ core guidelines say not to use references at all.
  5. Just avoid references" isn't comprehensive, e.g. we have span, string_view, even though they are not reference, can still facing the same dangling issue after co_await/co_yield/co_return.
    Thus, a better contract is:
    Pass by Value with Owning Types.
  6. The way we ensure caller doesn't compile is by making Co non-moveable, and accepting it by value in the co_await implementation in the promise type.

    This means the only way to await it is if you do so immediately in the same full expression as the function call, so that guaranteed copy elision can kick in. 

    Because temporaries aren't destroyed until that full expression has been evaluated, the lifetimes work out perfectly using the usual language rules around lifetimes.

    Design TIP:
    Eliminated the problem with references by just declaring an entire pattern of code illegal.
      Co<void> UseInt_Async(const int& x) {
         printf("%d\n", x);
         co_await DoSomething();
         printf("%d\n", x);
      }
      
      k3::Co<void> UseInt_Async(const int&);
      // Works fine
      co_await UseInt_Async(17);
        
      // Compiler error due to k3::Co<void> is not moveable.
      k3::Co<void> co = UseInt_Async(17);
      co_await std::move(c);
  7. What if we want to use above pattern?
    Indirection, just like std::bind
    k3::Co<void> UseInt_Async(const int&);
    // Totally safe
    k3::Future<void> future{UseInt_Async, 17};
    printf("I created the future!\n");
    co_await std::move(future).Run();
  8. Co<void> has the interface concept of awaitable, as below.
  9.  Fan out or run in sequence; the implementation is imaginable.
      
    // Run all concurrently, finishing once all finish.
    k3::Co<void> FanOut(std::vector<k3::Future<void>> futures);
    // Run all concurrently, finishing when the first finishes.
    template <typename T>
    k3::Co<T> Race(std::vector<k3::Future<T>> futures);
    

  10. co_await awaitable;
    struct awaitable {
    	bool await_ready() { return false; // false: suspended, true: not suspended}
    	void await_suspend(std::coroutine_handle<> h) { // what to do when suspended}
    	void await_resume() { // in await_suspend's coroutine_handle calling .resume() comes here. and after this resume to coroutine.  }
        int await_resume() {return 42; // co_await returns value from here.}
    }; 
    co_yield
    co_return

Stack:

Go goroutines has dynamic stack size, default to 2kb. linux has thread size default to 8mb, 64k in production.
So in coroutine, how do we avoid stack overflow?

tail call comes to the rescue;
c++ coroutine guarantees a tail call into the other coroutine, and then you can do it again on the way back out. There's no need to have a stack frame to return to; everything is a JUMP instruction.
This is the only guaranteed[as of c++20] tail call in the standard; it's actually kind of a unique mixing of abstraction levels.
Co<void> Foo();
Co<void> Bar() {
  // Tail call into the body of Foo.
  co_await Foo();
  // Tail call back from the body of Foo.
  [...]
}
This indicates that with coroutine embedded inside a coroutine all use the same stack; thus
if there are multiple suspension, the stack can be overflown.

c9 solution:
we are never allowed to have one coroutine directly resume another.

Possible solution to the Notify/Wait pattern:
class Event {
Co<void> Wait();
// If there is a waiter, it will start running concurrently
// on another thread. The calling thread continues on.
// Look at the co_wait example below.
void Notify();
};
We could resolve this by breaking the assumption that there is only one thread available.
Instead we could resume the coroutine on a different thread, letting it run concurrently at the same time as the notifier.


Avoid executors in the coroutine library design:

Don't offer unnecessary configurability.
library should be agnostic to executors. All it has is its one thread-local queue of things that have been resumed by the running coroutine.
Of course individual things you wait for might have an executor internally. Like if you wait for an RPC to finish, you'll probably be resumed on one of a team of threads reading RPC replies from the network.
e.g.


// Hop to a specific executor. We need to run there because…
// look the co_await example below~
co_await Reschedule(my_executor);

Cancellation in library design

  • RPCs to stuck machines
  • Request hedging (idempotent operations)
  • Avoiding wasted work
  • Timeouts

The callee exists only to serve the caller. It must stop promptly if the caller loses interest.

  • the caller of a coroutine is always in control.
  • If the caller no longer wants the callee to run, the callee should stop running.
  • And it should do so promptly. Not after getting a response to its RPC. Not after a timeout expires. Immediately.
    All it should get to do is the kind of thing you want to do before you release a lock or unwind after an exception. Ensure internal invariants are restored; that kind of thing.
  • coroutine's local automatic variable won't be destructed until it is resumed till the end of the coroutine. (while is is on the heap)
  • If cancellation happens (which is to resume after coroutine suspended, and pass the cancellation bit to the coroutine_handler which be consumed by the awaitable, and the awaitable resume checks the bit to cancel the original code path.), the coroutine continues till the end.

Notes on structured concurrency; idea:
Children should not out-live parent.
Prefer structured concurrency wherever possible.
Good API design also helps with usability and safety.

Handler:

// task, aka handler, will be created through promise_type instance's get_return_object()
// and destructed once the coroutine function is returned to the caller.
// the destruct of task does not mean the promise_type instance is destructed, it still resides on the heap where
// coroutine is located.
struct task
{
    struct promise_type
    {
        task get_return_object() { return {}; // Always called first when coroutine being called. }
        std::suspend_never initial_suspend() { return {}; // Always called second when coroutine being called.   }
        std::suspend_never final_suspend() noexcept { return {}; / Always called last when coroutine is finished. (i.e. done)  }
        void return_void() { // called if return from coroutine with void.}
        std::suspend_always yield_value(T value) noexcept
        {
          // used for co_yeild, once co_yeild is called, the yield value is installed here.
          // and suspend afterwards due to return `std::suspend_always`
          return {};
        }
        
        std::suspend_always return_value(T value) {
          // used for co_return, once co_return is called with expr, the expr value is installed here.
          // and suspend afterwards due to return `std::suspend_always`
          return {};
        }
        void unhandled_exception() {}
    };
    
    task(std::coroutin_handler<promise_type> h) {
      // store the coroutin_handler inside the task.
    }
};

#include <iostream>
#include <coroutine>

// 1. The Promise Type
struct MyTaskPromise {
    // The compiler calls this to get the return object.
    // We return a 'MyTask' object, and in its constructor, we pass it the handle.
    struct MyTask get_return_object();

    std::suspend_never initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() noexcept {}
    void unhandled_exception() noexcept {}
};

// 2. The Task Type
// This is the wrapper around the coroutine_handle.
struct MyTask {
    using promise_type = MyTaskPromise;
    std::coroutine_handle<MyTaskPromise> handle;

    // The constructor takes the handle from the promise's get_return_object() call.
    MyTask(std::coroutine_handle<MyTaskPromise> h) : handle(h) {}
};

// Now we can define get_return_object() because MyTask is defined.
MyTask MyTaskPromise::get_return_object() {
    // This is the key line: we construct the MyTask object with the handle
    // to the coroutine that owns this promise.
    return MyTask{std::coroutine_handle<MyTaskPromise>::from_promise(*this)};
}

// 3. A simple awaitable to demonstrate suspension.
struct Awaitable {
    bool await_ready() { return false; }
    void await_resume() {}

    // This await_suspend returns `true`, which suspends the coroutine
    // and returns control to the caller (main).
    bool await_suspend(std::coroutine_handle<>) noexcept {
        std::cout << "-> Awaitable: Coroutine is suspending." << std::endl;
        return true;
    }
};

// 4. The Coroutine Function
MyTask MyCoroutine() {
    std::cout << "Coroutine: Starting." << std::endl;
    co_await Awaitable{}; // Coroutine suspends here.
    std::cout << "Coroutine: Resumed and finishing." << std::endl;
    co_return;
}

// 5. The Caller (main function)
int main() {
    // 1. The call to MyCoroutine() returns a 'MyTask' object.
    // This object contains the handle to the suspended coroutine.
    MyTask task = MyCoroutine();

    std::cout << "\nMain: Coroutine is suspended. I am the caller.\n" << std::endl;

    // 2. We can now use the handle stored inside the 'task' object
    // to resume the coroutine.
    if (task.handle) {
        std::cout << "Main: Resuming the suspended coroutine." << std::endl;
        task.handle.resume();
    }

    std::cout << "\nMain: Coroutine has finished its execution." << std::endl;

    // 3. Clean up the coroutine's memory.
    if (task.handle) {
        task.handle.destroy();
    }

    return 0;
}



co_yield Example:

#include <coroutine>
#include <iostream>
#include <optional>

template<std::movable T>
class Generator
{
public:
    struct promise_type
    {
        Generator<T> get_return_object()
        {
          std::cout << "get_return_object()\n"; // -2; Generator created with pointer to the heap
          return Generator{Handle::from_promise(*this)};
        }
        static std::suspend_always initial_suspend() noexcept
        {
          std::cout << "suspend_always initial_suspend()\n"; // -4 init. suspend. Go to the caller.
          return {};
        }
        static std::suspend_always final_suspend() noexcept
        {
          std::cout << "suspend_always final_suspend()\n"; // -15 coroutine ends, call this and suspend.
          // Back to the caller. Go to (13)
          return {};
        }
        std::suspend_always yield_value(T value) noexcept
        {
          std::cout << "yield_value() : " << value << "\n"; // -9, suspend. Go to the caller.
          current_value = std::move(value);
          return {};
        }
        // Disallow co_await in generator coroutines.
        void await_transform() = delete;
        [[noreturn]]
        static void unhandled_exception() { throw; }

        std::optional<T> current_value;
    };

    using Handle = std::coroutine_handle<promise_type>;

    explicit Generator(const Handle coroutine) :
        m_coroutine{coroutine}
    {
      std::cout << "Generator constructor\n"; // -3
    }

    Generator() = default;
    ~Generator()
    {
        // make sure no double free through handler.destroy() while
        // caller could have a copy of the handler.
        if (m_coroutine && !m_coroutine.done())
            m_coroutine.destroy();
      std::cout << "Generator destructor\n";
    }

    Generator(const Generator&) = delete;
    Generator& operator=(const Generator&) = delete;

    Generator(Generator&& other) noexcept :
        m_coroutine{other.m_coroutine}
    {
      std::cout << "Generator move constructor\n";
      other.m_coroutine = {};
    }
    Generator& operator=(Generator&& other) noexcept
    {
      std::cout << "Generator assign operator=\n";
        if (this != &other)
        {
            if (m_coroutine)
                m_coroutine.destroy();
            m_coroutine = other.m_coroutine;
            other.m_coroutine = {};
        }
        return *this;
    }

    // Range-based for loop support.
    class Iter
    {
    public:
        void operator++()
        {
          std::cout << "Iter ++ currnet value: " << *m_coroutine.promise().current_value << "\n";
          
          m_coroutine.resume(); // -11, resume from (9) yeild's suspend; JUMP to COROUTIN RIGHT AWAY!
          // Following cout is not run until coroutin suspend again.
          
          // -13, followed by (12) and after (9) yeild's suspend.
          std::cout << "Iter ++ resumed currnet value: " << *m_coroutine.promise().current_value << "\n";
        }
        const T& operator*() const
        {
          // -10, caller print out the value.
          std::cout << " Iter* return value: " << *m_coroutine.promise().current_value << "\n";
          return *m_coroutine.promise().current_value;
        }
        bool operator==(std::default_sentinel_t) const
        {
          // -16, caller is calling this from coroutin's suspend.
          std::cout << "Iter == called: !m_coroutine: " << (!m_coroutine) << " m_coroutine.done(): " << m_coroutine.done() << "\n";
            return !m_coroutine || m_coroutine.done();
        }

        explicit Iter(const Handle coroutine) :
            m_coroutine{coroutine}
        {}

    private:
        Handle m_coroutine;
    };

    Iter begin()
    {
      std::cout << "Iter begin\n"; // -5, caller range for calls begin()
        if (m_coroutine)
            m_coroutine.resume(); // -6 (4) suspended resumed. Go to coroutine.
        return Iter{m_coroutine};
    }

    std::default_sentinel_t end() { return {}; }

private:
    Handle m_coroutine;
};

template<std::integral T>
Generator<T> range(T first, const T last)
{
  // suspended right away since initial_suspend() returns std::suspend_always
  // returns to the caller with Generator instance
  std::cout << "Range\n"; // -7 from (6)
  while (first < last){
    std::cout << "Range first: " << first << "\n";
    co_yield first++; // -8, go to yield_value(), install `first` value, first is + 1, and suspend.
    // // -12, resume from (11) operator++()
    std::cout << "Range after first++: " << first << "\n";
  }
  // -14, coroutine end, suspend_always final_suspend() called.
}

int main()
{
  std::cout << "Start for loop\n"; // -1
  // Generator is destructed only once due to range loop extends lifetime.  
  for (const char i : range(65, 67))
      std::cout << i << "\n";
  // -17, out of range for loop scope, 
  // range(65, 67) returned `Generator` destructs.

  std::cout << "End for loop\n";
    std::cout << '\n';
}
stdout:
Start for loop
get_return_object()
Generator constructor
suspend_always initial_suspend()
Iter begin
Range
Range first: 65
yield_value() : 65
Iter == called: !m_coroutine: 0 m_coroutine.done(): 0
 Iter* return value: 65
A
Iter ++ currnet value: 65
Range after first++: 66
Range first: 66
yield_value() : 66
Iter ++ resumed currnet value: 66
Iter == called: !m_coroutine: 0 m_coroutine.done(): 0
 Iter* return value: 66
B
Iter ++ currnet value: 66
Range after first++: 67
suspend_always final_suspend()
Iter ++ resumed currnet value: 66
Iter == called: !m_coroutine: 0 m_coroutine.done(): 1
Generator destructor
End for loop


co_await Example:

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
 
auto switch_to_new_thread(std::jthread& out) {
    std::cout << "switch_to_new_thread start\n";
    struct awaitable {
        std::jthread* p_out;
        bool await_ready() {
		  std::cout << "await ready\n";
		  return false; 
	    };
    
    	void await_suspend(std::coroutine_handle<> h) {
	    	std::cout << "await_suspend\n";
	        std::jthread& out = *p_out;
    	    if (out.joinable())
        	  throw std::runtime_error("Output jthread parameter not empty");
	        out = std::jthread([h] { 
        		std::cout << "calling handler.resume()\n";
				h.resume();
    	    });
            
        // Potential undefined behavior: accessing potentially destroyed *this
        // std::cout << "New thread ID: " << p_out->get_id() << '\n';
	        std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
        }
    
    	void await_resume() {
	    	std::cout << "await_resume\n";
	    }
    };

    std::cout << "switch_to_new_thread about to return\n";
    
    return awaitable{&out};
}
 
struct task {
    struct promise_type {
        task get_return_object() {
			std::cout << "get_return_object()\n";
			return {}; 
		}
    
    	std::suspend_never initial_suspend() {
			std::cout << "inital_suspend()\n";
			return {}; 
		}
    
	    std::suspend_never final_suspend() noexcept {
			std::cout << "final_suspend()\n";
			return {}; 
		}
    
    	void return_void() {
			std::cout << "return_void\n";
		}
     
    	 void unhandled_exception() {}
    };

    ~task() {
	    std::cout << "task destruct\n";
    }
};
 
task resuming_on_new_thread(std::jthread& out) {
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
 
int main() {
    std::jthread out;
    std::cout << "start\n";
    resuming_on_new_thread(out);
    std::cout << "ending main()\n";
}
stdout:
start
get_return_object()
inital_suspend()
Coroutine started on thread: 140248882116480
switch_to_new_thread start
switch_to_new_thread about to return
await ready
await_suspend
New thread ID: 140248877143744
task destruct
ending main()
calling handler.resume()
await_resume
Coroutine resumed on thread: 140248877143744
return_void
final_suspend()