# Coroutines TS Customization Points This is intended to be a reference/cheatsheet/decoder ring, not an introduction. If you don't know anything about the Coroutine TS, I suggest reading Lewis Baker's excellent [series](https://lewissbaker.github.io/2017/09/25/coroutine-theory) of [blog](https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await) [posts](https://lewissbaker.github.io/2018/09/05/understanding-the-promise-type). Or if you are feeling brave, just go read the current [working draft](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4775.pdf) of the TS. This is also intended for people who need to know the inner workings of the TS, such as people *writing* [libraries](https://github.com/lewissbaker/cppcoro). This is not for people who just want to *use* coroutines with one of these libraries, since that is *much* simpler. ## Table of customization points A few long names are split up so that the table formats better. All names should be considered a single possibly-qualified identifier. Everything is explained in more detail [below](#Coroutine-function-transformation). These are split into sections based on what the customization point is attached to. | Name | Required | Attached To | Notes | | - | - | - | - | | `get_return_object()` | :heavy_check_mark: | Promise | What gets returned from the coroutine (eg `lazy<T>` or `generator<T>`) | | `get_return_object_on` `_allocation_failure()` | | Promise (static) | If present, `std::nothrow` will be passed to `operator new` | | `return_value()` **or** `return_void()` | :heavy_check_mark: | Promise | Argument is what `co_return` takes | | `unhandled_exception()` | :heavy_check_mark: | Promise | | | `yield_value()` | with `co_yield` | Promise | | | `await_transform()` | | Promise | | | `initial_suspend()` | :heavy_check_mark: | Promise | | | `final_suspend()` | :heavy_check_mark: | Promise | | | `Constructor()` | can be defaulted | Promise | Either nullary (default ctor) or gets to "preview" coro parameters | | `operator new()` | | Promise or global | Also gets "parameter preview" | | `operator delete()` | | Promise or global | No "parameter preview", but supports sized-deallocation | | <hr> | <hr> | <hr> | <hr> | `std::` `coroutine_traits<T, Args...>::promise_type` | | | Only provide this if you can't add a nested `promise_type` on your coroutine type. Also gets "parameter preview" for types.| | <hr> | <hr> | <hr> | <hr> | `operator co_await()` | | ProtoAwaitable or ADL free-function | Converts a ProtoAwaitable to an Awaitable | | <hr> | <hr> | <hr> | <hr> | `await_ready()` | :heavy_check_mark: | Awaitable | | | `await_suspend()` | :heavy_check_mark: | Awaitable | Only called if `await_ready()` returns `false`. Bool return has **opposite** meaning as `await_ready()` (ie. `true` means suspend)| | `await_resume()` | :heavy_check_mark: | Awaitable | | ## Table of `std::` things | Name | Meaning | | - | - | | `coroutine_traits<T>::promise_type` | `T::promise_type` unless specialized | | [`coroutine_handle<PromiseType>`](#coroutine_handleltPromiseTypegt) | A magical type that lets you resume or destroy a suspended coroutine. Can also get to or from a `PromiseType&` | | `coroutine_handle<>` | Type-erased version of above. The unerased type implicitly converts to the erased type. This conversion is harmless and encouraged when you don't need access to a Promise | | `noop_coroutine_handle` | A `coroutine_handle` that when returned from `await_suspend()` behaves as-if `void` or `true` were returned | | `noop_coroutine()` | Returns a `noop_coroutine_handle` | | `suspend_always` | A trivial Awaitable that **always** suspends and yields `void`. Useful for returning from `initial_suspend()`, `final_suspend()`, and `yield_value()` | `suspend_never` | A trivial Awaitable that **never** suspends and yields `void`. Useful for returning from `initial_suspend()` and `final_suspend()` ## `coroutine_handle<PromiseType>` ```cpp template <class Promise = void> struct coroutine_handle; /** * The type-erased base type for all coroutine_handles. * Allows libraries that don't care about the Promise type to deal * with a single concrete type which represents all coroutines. */ template <> struct coroutine_handle<void> // Usually written as coroutine_handle<> { /// Make a null coroutine_handle. constexpr coroutine_handle() noexcept; constexpr coroutine_handle(nullptr_t) noexcept; coroutine_handle& operator=(nullptr_t) noexcept; /// Is this a null coroutine_handle? constexpr explicit operator bool() const noexcept; /// Convert to/from an opaque address. Useful with C APIs. constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr); // // Remaining methods can only be called while the coro is suspended! // /// Is the coro suspended at the `final_suspend()` point? /// WARNING: this should never be used for coroutines that may return /// `suspend_never` from `final_suspend()`, since it will either return /// true or dereference a pointer to deleted memory! bool done() const; /// These both resume the coroutine where is last suspended. /// coroutine_handle objects can be treated as a `void()` function /// object, which simplfies integration into existing libraries /// which take the equivalent of a `std::function<void()>`. void resume() const; void operator()() const { resume(); } /// Destroy any local variables currently in scope inside the coroutine /// and delete the allocated frame buffer. This will automatically be /// called if resumed following the call to `final_suspend()`, or if /// that doesn't actually suspend. May be called any time the coroutine /// is supsended, not just at `final_suspend()`, however not all coroutines /// will be prepared to not wake up after suspending part way through, so /// This should only be done in cases where it is known to be safe. void destroy() const; }; /** * The specific type used by the coroutine machinery. */ template <class Promise> struct coroutine_handle { /// Can be constructed/retrieved the same as above. constexpr coroutine_handle() noexcept; constexpr coroutine_handle(nullptr_t) noexcept; coroutine_handle& operator=(nullptr_t) noexcept; constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr); /// Also supports conversion to/from a reference to the Promise. /// This is the only API gained by not type erasing. Promise& promise() const; static coroutine_handle from_promise(Promise&); /// Implicit conversion to erase Promise type constexpr operator coroutine_handle<>() const noexcept; /// Also provides the API from coroutine_handle<void> constexpr explicit operator bool() const noexcept; bool done() const; void operator()() const; void resume() const; void destroy() const; }; /// coroutine_handles are ordered by `addres()`. Maybe we'll get a <=> as well? constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept; constexpr bool operator!=(coroutine_handle<> x, coroutine_handle<> y) noexcept; constexpr bool operator<(coroutine_handle<> x, coroutine_handle<> y) noexcept; constexpr bool operator>(coroutine_handle<> x, coroutine_handle<> y) noexcept; constexpr bool operator<=(coroutine_handle<> x, coroutine_handle<> y) noexcept; constexpr bool operator>=(coroutine_handle<> x, coroutine_handle<> y) noexcept; template <class Promise> struct hash<experimental::coroutine_handle<Promise>>; ``` ## Coroutine function transformation A coroutine function like `MyCoroType func(Args... args) { BODY; }` is transformed to something roughly like the following. This is done for any function where `BODY` contains any of [`co_return`](#co_return), [`co_await`](#co_await), or [`co_yield`](#co_yield) (which is really also just a `co_await`). The detailed expansions below may refer back to local variables declared here. The names are not actually visible to the user-provided `BODY` statements. ```cpp // If `func` is a method on some Type, pretend it is just a free funtion // with a `Type&` argument before the declared arguments. MyCoroType func(Args... args) { using PromiseType = std::coroutine_traits<MyCoroType, Args...>::promise_type; // All local variables are actually constructed inside this buffer. // The arguments to `func()` will also be copied into this buffer. // The allocation may be elided if the compiler can prove it safe. // `operator new` will first be looked up in PromiseType, then globally. // If the found name is not callable with `args...`, it will try again // without passing them. This is known as "parameter preview" and allows // things like passing the allocator that should be used. void* coro_frame; size_t coro_frame_size = /*...*/; if constexpr (PromiseType::get_return_object_on_allocation_failure exists) { coro_frame = operator new(coro_frame_size, std::nothrow, args...); if (!coro_frame) { return PromiseType::get_return_object_on_allocation_failure(); } } else { coro_frame = operator new(coro_frame_size, args...); } // Will fall back to `PromiseType()` if it can't // be constructed like this. Note that the promise object // will be allocated in the coro_frame buffer and will live // as long as the coroutine. This makes a handy place to // stash coordination state that would normally go in // some refcounted SharedState object referenced from both // the promise and MyCoroType objects. If you do this, // make sure that `final_suspend()` actually suspends! auto promise = PromiseType(args...); auto my_handle = std::coroutine_handle<PromiseType>::from_promise(promise); auto protoReturnValue = promise.get_return_object(); // This is the value that is actually returned to the caller // of `func()`. It is unspecified whether this conversion // happens before calling `initial_suspend()` or the first // time the coroutine actually suspends or returns, which // is when `func()` returns to its caller. If it is called // late, you can use that to optimize for cases where the // coro completes without ever suspending. MyCoroType actualReturnValue = std::move(protoReturnValue); // This may move inside the try block in a future version of the TS. co_await promise.initial_suspend(); try { // This will contain usually a call to `promise.return_void()` // or `promise.return_value()`. See co_return below. BODY; if constexpr (promise has return_void()) { // Make rolling off the end of BODY well defined for // coros that (logically) return `void`. For other // kinds ofcoros this is UB that your compiler should // warn about, just like in normal functions. co_return; } } catch (...) { // This can get the exception using either `throw;` // or `std::current_exception()`. If this throws, // the coroutine is considered suspended at its final // suspend point, without calling final_suspend(). promise.unhandled_exception(); } co_await promise.final_suspend(); // It is common for coroutines to never be resumed from // this call if they suspend here. In that case, the // following line will never execute, and something // else must free the storage by calling `destory()` // on the coroutine handle. // Will fallback to single-argument form. operator delete(coro_frame, coro_frame_size); } ``` ### `co_await` The expression `co_await makeProtoAwaitable()` expands to roughly the following. Commented out portions are optional and will be used if present. `SUSPEND_HERE()` is shorthand for returning from the call to the function that creates the coroutine object, or from the call to `resume()` on the handle. `SUSPEND_HERE_AND_TAIL_JUMP_TO(dest)` is shorthand for doing `SUSPEND_HERE()` and then **after** poping the stack frame calling `dest.resume()` without actually returning control to the caller/resumer. ```cpp [&]() -> decltype(auto) { // Immediately invoked below // Note: The await_trasnform operation is only done on // users' co_awaits, not the implicit co_awaits on the // returns from yield_value, initial_suspend, and // final_suspend, because the promise is already involved // in those. auto&& Awaitable awaitable = /*operator co_await*/ ( /*promise.await_transform*/ ( makeProtoAwaitable() ) ); if (!awaitable.await_ready()) { // If await_ready() returns false, the coroutine enters a "suspended" state. // This means that it is legal to do things like call destroy() or // resume() on the handle passed to await_suspend(). However, rather than // calling resume(), it is better to return the handle so that a tail-call // is made rather than recursively growing the stack. using SuspendRet = decltype(awaitable.await_suspend(my_handle)); if constexpr (is_void<SuspendRet>()) { awaitable.await_suspend(my_handle) SUSPEND_HERE(); } else if constexpr (is_bool<SuspendRet>()) { if (awaitable.await_suspend(my_handle)) { SUSPEND_HERE(); } } else { auto dest = awaitable.await_suspend(my_handle); if (dest is a noop_coroutine_handle) { SUSPEND_HERE(); } else { // This is effectively a no-op if dest == my_handle SUSPEND_HERE_AND_TAIL_JUMP_TO(dest); } } // Once we get here, either by being resumed, or never reaching a SUSPEND, // the coroutine leaves the "suspended" state, and it is no longer legal // to call resume() or destroy() on the handle. } return awaitable.await_resume(); // May return void }(); ``` Yes `operator co_await()` is optional, and yes that does mean that often `co_await` expressions will not actually call any `operator co_await`. ### `co_yield` The expression `co_yield value` expands to `co_await promise.yield_value(value)` (see above for how to expand the `co_await`). Promises will *usually* just return `suspend_always()` here, however that isn't required. If it returns a non-void ProtoAwaitable, you have a two-way generator that supports things like `auto input = co_yield output;` While similar to `co_await`, there is one notable grammatical difference due to their different intended usages. `co_await` binds tightly to its argument like any other unary operator, such as `*expr`. `co_yield` binds loosely like `return expr`. So `co_await a + b` is parsed as `(co_await a) + b`, while `co_yield a + b` is parsed as `co_yield (a + b)`. In most cases you don't need to worry about this since they behave how you would expect, but be careful with complex expressions if you are using either in a "weird" way. ### `co_return` The statement `co_return;` is rewritten to `promise.return_void();` and `co_return expr;` is rewritten to `promise.return_value(expr);`. In both cases, exection will then leave the `BODY;` of the transformed coroutine, and go to the call to `promise.final_suspend()`. This is logically like normal `return` except in a coroutine. Normal `return` is banned in a coroutine, and `co_return` can't be in a non-coroutine function because it will turn it into a coroutine. ## Templates These are some templates (not in the c++ sense) ready for copy-pasting! They don't need to be a templates (in the c++ sense), but often are, or are inner types of templates. Nota Bene: These have not been tested. Treat them as slideware rather than as production code. ### Promise ```cpp template <typename T> struct promise_type { // If you care about parameter preview template <typename... Args> promise_type(Args&&...); auto initial_suspend() { return std::experimental::suspend_always(); } auto final_suspend() { return std::experimental::suspend_always(); } auto get_return_object() { return MyCoroType(this); } void return_value(T val) {} void unhandled_exception() {} // If you need to see ProtoAwaitables before `operator co_await` // is called on them. template <typename T> auto await_transform(T&& awaitable); // If you want to control allocation template <typename... Args> void* operator new(size_t size, Args...); void operator delete(void* p, size_t size); // Only if you care about recovering from allocation failure static MyCoroType get_return_object_on_allocation_failure(); }; struct promise_type /*void*/ { // If you care about parameter preview template <typename... Args> promise_type(Args&&...); auto initial_suspend() { return std::experimental::suspend_always(); } auto final_suspend() { return std::experimental::suspend_always(); } auto get_return_object() { return MyCoroType(this); } void return_void() {} void unhandled_exception() {} // If you want to support co_yield. // You rarely have this in the same type as return_value. void yield_value(T) {} // If you need to see ProtoAwaitables before `operator co_await` // is called on them. template <typename T> auto await_transform(T&& awaitable); // If you want to control allocation template <typename... Args> void* operator new(size_t size, Args...); void operator delete(void* p, size_t size); // Only if you care about recovering from allocation failure static MyCoroType get_return_object_on_allocation_failure(); }; ``` ### Awaitable ```cpp template <typename T> struct MyAwaitable { bool await_ready() { return false; } void await_suspend(coroutine_handle<> h) {} T await_resume() {} }; ``` ### Future-like type ```cpp template <typename T> struct MyFuture { struct promise_type; // see above // Can also just *be* an Awaitable with no `operator co_await` MyAwaitable<T> operator co_await(); // see above }; ``` ### Generator (Range) type These use `co_yield`. While this returns `T&` from the iterator's `operator*()` and stores a copy of the value, a real implementation should strongly consider returning `T&&` and storing a pointer, as is done by `std::generator`. See [P2529](https://wg21.link/p2529r0) by the same author as this page for more info. ```cpp template <typename T> struct generator { struct promise_type { suspend_never initial_suspend() { return {}; } suspend_always final_suspend() { return {}; } auto yield_value(T v) { val.emplace(std::move(v)); return suspend_always(); } auto get_return_object() { return generator(this); } void return_void() {} void unhandled_exception() { throw; } auto coro() { return coroutine_handle<promise_type>::from_promise(*this); } std::optional<T> val; }; generator(generator&& source) : p(std::exchange(source.p, nullptr)) {} explicit generator(promise_type* p) :p(p) {} ~generator() { if (p) p->coro().destroy(); } // generator<T> is a Range. You can use it in a range-for loop. struct EndSentinel{}; auto end() { return EndSentinel(); } auto begin() { struct Iter { // You probably want to add iterator_tag and friends. // That isn't needed if you only want to support range-for. bool operator!=(EndSentinel) const { return !p->coro().done(); } void operator++() { p->coro().resume(); } T& operator*() const { return *p->val; } promise_type* p; }; return Iter{p}; } promise_type* p; }; ```