yohhoyの日記

技術的メモをしていきたい日記

std::generator<R>

次期C++2b(C++23)標準ライブラリに追加されるstd::generator<R>クラステンプレートについてメモ。

提案文書P2502R2よりコード引用:*1

// C++2b
#include <functional>
#include <generator>
#include <range>
#include <utility>

std::generator<int> fib() {
  auto a = 0, b = 1;
  while (true) {
    co_yield std::exchange(a, std::exchange(b, a + b));
  }
}

int answer_to_the_universe() {
  auto rng = fib() | std::views::drop(6) | std::views::take(3);
  return std::ranges::fold_left(std::move(rng), 0, std::plus{});
}

コルーチンfibから生成されるフィボナッチ数列 0, 1, 1, 2, 3, 5, 8, 13, 21... のうち、先頭6要素を破棄(drop)した後に3要素のみ取出す(take)遅延評価レンジrngを作成する(この時点ではコルーチン本体処理は開始されない)。rngに対して初期値 0 と算術加算(plus)による畳み込み(fold)操作を行い、結果値 42 を得る。

まとめ:

  • ユーザ定義コルーチンの記述で利用するR型の値を生成するジェネレータ(generator)型。Rangesライブラリとの高い親和性をもつ。
    • co_yield valによる単一値の生成。
    • co_yield elements_of(range)による複数値の逐次生成。
    • コルーチン本体処理は遅延評価される。*2
  • 新しい標準ヘッダ<generator>*3
    • クラステンプレートstd::generator<R, V, Allocator>V, Allocatorは省略可)
    • generatorviewコンセプトとinput_rangeコンセプトのモデル。範囲for文や各種レンジアダプタ(range adaptor)と組み合わせて利用可能。
  • 標準ヘッダ<ranges>の拡張
    • std::ranges::elements_of<R, Allocator>クラステンプレート(Rrangeコンセプトのモデル、Allocatorは省略可)
    • elements_ofはco_yield式に対するタグ型として機能する。
  • ジェネレータ利用側イテレータの提供型
  • generator<R>型(V省略時)
    • 大半のユースケースはテンプレートパラメータR型のみ指定でOK。
    • Reference型=R&&*5Value型=remove_cvref_t<R>
    • 例: generator<int>, generator<string>
  • generator<R, V>
    • 参照プロキシ型の利用や、右辺値を生成する特殊なユースケース向け。
    • Reference型=RValue型=V
    • 例: generator<string_view, string>
  • co_yield val
    • co_yield式の評価結果はvoid
    • co_yield式に指定する右辺値valはコピーされない(R=値型Tのシンプルなケースを想定)。
    • 内部的には指定値へのアドレス情報のみを保持しており(value_ = std::addressof(val))、ジェネレータ利用側イテレータで実体(*value_)をReference型へとキャストする。
    • co_yield式に指定する左辺値valはAwaitableオブジェクト内部にコピー格納され、そのアドレス情報が保持される。
    • 2022-08-10追記:テンプレートパラメータRへ指定する型とコピー/ムーブの関係性は id:yohhoy:20220810 を参照。
  • co_yield elements_of(range)
    • レンジrangeの全要素eに対して、同コルーチンから逐次的に生成(co_yield e)する。Flatten操作。
    • コルーチンが入れ子構造になる場合、動作効率改善のため内部的に対称転送(symmetric transfer)が行われる。*6
  • co_await式はサポートしない。
  • コルーチンから送出された例外はジェネレータ利用側にそのまま伝搬する(→id:yohhoy:20211112)。
    • co_yield elements_of(child)の子コルーチンchildの送出例外は、親コルーチン側でもcatch可能。ハンドリングしなければ親コルーチンが返すジェネレータ利用側へと伝搬する。
  • アロケータサポート
    • コルーチンフレーム*7のメモリ確保/解放に用いられる。
    • テンプレートパラメータAllocator型またはコルーチン引数にてstd::allocator_arg_t+アロケータ型を指定する。
    • アロケータの型消去(type erasure)をサポート。例: std::generator<int>型は任意のアロケータ型をコルーチンフレーム上に格納する。
    • ステートレス/ステートフル両アロケータをサポート。
  • 動作性能はC++コンパイラの最適化性能に強く依存する(→id:yohhoy:20180415)。

関連URL

*1:std::ranges::fold_left アルゴリズムP2322R6 にてC++2b標準ライブラリへ追加予定となっている。

*2:std::generator<R>::promise_type 型の初期サスペンドポイント(initial_suspend)は suspend_always 型を返す。

*3:C++20 <coroutine>ヘッダはコルーチン機構実装用の低レイヤ機能のみを提供する。

*4:Value型は std::generator それ自身からは参照されず、利用側で イテレータvalue_type 型にアクセスするユースケース向け。

*5:テンプレートパラメータ R に対して "reference collapsing" 規則が適用される。例: R=const T& ならば R&&=const T& となる。

*6:std::generator<R>::promise_type 型の yield_value メンバ関数が返すAwaiter型では、await_suspend メンバ関数が転送先コルーチンの std::coroutine_handler 値を返す。

*7:C++標準規格では "coroutine state" と呼称される。