This project provides a set of coroutine-based asynchronous utilities and generators for C++20. It includes synchronous and asynchronous generators, tasks, and various utilities to facilitate coroutine-based programming.
- Synchronous Generators: Create coroutine-based generators that produce values synchronously.
- Asynchronous Generators: Create coroutine-based generators that produce values asynchronously.
- Tasks: Manage coroutine-based tasks that produce values of a specified type.
- C++20 compatible compiler
- CMake 3.23 or higher
cmake -B build
cmake --build build
sudo cmake --install build| Option | Description | Default | 
|---|---|---|
| BUILD_TESTS | Build tests | ON | 
| BUILD_EXAMPLES | Build examples | ON | 
| BUILD_DOCS | Build documentation | ON | 
| BUILD_INTERNAL_DOCS | Build internal documentation | OFF | 
| ENABLE_MAINTAINER_MODE | Maintainer mode (enable more compiler warnings, treat warnings as errors) | OFF | 
| USE_CLANG_TIDY | Use clang-tidyduring build | OFF | 
The BUILD_DOCS (public API documentation) and BUILD_INTERNAL_DOCS (public and private API documentation) require Doxygen
and, optionally, dot (a part of Graphviz).
The USE_CLANG_TIDY option requires clang-tidy.
| Build Type | Description | 
|---|---|
| Debug | Build with debugging information and no optimization. | 
| Release | Build with optimization for maximum performance and no debugging information. | 
| RelWithDebInfo | Build with optimization and include debugging information. | 
| MinSizeRel | Build with optimization for minimum size. | 
| ASAN | Build with AddressSanitizer enabled for detecting memory errors. | 
| LSAN | Build with LeakSanitizer enabled for detecting memory leaks. | 
| UBSAN | Build with UndefinedBehaviorSanitizer enabled for detecting undefined behavior. | 
| TSAN | Build with ThreadSanitizer enabled for detecting data races. | 
| Coverage | Build with code coverage analysis enabled. | 
ASAN, LSAN, UBSAN, TSAN, and Coverage builds are only supported with GCC or clang.
Coverage build requires gcov (GCC) or llvm-gcov (clang) and gcovr.
To run the tests, use the following command:
ctest -T test --test-dir build/testor run the following binary:
./build/test/coro_testThe test binary uses Google Test library. Its behavior can be controlled via environment variables and/or command line flags.
Run coro_test --help for the list of available options.
The documentation is available at https://sjinks.github.io/coro-cpp/.
An eager coroutine (or hot-start coroutine) starts execution immediately upon creation and keeps running until the first suspending co_await.
#include <wwa/coro/eager_task.h>
wwa::coro::eager_task my_task()
{
    co_await some_other_coroutine();
}Eager coroutines can co_await other coroutines but they cannot be co_await'ed.
Tasks are lightweight coroutines that start executing when they are awaited; they can optionally return values (the type of the return value
is determined by the template parameter T). Use tasks to create your coroutines, and use co_await or co_yield within tasks
to perform asynchronous operations.
#include <wwa/coro/task.h>
wwa::coro::task<int> task1()
{
    co_return 123;
}
wwa::coro::task<int> task2()
{
    co_return 456;
}
wwa::coro::task<int> sum()
{
    const auto a = co_await task1();
    const auto b = co_await task2();
    co_return a + b;
}
wwa::coro::task<> print()
{
    std::cout << "The result is " << co_await sum() << "\n";
}It is possible to turn any task (or any awaitable) into a fire-and-forget eager coroutine. For the example above,
#include <wwa/coro/eager_task.h>
#include <wwa/coro/task.h>
wwa::coro::run_awaitable(print);See examples/task.cpp.
Generators are special coroutines that produce sequences of values of a specified type (T). These values are produced lazily and synchronously.
The coroutine body is able to yield values of type T using the co_yield keyword.
However, the coroutine body is not able to use the co_await keyword; values must be produced synchronously.
Generators can be used with range-based for loops and ranges.
#include <wwa/coro/generator.h>
wwa::coro::generator<int> fibonacci(int n)
{
    int a = 0;
    int b = 1;
    if (n > 0) {
        co_yield a;
    }
    if (n > 1) {
        co_yield b;
    }
    for (int i = 2; i < n; ++i) {
        auto s = a + b;
        co_yield s;
        a = b;
        b = s;
    }
}
// ...
std::cout << "The first 10 Fibonacci numbers are: ";
for (auto n : fibonacci(10)) {
    std::cout << n << ' ';
}Generators are special coroutines that produce sequences of values of a specified type (T). These values are produced lazily and asynchronously.
Unlike the synchronous counterpart, the coroutine body is able to use both co_await and co_yield expressions.
// Simulates an asynchronous task
wwa::coro::task<int> get_next_value(int n)
{
    co_return n + 1;
}
wwa::coro::async_generator<int> async_first_n(int n)
{
    int v = 0;
    while (v < n) {
        co_yield v;
        v = co_await get_next_value(v);
    }
}
// ...
auto gen = async_first_n(5);
auto it  = co_await gen.begin();  // IMPORTANT! co_await is required
auto end = gen.end();
while (it != end) {
    std::cout << *it << "\n";
    co_await ++it;                // IMPORTANT! co_await is required
}See examples/async_generator.cpp.
Unfortunately, it is impossible to use asynchronous iterators directly in range-based for loops.