async_generator
allows writing enumerators that may require asynchronous operation.
The pattern for using an async_generator is:
task<> something_async();
std::string get_string();
async_generator<std::string> enumerate_strings(bool returnLast)
{
// Yields a reference to a temporary value
co_yield "hello world";
std::string value = get_string();
// Yields a reference to a -copy- of the l-value value
co_yield value;
// Waits for another task to complete before yielding more items
co_await something_async();
// Yields a reference to the r-value
co_yield get_string();
if(!return_last)
{
co_return;
}
// Yields a reference to a temporary value
co_yield "last";
}
task<> do_something_with_enumeration()
{
// Note there is no "co_await" on enumerate_strings()
auto enumeration = enumerate_strings();
for(
// begin() needs to be co_await'ed, and returns an iterator
auto iterator = co_await enumeration.begin();
// end() should not be co_await'ed
iterator != enumeration.end();
// ++iterator needs to be co_await'ed
co_await ++iterator)
{
// The value of the iterator is accessed via *iterator,
// which returns a reference to the co_yield'ed value.
std::cout << *iterator;
}
}
The async_generator
type accepts a Result
type argument to return from the
iterator. This type may be a reference type. The semantics of Result being
an various reference types and yield values of various reference types are as follows:
-
Result
isT
,co_yield T&
:*iterator
is aT&
to a copy of the value referenced in theco_yield
statement -
Result
isT
,co_yield T
orco_yield T&&
:*iterator
is aT&
to the original value referenced in theco_yield
statement -
Result
isT&
,co_yield T&
:*iterator
is aT&
to the original value referenced in theco_yield
statement -
Result
isT&
,co_yield T
orco_yield T&&
:*iterator
is aT&
to the value referenced in theco_yield
statement
The implication here is that the following code is probably not the most performant:
async_generator<std::string> get_strings()
{
std::string result = "hello world";
// This will _copy_ result, because the co_yield
// is referencing an l-value
co_yield result;
}
Instead, say:
async_generator<std::string> get_strings()
{
std::string result = "hello world";
// *iterator returns a reference to the original result, without
// actually doing a move-construct operation.
co_yield std::move(result);
}
or:
async_generator<std::string> get_strings()
{
// *iterator returns an r-value reference to the temporary variable, without
// actually doing a move-construct operation.
co_yield "hello world";
}
async_generator<Result, Policies...>
and async_generator_promise<Result, Policies...>
accept
a base_promise_type<Promise>
policy to specify
the backing promise type, which defaults to task_promise<Result>
.
async_generator
can use any backing promise which supports these facilities:
-
The
get_return_object()
method call ofPromise
is awaitable (which is true of all ordinary promise objects). -
The value of
get_awaiter
(get_return_object())
is an awaiter. -
The awaiter's
await_ready()
andawait_suspend()
methods can be called multiple times, with eachawait_suspend()
replacing the current continuation of the underlyingPromise
. -
The awaiter's
await_resume()
method can be called zero or one times. It will be called when the enumeration is complete. -
The awaiter type possesses a method matching the signature:
decltype(auto) get_result_value( std::invocable iterator );
This method is used to translate an
iterator_type
into the correct type to return fromco_await iterator_type::operator++
andco_await async_generator<>::begin()
when the enumeration is returning a valid iterator. -
The Awaiter type possesses a method matching the signature:
decltype(auto) await_resume_value( std::invocable iterator );
This method is used to translate an
iterator_type
into the correct type for callers toco_await iterator_type::operator++
andco_await async_generator<>::begin()
when the enumeration has completed, either by an explicitco_return
statement, falling off the end of the coroutine body, or by an exception. In the exception case, theresume_result
method should throw the exception or return the expected error type. -
The
Promise
object has a methodcontinuation()
which returns the continuation from the most recent call to the awaiter'sawait_suspend()
invocation.
Phantom.Coroutines provides these core promise types that meet these requirements:
These attributes are embodied by the concept is_async_generator_base_promise<T>
.