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-tidy during 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/test
or run the following binary:
./build/test/coro_test
The 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.