The library originated as a support code for the "Introduction to C++20" workshop. The workshop covers the design choices and implementation steps needed to implement most of the features provided here.
On the way it turned out that cppcoro is not maintained anymore and there are no good libraries of similar
quality (at least I was not able to find them). As a result this library was refactored and cleaned up
in the hope that others will find it useful and that the C++ community can provide feedback on it and ways
to improve it.
Still, one of the main goals of the library is to provide clean, short, simple but powerful interfaces to help C++ engineers learn how C++ coroutines work.
The design of this library is heavily influenced by:
cppcorolibrary by Lewis Baker, https://github.com/lewissbaker/cppcoro- "Asymmetric Transfer" blog by Lewis Baker, https://lewissbaker.github.io
- "Understanding C++ coroutines by example" by Pavel Novikov on C++ London, https://www.youtube.com/watch?v=7sKUAyWXNHA
- Compatible with the C++20 Standard rather then Coroutines TS specification
- CMake usage allows better adoptability
- Usage of C++20 synchronization features increase portability
- Easier, stronger, simpler interfaces and implementation thanks to
- RAII usage for managing coroutine lifetime
- usage of C++20 synchronization features
- usage of C++20 concepts
- usage of other C++20 features (i.e. spaceship operator)
- using
[[nodiscard]]attributes in much more places synchronized_task<Sync, T>class template- task coroutine return type for handling non-standard (coroutine-like) synchronization
- takes a synchronization primitive as the first template parameter
- used in
sync_awaitandwhen_all
storage<T>class template- responsible for storage of the result or the current exception
- implementation uses
std::variantunder the hood - interface similar to
std::promise/std::futurepair - could be replaced with
std::expectedproposed in P0323 in the future - used in
task,synchronized_task, andasync
nonvoid_storage<T>class template- returns
void_typefor a task ofvoid - needed for
when_all
- returns
task_promise_storage<T>class template- separates coroutine promise logic from its storage
- used in
taskandsynchronized_task - NOTE: It is an undefined behavior to have
return_value()andreturn_resultin a coroutine promise type (even if mutually exclusive thanks to constraining with C++20 concepts) :-(
- Not default-constructible
- Internal storage is not mutable for task lvalues (
constreference returned to the user)
// task<int>
static_assert(awaitable_of<task<int>, int&&>);
static_assert(awaitable_of<task<int>&, const int&>);
static_assert(awaitable_of<const task<int>&, const int&>);
static_assert(awaitable_of<task<int>&&, int&&>);
// task<int&>
static_assert(awaitable_of<task<int&>, int&>);
static_assert(awaitable_of<task<int&>&, int&>);
static_assert(awaitable_of<const task<int&>&, int&>);
static_assert(awaitable_of<task<int&>&&, int&>);
// task<const int&>
static_assert(awaitable_of<task<const int&>, const int&>);
static_assert(awaitable_of<task<const int&>&, const int&>);
static_assert(awaitable_of<const task<const int&>&, const int&>);
static_assert(awaitable_of<task<const int&>&&, const int&>);
// task<void>
static_assert(awaitable_of<task<void>, void>);
static_assert(awaitable_of<task<void>&, void>);
static_assert(awaitable_of<const task<void>&, void>);
static_assert(awaitable_of<task<void>&&, void>);- Uses
std::binary_semaphorefor synchronization - Much cleaner and shorter design
- Returns and instance of
void_typein a tuple of results in case ofawaitable_of<void> - Much cleaner and shorter design
- Not default-constructible
- Produced values are not mutable (
const_iteratorreturned to the user) - As this is lazy synchronous generator a promise type does not waste space for storing
std::exception_ptrbut instead rethrows the exception right away- also no branches are taken in
begin()andoperator++to check if an exception should be re-thrown
- also no branches are taken in
- Returns
std::default_sentinel_tfromend()which immediately makes it usable withstd::counted_iteratorand possibly other facilities
static_assert(!awaitable<generator<int>>);
static_assert(std::ranges::input_range<generator<int>>);mp_coro::generator<std::uint64_t> fibonacci()
{
std::uint64_t a = 0, b = 1;
while (true) {
co_yield b;
a = std::exchange(b, a + b);
}
}
void f()
{
auto gen = fibonacci();
for(auto i : std::views::counted(gen.begin(), 10))
std::cout << i << ' ';
std::cout << '\n';
}A concept that indicates a type contains the await_ready(), await_suspend() and await_resume()
member functions required to implement the protocol for suspending/resuming an awaiting coroutine.
A concept that ensures that type T is an awaiter and that await_resume() returns Value type.
A concept that indicates that a type can be co_awaited in a coroutine context that has no
await_transform() overloads.
Any type that implements the awaiter<T> concept also implements the awaitable<T> concept.
A concept that ensures that type T is an awaitable and that await_resume() returns Value type.
For example, the type task<T> implements the concept awaitable_of<T&&> whereas the type
task<T>& implements the concept awaitable_of<const T&>.
A concept that ensures that the task result type is either std::move_constructible or a
reference or a void type.
A std::unique_ptr with a custom deleter.
Awaitable that allows to asynchronously co_await on any invocable. More efficient than std::async
as it never allocates memory for shared std::promise/std::future storage.
NOTE: async should be co_awaited only once and that is why it works only for rvalues.
// async<int(*)()>
static_assert(awaitable_of<async<int(*)()>, int&&>);
static_assert(awaitable_of<async<int(*)()>&&, int&&>);
static_assert(!awaitable<async<int(*)()>&>);
static_assert(!awaitable<const async<int(*)()>&>);
// async<int&(*)()>
static_assert(awaitable_of<async<int&(*)()>, int&>);
static_assert(awaitable_of<async<int&(*)()>&&, int&>);
static_assert(!awaitable<async<int&(*)()>&>);
static_assert(!awaitable<const async<int&(*)()>&>);
// async<const int&(*)()>
static_assert(awaitable_of<async<const int&(*)()>, const int&>);
static_assert(awaitable_of<async<const int&(*)()>&&, const int&>);
static_assert(!awaitable<async<const int&(*)()>&>);
static_assert(!awaitable<const async<const int&(*)()>&>);
// async<void(*)()>
static_assert(awaitable_of<async<void(*)()>, void>);
static_assert(awaitable_of<async<void(*)()>&&, void>);
static_assert(!awaitable<async<void(*)()>&>);
static_assert(!awaitable<const async<int(*)()>&>);A macro used across the library to facilitate debugging and learning of coroutines workflow.
Tracing level can be selected with MP_CORO_TRACE_LEVEL preprocessor define and CMake cache
variable.