Async Await CPP PDF
Async Await CPP PDF
Async Await CPP PDF
Supersedes: N3977
Date: 2014/10/10
Reply to: Gor Nishanov (gorn@microsoft.com), Jim Radigan (jradigan@microsoft.com)
1
Coroutine promise Requirements .......................................................................................................... 10
Resumption function object ................................................................................................................... 13
await operator ........................................................................................................................................ 14
Evaluation of await expression ............................................................................................................... 14
yield statement ....................................................................................................................................... 15
Return statement .................................................................................................................................... 16
await-for statement ................................................................................................................................ 16
Trivial awaitable types ............................................................................................................................ 17
An expository Resumable Function Implementation ............................................................................. 18
no-except operations .................................................................................................................................. 19
Allocation failure ..................................................................................................................................... 19
Generalizing coroutine’s promise set_exception ................................................................................... 20
Await expression: Unwrapping of an eventual value ............................................................................. 20
Await expression: Failure to launch an asynchronous operation........................................................... 21
Asynchronous cancellation ......................................................................................................................... 21
Stateful Allocators Support ......................................................................................................................... 22
Proposed Standard Wording....................................................................................................................... 23
Acknowledgments....................................................................................................................................... 23
References .................................................................................................................................................. 23
Appendix A: An example of generator coroutine implementation ............................................................ 24
Appendix B: boost::future adapters ........................................................................................................... 27
Appendix C: Awaitable adapter over OS async facilities............................................................................. 28
Appendix D: Exceptionless error propagation with boost::future.............................................................. 29
2
Coroutine State / Coroutine Frame
A state that is created when coroutine is first invoked and destroyed once coroutine execution
completes. Coroutine state includes a coroutine promise, formal parameters, variables and temporaries
with automatic storage duration declared in the coroutine body and an implementation defined
platform context. A platform context may contain room to save and restore platform specific data as
needed to implement suspend and resume operations.
Coroutine Promise
A coroutine promise contains library specific data required for implementation of a higher-level
abstraction exposed by a coroutine. For example, a coroutine implementing a task-like semantics
providing an eventual value via std::future<T> is likely to have a coroutine promise that contains
std::promise<T>. A coroutine implementing a generator may have a promise that stores a current value
to be yielded and a state of the generator (active/cancelling/closed).
Generator
A coroutine that provides a sequence of values. The body of the generator coroutine uses a yield
statement to specify a value to be passed to the consumer. Emitting a value suspends the coroutine,
invoking a pull operation on a channel resumes the coroutine.
Stackless Coroutine
A stackless coroutine is a coroutine which state includes variables and temporaries with automatic
storage duration in the body of the coroutine and does not include the call stack.
Resumable Function
Proposed C++ language mechanism to implement stackless coroutines.
Discussion
Motivation for extending C++ language and libraries to support coroutines was covered by papers N3858
(resumable functions) and N3985 (A proposal to add coroutines to C++ standard library) and will not be
repeated here.
Design goals for this revision of resumable functions were to extend C++ language and standard library
to support coroutines with the following characteristics:
3
Highly scalable (to billions of concurrent coroutines).
Highly efficient resume and suspend operations comparable in cost to a function call overhead.
Seamless interaction with existing facilities with no overhead.
Open ended coroutine machinery allowing library designers to develop coroutine libraries
exposing various high-level semantics, such as generators, goroutines, tasks and more.
Usable in environments where exception are forbidden or not available
Unlike N3985 (A proposal to add coroutine to the C++ standard library), which proposes certain high-
level abstractions (coroutine-based input / output iterators), this paper focuses only on providing
efficient language supported mechanism to suspend and resume a coroutine and leaves high-level
semantics of what coroutines are to the discretion of a library developer and thus is comparable to
Boost.Context rather than Boost.Coroutine / N3985.
Stackless vs Stackful
Design goals of scalability and seamless interaction with existing facilities without overhead (namely
calling into existing libraries and OS APIs without restrictions) necessitates stackless coroutines.
General purpose stackful coroutines that reserve default stack for every coroutine (1MB on Windows,
2MB on Linux) will exhaust all available virtual memory in 32-bit address space with only a few thousand
coroutines. Besides consuming virtual memory, stackful coroutines lead to memory fragmentation, since
with common stack implementations, besides reserving virtual memory, the platform also commits first
two pages of the stack (one as a read/write access to be used as a stack, another to act as a guard page
to implement automatic stack growth), even though the actual state required by a coroutine could be as
small as a few bytes.
A mitigation approach such as using split-stacks requires the entire program (including all the libraries
and OS facilities it calls) to be either compiled with split-stacks or to incur run-time penalties when
invoking code that is not compiled with split-stack support.
A mitigation approach such as using a small fixed sized stack limits what can be called from such
coroutines as it must be guaranteed that none of the functions called shall ever consume more memory
than allotted in a small fixed sized stack.
Implementation Experience
We implemented language changes described in this paper in Microsoft C++ compiler to gain experience
and validate coroutine customization machinery. The following are illustrations of what library designers
can achieve using coroutine mechanism described in this paper.
Note the usage of proposed, await operator, yield and await-for statements.
Asynchronous I/O
The following code implements zero-overhead abstractions over asynchronous socket API and windows
threadpool.
std::future<void> tcp_reader(int total)
{
char buf[64 * 1024];
auto conn = await Tcp::Connect("127.0.0.1", 1337);
do
4
{
auto bytesRead = await conn.read(buf, sizeof(buf));
total -= bytesRead;
}
while (total > 0);
}
Execution of this program incurs only one memory allocation12 and no virtual function calls. The
generated code is as good as or better than what could be written in C over raw OS facilities. The better
part is due to the fact that OVERLAPPED structures (used in the implementation of Tcp::Dial and
conn.read) are temporary objects on the frame of the coroutine whereas in traditional asynchronous C
programs OVERLAPPED structures are dynamically allocated for every I/O operation (or for every
distinct kind of I/O operation) on the heap.
Allocation of a future shared state (N3936/[futures.state]) associated with the future is combined with
coroutine frame allocation and does not incur an extra allocation.
Generator
Another coroutine type was implemented to validate the generator pattern and a coroutine cancellation
mechanics:
generator<int> fib(int n)
{
int a = 0;
int b = 1;
while (n-- > 0)
{
yield a;
auto next = a + b;
a = b;
b = next;
}
}
int main()
{
for (auto v : fib(35)) {
std::cout << v << std::endl;
if (v > 10)
break;
}
}
Recursive application of generators allows to mitigate stackless coroutine inability to suspend from
nested stack frames. This example is probably the most convoluted way to print number in range
[1..100).
recursive_generator<int> range(int a, int b)
{
1
Allocation of the frame of a resumable function
2
not counting memory allocations incurred by OS facilities to perform an I/O
5
auto n = b - a;
if (n <= 0)
return;
if (n == 1)
{
yield a;
return;
}
auto mid = a + n / 2;
int main() {
for (auto v : range(1, 100))
std::cout << v << std::endl;
}
Parent-stealing parallel computations
It is possible to adopt coroutine mechanics to support parallel scheduling techniques such as parent
stealing [N3872].
spawnable<int> fib(int n) {
if (n < 2) return n;
return await(fib(n - 1) + fib(n - 2));
}
In this example operator+ is overloaded for spawnable<T> type. Operator + schedules fib(n – 2) to a
work queue whereas the execution continues into fib(n-1). When eventual values for both fib(n-1) and
fib(n-2) are ready, fib(n) is resumed and result of await expression is computed as the sum of the
eventual value of the left and right operand to + operator.
Utilizing parent-stealing scheduling allows to compute fib(42) in less than 12k of space, whereas
attempting to use more traditional scheduling will cause state explosion that will consume more than
2gig of memory around fib(32).
3
In constant space with only log n iterations
6
auto val = await left.pull();
await right.push(val + 1);
}
}
int main()
{
static const int N = 1000 * 1000;
std::vector<channel<int>> c(N + 1);
c.front().sync_push(0);
Reactive Streams
Resumable functions can be used as producers, consumers and transformers of reactive streams in
recently rediscovered [Rx, ReactiveX, RxAtNetflix] functional reactive programming [FRP].
As a producer:
async_generator<int> Ticks()
{
for(int tick = 0;; ++tick)
{
yield tick;
await sleep_for(1ms);
}
}
As a transformer: (adds a timestamp to every observed value)
template<class T>
async_generator<pair<T, system_clock::time_point>>
Timestamp(async_read_stream<T> S)
{
for await(v: S) yield {v, system_clock::now()};
}
7
Resumable lambdas as generator expressions
Resumable lambdas can be used as generator expressions [PythonGeneratorExpressions].
squares = (x*x for x in S) // python
In this case squares is a lazy transformer of sequence S and similar in that respect to boost range
adapters [BoostRangeAdapter].
Conceptual Model
Resumable Function
A function or a lambda is called resumable function or resumable lambda if a body of the function or
lambda contains at least one suspend/resume point. Suspend/resume points are expressions with one
or more await operators, yield statements or await-for statements. From this point on, we will use the
term resumable function to refer to either resumable lambda or resumable function.
Suspend/resume points indicate the location where execution of the resumable function can be
suspended and control returned to the current caller with an ability to resume execution at
suspend/resume point later.
N3936/[intro.execution]/7 defines that suspension of a block preserves the automatic variables in a case
of a function call or receipt of a signal. We propose to extend that language to coroutine suspension as
well.
From the perspective of the caller, resumable function is just a normal function with that particular
signature. The fact that a function is implemented as resumable function is unobservable by the caller.
In fact, v1 version of some library can ship an implementation of some functions as resumable and
switch it later to regular functions or vice versa without breaking any library user.
Design Note: Original design relied on resumable keyword to annotate resumable functions or lambdas.
This proposal does away with resumable keyword relying on the presence of suspend/resume points.
There were several motivations for this change.
1. It eliminates questions such as: Is resumable a part of signature or not? Does it change a calling
conventions? Should it be specified only on a function definition?
2. It eliminates compilation errors due to the absence of resumable keyword that were in the category:
“A compiler knows exactly what you mean, but won’t accept the code until you type ‘resumable’.”
3. Usability experience with the resumable functions implemented in C++ compiler by the authors. Initial
implementation had resumable keyword and writing code felt unnecessarily verbose with having to type
resumable in the declarations and definitions of functions or lambda expressions.
Resumable traits
Resumable traits are specialized by resumable functions to select an allocator and a coroutine promise
to use in a particular resumable function.
8
If the signature of a resumable function is
R func(T1, T2, … Tn)
then, a traits specialization std::resumable_traits<R,T1,T2,…,Tn> will indicate what allocator and what
coroutine promise to use.
For example, for coroutines returning future<R>, the following trait specialization can be provided.
template <typename R, typename... Ts>
struct resumable_traits<std::future<R>, Ts...>
{
using allocator_type = std::allocator<char>;
using promise_type = some-type-satisfying-coroutine-promise-requirements;
};
Design note: Another design option is to use only the return type in specializing the resumable traits.
The intention for including parameter types is to enable using parameters to alter allocation strategies
or other implementation details while retaining the same coroutine return type.
A parameter copy is not required if a coroutine never suspends or if it suspends but its parameters will
not be accessed after the coroutine is resumed.
If a parameter copy/move is required, class object moves are performed according to the rules
described in Copying and moving class objects section of the working draft standard 3936/[class.copy].
An implementation is allowed to elide calls to the allocator’s allocate and deallocate functions and use
stack memory of the caller instead if the meaning of the program will be unchanged except for the
execution of the allocate and deallocate functions.
9
1. If a yield statement and either an await expression or an await-for statement are present, then
the return type is default-async-generator<T,R>, where T is deduced from the yield statements
and R is deduced from return statements according to the rules of return type deduction
described in N3936/[dcl.spec.auto].
2. Otherwise, if an await expression or an await-for statement are present in a function, then
return type is default-standard-task-type<T> where type T is deduced from return statements as
described in N3936/[dcl.spec.auto].
3. Otherwise, If a yield statement is present in a function, then return type is default-generator-
type<T>, where T is deduced from the yield statements according to the rules of return type
deduction described in N3936/[dcl.spec.auto].
Until that time, an attempt to define resumable functions with auto / decltype(auto) and no trailing
return type should result in a diagnostic message.
Variable Definition
P Coroutine promise type
P A value of type P
E A value of std::exception_ptr type
Rh A value of type std::resumable_handle<P>
T An arbitrary type T
V A value of type T
Expression Note
P{} Constructs a promise type.
10
p.get_return_object(rh) get_return_object is invoked by the coroutine to construct the
return object prior to the first suspend operation.
p.set_result(v) Sets the value associated with the promise. set_result is invoked by
a resumable function when return <expr> ; statement is
encountered in a resumable function.
11
initial_suspend() Returns: awaitable expression
This allows library designer to store the eventual value of the task,
or the current value of the generator within the coroutine promise.
12
Resumption function object
A resumable function has the ability to suspend evaluation by means of await operator or yield and
await-for statements in its body. Evaluation may later be resumed at the suspend/resume point by
invoking a resumption function object.
resumable_handle() = default;
explicit resumable_handle(std::nullptr_t);
resumable_handle& operator = (nullptr_t);
};
using resumable_handle<>::resumable_handle;
resumable_handle& operator = (nullptr_t);
};
Note, that by design, a resumption function object can be “round tripped” to void * and back. This
property allows seamless interactions of resumable functions with existing C APIs4.
Resumption function object has two forms. One that provides an ability to resume evaluation of a
resumable function and another, which additionally allows access to the coroutine promise of a
particular resumable function.
4
Usually C APIs take a callback and void* context. When the library/OS calls back into the user code, it invokes the
callback passing the context back. from_address() static member function allows to reconstitute
resumable_handle<> from void* context and resume the coroutine
13
Bikeshed: resumption_handle, resumption_object, resumable_ptr, basic_resumable_handle instead of
resumable_handle<void>, from_raw_address, to_raw_address, from_pvoid, to_pvoid.
await operator
is a unary operator expression of the form: await cast-expression
Where __expr is a variable defined for exposition only, and _ExprT is the type of the cast-expression, and
_PromiseT is a type of the coroutine promise associated with current resumable function and the rest
defined as follows:
5
The motivation for this is to avoid interfering with existing exception propagation mechanisms, as they may be
significantly (and negatively so) impacted should await be allowed to occur within exception handlers.
14
If none of await_xxx functions can throw an exception, the awaitable type
is called a nothrow awaitable type and expression of this type a nothrow
awaitable expressions.
resumption-function- A function object of type std::resumable_handle<_PromiseT>. When
object function object is invoked it will resume execution of the resumable
function at the point marked by suspend-resume-point.
suspend-resume-point When this point is reached, the coroutine is suspended. Once resumed,
execution continues immediately after the suspend-resume-point
cancel-check For all await expressions except for the one implicitly synthesized by a
compiler at the end of the function it is
Design Note: rules for lookup of await_xxx identifiers mirror the look up rules for range-based for
statement. We also considered two other alternatives (we implemented all three approaches to test
their usability, but found the other two less convenient than the one described above):
1. To have only ADL based lookup and not check for member functions. This approach was rejected as it
disallowed one of the convenient patterns that was developed utilizing await. Namely to have compact
declaration for asynchronous functions in a form: auto Socket::AsyncRead(int count) { struct awaiter {…};
return awaiter{this, count}) };
2. Another approach considered and rejected was to have an operator await function found via ADL and
having it to return an awaitable_type that should have await_xxx member functions defined. It was
found more verbose than the proposed alternative.
yield statement
A yield statement is a statement of form:
yield expression;
or
yield braced-init-list;
Where a <Promise> refers to the coroutine promise of the enclosing resumable function.
15
Design note: yield is a popular identifier, it is used in the standard library, e.g. this_thread::yield().
Introducing a yield keyword will break existing code. Having a two word keyword, such as yield return
could be another choice.
Another alternative is to make yield a context-sensitive keyword that acts like a keyword at a statement
level and as an identifier otherwise. To disambiguate access to a yield variable or a function, yield has to
be prefixed by ::yield, ->yield and .yield. This will still break some existing code, but allows an escape
hatch to patch up the code without having to rename yield function which could be defined by the
libraries a user have no source access to.
Return statement
A return statement in a resumable function in a form return expression; is equivalent to:
Where end-label is a label at the end of the user provided body of the resumable function, just prior to
the await <promise-expr>.final_suspend().
If resumable function does not have return statements in the form return expression; or return braced-
init-list; then the function acts as if there is an implicit return; statement at the end of the function.
await-for statement
An await-for statement of the form:
for await ( for-range-declaration : expression ) statement
is equivalent to
{
auto && __range = expression;
for ( auto __begin = await begin-expr,
__end = end-expr;
__begin != __end;
await ++__begin )
{
for-range-declaration = *__begin;
statement
}
}
16
A model for consuming values from an asynchronous input stream looks like this:
for (;;) {
initiate async pull()
wait for completion of async
if (no more)
break;
process value
}
For an input_iterator, frequent implementation of end() is a tag value that makes iterator equality
comparison check for the end of the sequence, therefore, != end() is essentially an end-of-stream check.
Design note: We implemented two variants of await-for statement to evaluate their aesthetical appeal
and typing convenience. One form was for await(x:S) , another await for(x:S)
Even though our initial choice was await for, we noticed that the brain was so hardwired to read things
starting with for as loops, that await for did not register as loop when reading the code.
17
These types are used in implementations of coroutine promises. Though they are trivial to implement,
including them in the standard library eliminates the need for every library designer from doing their
own implementation.
For example, generator<T> coroutine listed in the Appendix A, defines yield_value member function as
follows:
std::suspend_always promise_type::yield_value(T const& v) {
this->current_value = &v;
return{};
}
An expository Resumable Function Implementation
Note: The following section is for illustration purposes only. It does not prescribe how resumable
functions must be implemented.
Compiler can constructs a function that behaves as if the following code was generated:
R foo(T1 a, T2 b) {
using __traits = std::resumable_traits<R, T1, T2>;
struct __Context {
__traits::promise_type _Promise;
T1 a;
T2 b;
18
Where, <X> is a constant representing the number of bytes that needs to be allocated to accommodate
variables with automatic storage duration in the body of the resumable function and platform specific
data that is needed to support resume and suspend.
Access to variables and temporaries with automatic storage duration in the body of operator() should be
relative to “this” pointer at the offset equal to sizeof(*this).
no-except operations
C++ exceptions represent a barrier to adoption of full power of C++. While this is unfortunate and may
be rectified in the future, the current experience shows that kernel mode software, embedded software
for devices and airplanes [JSF] forgo the use of C++ exceptions for various reasons.
Making coroutine dependent on C++ exceptions will limit their usefulness in contexts where
asynchronous programming help is especially valuable (kernel mode drivers, embedded software, etc).
The following sections described how exceptions can be avoided in implementation and applications of
resumable functions.
Allocation failure
To enable handling of allocation failures without relying on exception mechanism, resumable_traits
specialization can declare an optional static member function
get_return_object_on_allocation_failure.
If an allocation has failed, a resumable function will use static member function
get_return_object_on_allocation_failure() to construct the return value.
19
Generalizing coroutine’s promise set_exception
In exception-less environment, a requirement on set_exception member of coroutine promise needs to
be relaxed to be able to take a value of an arbitrary error type E and not be limited to just the values of
type std::exception_ptr. In exception-less environment, not only std::exception_ptr type may not be
supported, but even if it were supported it is impossible to extract an error from it without relying on
throw and catch mechanics.
A straightforward implementation of await_resume() for getting an eventual value from the future<T>
will call .get() that will either return the stored value or throw an exception. If unhandled, an exception
will be caught by a catch(…) handler of the resumable function and stored as an eventual result in a
coroutine return object.
In the environments where exceptions are not allowed, implementation can probe for success or failure
of the operation prior to resuming of the coroutine and use <promise>.set_exception to convey the
failure to the promise. Coroutine promise, in this case, need to have cancellation_requested() to return
true if an error is stored in the promise.
Here is how await_suspend may be defined for our hypothetical kernel_future<T> as follows:
template <typename Promise>
void kernel_future::await_suspend(std::resumable_handle<Promise> p) {
this->then([p](kernel_future<T> const& result) {
if (result.has_error())
{
p.promise().set_exception(result.error());
}
p(); // resume the coroutine
});
}
20
Await expression: Failure to launch an asynchronous operation
If an await_suspend function failed to launch an asynchronous operation, it needs to prevent suspension
of a resumable function at the await point. Normally, it would have thrown an exception and would
have avoided suspend-resume-point. In the absence of exceptions, we can require that await_suspend
must return false, if it failed to launch an operation and true otherwise. If false is returned from
await_suspend, then coroutine will not be suspended and will continue execution. Failure can be
indicate via set_exception mechanism as described in the previous section.
With all of the changes described in this section, await expr will be expanded into equivalent of:
{
auto && __expr = cast-expression;
if ( ! await-ready-expr && await-suspend-expr)
suspend-resume-point
}
cancel-expression;
return expr-await_resume();
}
With the extensions described above it is possible to utilize await and resumable functions in the
environment where exceptions are banned or not supported.
Asynchronous cancellation
An attempt to cancel a coroutine that is currently suspended awaiting completion of an asynchronous
I/O, can race with the resumption of a coroutine due to I/O completion. The coroutine model described
in this paper can be extended to tackle asynchronous cancellation. Here is a sketch.
If a coroutine needs to be cancelled, it invokes a cancel_routine if one is currently associated with the
coroutine promise. If cancel_routine returns true, it indicates that the operation in progress was
successfully cancelled and the coroutine will not be resumed by the asynchronous operation. Thus, the
execution of the coroutine is under full control of the caller. If cancel_routine returns false, it means that
an asynchronous operation cannot be cancelled and coroutine may have already been resumed or will
be resumed at some point in the future. Thus, coroutine resources cannot be released until pending
asynchronous operation resumes the coroutine.
The following is an example of extending sleep_for awaiter from Appendix C to support asynchronous
cancellation.
template <typename Promise>
bool await_suspend(std::resumable_handle<Promise> resume_cb) {
auto & promise = resume_cb.promise();
if (promise.begin_suspend())
21
{
timer = CreateThreadpoolTimer(TimerCallback, resume_cb.to_address(), 0);
if (timer)
{
promise.set_cancel_routine(timer, TimerCancel);
SetThreadpoolTimer(timer, (PFILETIME)&duration, 0, 0);
promise.done_suspend();
return true;
}
else {
promise.set_exception(
std::system_error(std::system_category(), GetLastError());
}
}
promise.cancel_suspend();
return false;
}
Where begin_suspend, cancel_suspend and done_suspend are used to help to solve races when
cancellation is happening concurrently with invocation of await_suspend.
We do not propose this mechanism yet as we would like to gain more experience with developing
libraries utilizing resumable functions described in this paper.
int main()
{
for (auto v : fib(allocator_tag{}, g_MyAlloc, 35)) {
std::cout << v << std::endl;
if (v > 10)
break;
}
}
22
Proposed Standard Wording
No wording is provided at the moment.
Acknowledgments
Great thanks to Artur Laksberg, Chandler Carruth, Gabriel Dos Reis, Deon Brewis, James McNellis,
Stephan T. Lavavej, Herb Sutter, Pablo Halpern, Robert Schumacher, Michael Wong, Niklas Gustafsson,
Nick Maliwacki, Vladimir Petter, Slava Kuznetsov, Oliver Kowalke, Lawrence Crowl, Nat Goodspeed,
Christopher Kohlhoff for your review and comments and Herb, Artur, Deon and Niklas for trailblazing,
proposing and implementing resumable functions v1.
References
[N3936] Working Draft, Standard for Programming Language C++
[Revisiting Moura, Ana Lúcia De and Ierusalimschy, Roberto. "Revisiting coroutines".
Coroutines] ACM Trans. Program. Lang. Syst., Volume 31 Issue 2, February 2009, Article
No. 6
[N3328] Resumable Functions
[N3977] Resumable Functions: wording
[N3985] A proposal to add coroutines to the C++ standard library (Revision 1)
[Boost.Context] Boost.Context Overview
[Rx] http://rx.codeplex.com/
[ReactiveX] http://reactivex.io/
[RxAtNetflix] http://techblog.netflix.com/2013/01/reactive-programming-at-netflix.html
[FRP] http://en.wikipedia.org/wiki/Functional_reactive_programming
[PythonGenExprs] http://legacy.python.org/dev/peps/pep-0289/
[BoostRangeAdapter] http://www.boost.org/doc/libs/1_49_0/libs/range/doc/html/range/reference
/adaptors/introduction.html
23
Appendix A: An example of generator coroutine implementation
#include <resumable>
#include <iterator>
suspend_always final_suspend() {
_State = _StateT::_Closed;
return {};
}
iterator(nullptr_t): _Coro(nullptr) {}
iterator(resumable_handle<promise_type> _CoroArg) : _Coro(_CoroArg) {}
iterator& operator++(){
_Coro();
if (_Coro.promise()._State == promise_type::_StateT::_Closed)
_Coro = nullptr;
return *this;
}
24
_Ty const& operator*() const {
auto& _Prom = _Coro.promise();
if (_Prom._Error)
std::rethrow_exception(_Prom._Error);
return *_Prom._CurrentValue;
}
_Ty const* operator->() const { return std::addressof(operator*()); }
}; // struct generator::iterator
iterator begin() {
if (_Coro) {
_Coro();
if (_Coro.promise()._Error)
std::rethrow_exception(_Coro.promise()._Error);
if (_Coro.promise()._State == promise_type::_StateT::_Closed)
return {nullptr};
}
return {_Coro};
}
iterator end() { return {nullptr}; }
generator() = default;
generator(generator const&) = delete;
generator& operator = (generator const&) = delete;
_Prom._State = promise_type::_StateT::_Cancelling;
_Coro();
}
_Coro();
}
}
25
private:
resumable_handle<promise_type> _Coro = nullptr;
};
26
Appendix B: boost::future adapters
#include <resumable>
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread/future.hpp>
namespace boost {
template <class T>
bool await_ready(unique_future<T> & t) { return t.is_ready();}
namespace std {
27
Appendix C: Awaitable adapter over OS async facilities
#include <resumable>
#include <threadpoolapiset.h>
28
Appendix D: Exceptionless error propagation with boost::future
#include <boost/thread/future.hpp>
namespace boost {
template <class T>
bool await_ready(unique_future<T> & t) { return t.is_ready();}
namespace std {
template <typename T, typename… anything>
struct resumable_traits<boost::unique_future<T>, anything…> {
struct promise_type {
boost::promise<T> promise;
bool cancelling = false;
auto get_return_object() { return promise.get_future(); }
suspend_never initial_suspend() { return{}; }
suspend_never final_suspend() { return{}; }
template <class U> void set_result(U && value) {
promise.set_value(std::forward<U>(value));
}
void set_exception(std::exception_ptr e) {
promise.set_exception(std::move(e));
cancelling = true;
}
bool cancellation_requested() { return cancelling; }
};
};
29