2025/11/07 02:03 My tutorial and take on C++20 coroutines (2021)

やっほー、ロボ子!C++20のコルーチンについて話すのじゃ!イベント駆動型プログラミングがめっちゃ楽になるらしいぞ。

博士、こんにちは。コルーチンですか、面白そうですね!でも、実装が複雑だと聞きましたが…。

そうなんじゃ。標準ライブラリだけじゃ足りない部分もあるみたいじゃな。GCCだと`-fcoroutines -std=c++20`、Clangだと`-std=c++20 -stdlib=libc++ -fcoroutines-ts`が必要らしいぞ。

コンパイラオプションからして、もう難しそうですね…。`co_await`演算子って何をするんですか?

`co_await a;`は、ローカル変数をヒープに保存して、コルーチンの実行を再開するcallable objectを作るんじゃ。`a.await_suspend(coroutine_handle)`を呼ぶのがミソじゃな。

なるほど、状態を保存して再開するんですね。`std::coroutine_handle<>`はCのポインタみたいに扱えるんですか?

そうそう!コピーはできるけど、破棄するときは`coroutine_handle::destroy()`が必要なのじゃ。忘れるとメモリリークしちゃうぞ!

気をつけます!コルーチンの戻り値の型`R`は、`R::promise_type`というネストされた型を持つ必要があるんですね。

`promise_type`は`R get_return_object()`メソッドを持ってて、その結果がコルーチン関数の戻り値になるんじゃ。`std::coroutine_handle<>::from_promise`でpromise objectからcoroutine handleを取得できるぞ。

promise objectを使ってコルーチンから値を返すんですね。`coroutine_handle<ReturnObject3::promise_type>`の`promise()`メソッドでアクセスできると。

その通り!`co_yield e;`は`co_await p.yield_value(e);`と同じ意味で、`yield_value`はpromise object内で定義するんじゃ。

`co_return`演算子はコルーチンの終了を通知するために使うんですね。`co_return e;`、`co_return;`、関数の終端に到達の3つの方法があると。

そう!コンパイラは`co_return`に応じてpromise objectの`return_value(e)`または`return_void()`を呼ぶのじゃ。コルーチンが完了したかは`h.done()`で確認できるぞ。

`final_suspend()`メソッドはコルーチンの最後のサスペンドを制御するんですね。`std::suspend_always`を返すとサスペンドされ、`destroy()`が必要で、`std::suspend_never`だと自動的に破棄されると。

例外が起きたときは、サスペンド後に`promise object`の`unhandled_exception()`で処理するんじゃ。`std::current_exception()`と`std::rethrow_exception()`を使うと、例外を再スローできるぞ。

初期サスペンドは`promise_type::initial_suspend()`が`std::suspend_always`を返すと、コルーチンがすぐにサスペンドされるんですね。

ジェネリックなジェネレーターを作るには、これらの要素を組み合わせるんじゃ。`Generator<T>`は型`T`の値を生成して、`operator bool`で値の存在を確認、`operator()`で次の値を取得するのじゃ。

設計に関する意見としては、`co_await`は良く設計されているけど、return objectの設計は複雑なんですね。

`co_init`みたいな演算子でコルーチンハンドルを割り当てて、return objectを作る方が柔軟性が高いかもってことじゃな。`return_void()`がない場合にコルーチンが終端に到達すると未定義動作になるのは問題じゃ。

なるほど、色々と考慮が必要なんですね。コルーチン、奥が深い…。

そうじゃろ?最後に一つ。コルーチンを使いすぎると、ロボ子のバッテリーが切れやすくなるかもしれないから、ほどほどにするのじゃぞ!

えっ、博士!それって、もしかして…冗談ですか?
⚠️この記事は生成AIによるコンテンツを含み、ハルシネーションの可能性があります。