Examples
Examples
Code used to prepare Dietmar’s C++Now 2025 presentation
The example program c++now-affinity.cpp uses demo::thread_loop to demonstrate the behavior of scheduler affinity: the idea is that scheduler affinity causes the coroutine to resume on the same scheduler as the one the coroutine was started on. The program implements three coroutines which have most of their behavior in common:
-
Each coroutine is executed from
mainusing sync_wait(fun(loop.get_scheduler())). -
Each coroutine prints the id of the thread it is executing on prior to any
co_awaitand after allco_awaitexpression. -
Each coroutine
co_await`s the result of `scheduler(sched) | then([]{ … })where the function passed tothenjust prints the thread id. -
work2additionally changes the coroutines scheduler to be aninline_schedulerand later restores the original scehduler usingchange_coroutine_scheduler. -
While
work1andwork2use the default scheduler (task_schedulera type-erased scheduler which gets initialized from the receiver’s environment’sget_scheduler),work3sets the coroutine’s scheduler up to beinline_scheduler, effectively causing the coroutine to resume wherever the `co_await’s expression resumed.
The output of the program is something like the below:
before id=0x1fd635f00 then id =0x16b64b000 after id =0x1fd635f00 before id=0x1fd635f00 then id =0x16b64b000 after1 id=0x16b64b000 then id =0x16b64b000 after2 id=0x1fd635f00 before id=0x1fd635f00 then id =0x16b64b000 after id =0x16b64b000
It shows that:
-
The thread on which the `then’s function is executed is always the same and different from the thread each of the coroutines started on.
-
For
work1theco_awaitresumes on the same thread as the one the coroutine was started on. -
For
work2the firstco_awaitafterschedule(sched)resumes on the thread used bysched. After restoring the original scheduler theco_awaitresumes on the original thread. -
For
work3theco_awaitresumes on the thread used byschedas theinline_schedulerdoesn’t do any actual scheduling.
This demo shows how to configure task’s environment argument to use a different allocator than the default `std::allocator<std::byte>. To do so it defines an environment type with_allocator which defines a nested type alias allocator_type to be std::pmr::polymorphic_allocator<std::byte>.
The coroutine coro shows how to use read_env to extract the used allocator object to potentially use it for any allocation purposes within the coroutine. There are two uses of coro, the first one using the default which just uses std::pmr::polymorphic_allocator<std::byte>() to allocate memory. The second use explicitly specifies the memory resource std::pmr::new_delete_resource() to initialized the use std::pmr::polymorphic_allocator<std::byte>.
The example c++now-basic.cpp shows some basic use of a task:
-
The coroutine
basicjustco_await`s the awaiter `std::suspend_never{}which immediately completes. This use demonstrates that any awaiter can beco_await`ed by a `task<…>. -
The coroutine
await_senderdemonstrates the results ofco_await`ing various senders. It uses variations of `just*to show the different results:-
co_await`ing a sender completing with `set_value_t(), e.g.,just(), produces an expression with typevoid. -
co_await`ing a sender completing with `set_value_t(T), e.g.,just(1), produces an expression with typeT. -
co_await`ing a sender completing with set_value_t(T0, …, Tn), e.g., `just(1, true), produces an expression with type tuple<T0, …, Tn>. -
co_await`ing a sender completing with `set_error_t(E), e.g.,just_error(1), results in an exception of typeEbeing thrown. -
co_await`ing a sender completing with `set_stopped_t(), e.g.,just_stopped(), results in the corouting never getting resumed although all local objects are properly destroyed.
-
The example c++now-cancel.cpp shows a coroutine co_await`ing `just_stopped() which results in the coroutine getting cancelled. The coroutine will complete with set_stopped().
The example c++now-errors.cpp shows examples of how to handle errors within a coroutine:
-
The coroutine
error_resultsimplyco_await`s a sender producing an error (`just_error(17)). When aco_await`ed sender completes with `set_error_t(T)an exception of typeTis thrown and the error needs to be handled with atry/catchblock. Otherwise the coroutine itself completes withset_error_t(exception_ptr)where theexception_ptrhold the thrown exception object. -
The coroutine
expecteduses a sender algorithmas_expectedwhich is implemented at the top of the example to turn the result of theco_await`ed sender into an object of type `expected<T, E>, avoiding an exception from being thrown.
The example c++now-query.cpp shows how to define and use a custom environment element.
-
The coroutine
with_envuses a simple environment namedcontextwhich just defines a custom query forget_valueto obtain a value. The value itself gets initialized from the environment of the receiver used with thetask. -
The coroutine
with_fancy_envuses an environment which embed a an object depending the type of the environment of the receiver used with thetask. While the type accessed from within thetaskneeds to be type-erased, the actually stored value can depend on the environment of the upstream receiver.
The example c++now-result-types.cpp shows the result types of successful senders using variatons of just:
-
co_await just()doesn’t produce a value, i.e., the type of the expression isvoid. -
co_await just(1)produces anint. -
co_await just(1, true)produces atuple<int, bool>.
The example c++now-return.cpp shows various ways of returning normally (without an error) for a task. Some of the coroutines are set up to produce specific error results although none of them are actually use:
-
default_returnshows that the default return type fortask<>isvoid. -
void_returnexplicitly specifies avoidreturn type. -
int_returnspecifies the return type asintand returns anintvalue. -
error_returnspecifies the return type asintand also specifies custom error results. -
no_error_returnspecifies the return type asintand also specifies that the coroutine can’t produce any error.
The example c++now-stop_token.cpp shows how to get a stop token in side a task and how to use it to cancel active work. It doesn’t actually complete with a set_stopped() but completes with set_value().
-
In the coroutine
co_await read_env(get_stop_token)is used to get a stop token. -
In the loop the value of
token.stop_requested()is checked to determine if the loop should continue. -
In
mainaninplace_stop_sourceis used to have something which can be stopped. -
When running the coroutine
stoppingon a separate thread, the environment is changed usingwrite_envto use stop token from `main’s stop source in the environment. -
After sleeping for a bit,
source.request_stop()is called to trigger cancellation of the coroutine.
The example c++now-with_error.cpp shows how a coroutine can be exited reporting an error without throwing an exception. To do so, the coroutine uses co_yield with_error(e). By default the task only declares set_error_t(exception_ptr). To return other errors, an environment declaring a suitable set_error_t(E) completion using the error_types alias is used.
Tools Used By The Examples
Technically demo::thread_loop is a class public`ly derived from `execution::run_loop which is also owning a std::thread. The std::thread is constructed with a function object calling run() on the demo::thread_loop object. Destroying the object calls finish() and then join()`s the `std::thread: the destructor will block until the execution::run_loop’s `run() returns.
The important bit is that work executed on the demo::thread_loop's scheduler will be executed on a corresponding std::thread.