C++2c(C++26)標準ライブラリに追加される実行制御ライブラリ(execution control library)についてメモ。別名:std::execution, Senders/Receivers(S/R)
2025年5月現在、ベースライン提案文書P2300R10までを採択済み。
2025-07-20追記:2025年6月会合にて関連する一連の提案文書P2079R10,
P3149R11, P3284R4, (PDF)P3433R1, P3481R5, P3552R3, P3557R3, P3570R2, (PDF)P3682R0が採択された。
- 非同期処理フレームワークのコンセプト・基本操作 → id:yohhoy:20250609
- 非同期処理ビルディングブロックとしてのSenderアルゴリズム → id:yohhoy:20250612
- C++コルーチン(→id:yohhoy:20180415)とSenderの相互運用サポート
コルーチンAwaitable==Sender
C++コルーチンにおいてco_await
演算子オペランドに指定可能なAwaitableオブジェクトは、実行制御ライブラリのSenderとしてReceiverと接続(connect)できる。2025年5月現在はそのような標準Awaitable型は提案文書P3552にて検討段階にあり、他プログラミング言語における非同期処理の結果型(例:Task
, Promise
, Future
など)に相当する。
2025-07-20追記:P3552R3にてSender互換コルーチン戻り値型として利用する非同期タスクexecution::task<T>
クラスが追加された。同クラスはコルーチンAwaitableとして定義されるものの、直接的にSenderへアダプト(メンバ型sender_concept
定義)するため下記機構は利用されない。
Awaitableオブジェクトの取り扱いは接続操作execution::connect
CPOにて仕様規定されるため、Awaitable型を定義するコルーチンライブラリ実装側での対処は必要ない。(コルーチン側でSenderを利用したい場合は、コルーチンライブラリ実装側にも後述対応が必要。)
式co_await Awaitable
の戻り値型T
のとき、コルーチンAwaitableと接続するReceiverは下記の完了シグネチャを持つ必要がある。
- 値完了:
set_value_t(T)
- エラー完了:
set_error_t(std::exception_ptr)
- 停止完了:
set_stopped_t()
// C++2c #include <coroutines> #include <execution> namespace exec = std::execution; // Awaitableコルーチン戻り値型 template<typename T> struct Lazy { struct promise_type; //... // co_await演算子オーバーロードを定義 auto operator co_await() noexcept; // 式co_await Lazy<T>はT型の値を返す } // 自作Receiver型 struct DumpReceiver { using receiver_concept = exec::receiver_t; // 値完了 set_value_t(int) void set_value(int) noexcept; // エラー完了 set_error_t(exception_ptr) void set_error(std::exception_ptr) noexcept; // 停止完了 set_stopped_t() void set_stopped() noexcept; }; // Awaitableオブジェクトを返すコルーチン(関数) Lasy<int> async_work(); // Awaitableオブジェクトはsenderコンセプトを満たす exec::sender auto task = async_work(); // OK // Awaitable(Sender)とRecevierを接続 exec::receiver auto rcvr = DumpReceiver{}; auto op = exec::connect(task, rcvr); // OK exec::start(op);
Sender Awaitableコルーチン
C++コルーチンのPromise型が特定インタフェース要件を満たすとき、コルーチン内部においてSenderオブジェクトに対して式co_await sndr
でAwait可能となる。実行制御ライブラリと相互運用可能なコルーチンライブラリ実装者向けに、Promise型P
の実装ヘルパとして基底クラスexecution::with_awaitable_senders<P>
が提供される。
2025-07-20追記:execution::task<T>
を戻り値型とするコールチンはSender Awaitableコルーチンとなる。[P3552R3]
Sender Awaitableコルーチン内部でSenderオブジェクトをco_await
演算子に指定するとexecution::as_awaitable
CPOが呼び出され、Senderとコルーチン接続用のAwaitableオブジェクトに自動変換される。同Awaitableオブジェクトはコルーチンを中断(suspend)してSenderを開始(start)し、非同期操作が完了すると送信値をco_await
式の値としてコルーチンを再開(resume)する。非同期操作がエラー完了したときは例外送出が行われ、停止完了の既定動作としてプログラム異常終了(std::terminate()
)する。
co_await
演算子オペランドに指定可能なSenderの制約として、値完了シグネチャを1種類だけ持つことが要請される。複数の値完了シグネチャを持つSenderの場合は、Senderアダプタexecution::into_variant
等を適用する必要がある。
- 2025-07-20追記:Senderがクエリオブジェクト
execution::get_await_completion_adaptor
に対応していれば、co_await
演算子の適用時に単一の値完了シグネチャへと自動変換される。この仕組みにより、Receiver接続時およびコルーチン利用時それぞれで最適なインタフェースを提供できる。[P3570R2]
// C++2c #include <coroutines> #include <execution> namespace exec = std::execution; // Sender Awaitableコルーチン戻り値型 template<typename T> struct Lazy { struct promise_type : exec::with_awaitable_senders<promise_type> { void return_value(T); //... }; // コルーチンco_return戻り値を取得するメンバ関数 T get(); }; // Sender Awaitableコルーチン Lazy<int> coro(int n) { // n*3を計算するSenderチェイン構築 exec::sender auto sndr = exec::just(n) | exec::then([](int m){ return m * 3; }); // Senderを開始して値取得を待機 int val = co_await sndr; // OK co_return val * 7; } auto task = coro(2); int value = task.get(); // 42==2*3*7
Sender Awaitableコルーチン動作のカスタマイゼーションポイントとして下記が定義される。
- 式
co_await sndr
- 停止完了ハンドラ:コルーチンPromsie型
unhandled_stopped
のメンバ関数 - 停止完了の委譲:
execution::with_awaitable_senders<P>::set_continuation
メンバ関数によるコルーチンハンドラ設定
メモ:難解なC++20コルーチン仕様 × 複雑なC++2c実行制御ライブラリの相乗効果で気分は宇宙猫 (゚ω゚)
関連URL
- P2300R10 std::execution
- (PDF) P3552R2 Add a Coroutine Task Type
- cppreference: Execution control library
- cpprefjp: std::execution
- C++26 Executionライブラリ(基礎編) - yohhoyの日記
- C++26 Executionライブラリ(Senderアルゴリズム編) - yohhoyの日記
- https://zenn.dev/yohhoy/scraps/5c500ed9096792
- https://gist.github.com/yohhoy/cd567aaf1aeb6ca6ff555d528b348b58