• Class / Function / Type

      std::
    • Header file

      <>
    • Other / All

    最終更新日時(UTC):
    が更新

    履歴 編集

    class template
    <generator>

    std::generator

    namespace std {
      template<class Ref, class V = void, class Allocator = void>
      class generator : public ranges::view_interface<generator<Ref, V, Allocator>> {
        ...
      };
    
      namespace pmr {
        template<class R, class V = void>
        using generator = std::generator<R, V, polymorphic_allocator<>>;
      }
    }
    

    概要

    generatorクラステンプレートは、コルーチンの評価により生成される要素列のビュー(view)を表現する。 特殊化されたgeneratorviewおよびinput_rangeのモデルである。 generatorはムーブのみ可能なオブジェクトである。

    戻り値generatorのコルーチン(以下、ジェネレータコルーチン)ではco_yield式を用いて値を生成する。co_yield std::ranges::elements_of(rng)式を用いると、ジェネレータコルーチンから入れ子Range(rng)の各要素を逐次生成する。 ジェネレータコルーチンではco_await式を利用できない。

    ジェネレータコルーチンは遅延評価される。 ジェネレータコルーチンが返すgeneratorオブジェクトを利用するコード(以下、ジェネレータ利用側)において、先頭要素を指すイテレータを取得する(begin)、またはイテレータのインクリメント操作を行うまでジェネレータコルーチンは再開(resume)されない。 ジェネレータコルーチン本体処理においてco_yield式に到達すると、生成値を保持してから中断(suspend)しジェネレータ利用側へと制御を戻す。 ジェネレータ利用側ではイテレータの間接参照を行うことで、ジェネレータによる生成値を取得する。

    説明用メンバ

    generatorでは下記の説明用メンバ型を定義する。

    generatorおよびpromise_typeiteratorの動作説明のため、下記の説明用メンバを用いる。

    第1テンプレートパラメータRefの概要

    generatorクラステンプレートでは、テンプレートパラメータRef, Vの組み合わせによってco_yieldオペランド型(yielded)やイテレータ間接参照の結果型(reference)やイテレータの値型(value_type)を制御する。

    第1パラメータRef以外は省略可能となっており、ほとんどのユースケース(提案文書によれば98%)では、テンプレートパラメータRefのみの明示指定で十分とされる。 値型Tを生成するジェネレータにおいて、第1テンプレートパラメータRefに応じて関連する型がそれぞれ導出される。

    Ref co_yieldオペランド型 間接参照の結果型 イテレータの値型
    T
    T&&
    T&&const T& T&& T
    T& T& T& T
    const T& const T& const T& T

    ジェネレータそれ自身ではイテレータの値型を直接利用しない。ジェネレータ利用側でイテレータのvalue_type型にアクセスする特殊な追加処理を行う場合のみ、第2テンプレートパラメータVの明示指定が必要とされる。

    ジェネレータと値のコピー/ムーブ

    generatorクラステンプレートでは、その内部動作において最低限のコピー/ムーブ処理しか行わないよう設計されている。このため一定条件を満たせば、コピー不可でムーブのみ可能な型や、コピー/ムーブいずれも不可能な型の生成もサポートする。

    値型Tを生成するジェネレータにおいて、第1テンプレートパラメータRefとco_yield演算子オペランドに指定する式(左辺値/右辺値やconst修飾)に応じて、ジェネレータコルーチン最大1回のコピーが発生する。 Ref=T&とした場合、co_yieldに右辺値を指定するとコンパイルエラーになる。

    Ref\co_yield式 左辺値 const左辺値 右辺値
    T
    T&&
    コピー 1回 コピー 1回 0回
    T& コピー 1回 コピー 1回 (不適格)
    const T& 0回 0回 0回

    またジェネレータ利用側においては、イテレータ間接参照結果を受ける変数の宣言型Xとの関係に応じて(例:範囲for文の変数宣言型)、最大で1回のコピーまたはムーブが生じる。 Ref=T&またはRef=const T&とした場合、右辺値参照型T&&には束縛できないためコンパイルエラーとなる。

    RefX T T&& const T&
    T
    T&&
    ムーブ 1回 0回 0回
    T& コピー 1回 (不適格) 0回
    const T& コピー 1回 (不適格) 0回

    ムーブのみ可能な型(例:std::unique_ptr)では、上記表においてコピーが生じる組み合わせを避ければジェネレータより生成可能である。 同様にコピー/ムーブいずれも不可能な型では、コピーまたはムーブが生じる組み合わせを避ければ生成可能である。

    アロケータサポート

    generatorクラステンプレートの第3テンプレートパラメータAllocatorによって、コルーチン・ステートの動的メモリ確保に用いる静的アロケータ型を指定できる。省略時はデフォルトアロケータallocator<void>が利用される。

    またジェネレータコルーチン定義の引数リストにallocator_argタグ型に続いてアロケータオブジェクトを指定すると、ジェネレータの生成毎に異なるアロケータ利用を指定することもできる。

    C++コンパイラによっては、ジェネレータコルーチンに関する動的メモリ確保・解放処理は最適化によって省略され(coroutine elision)、より効率的なコードが生成されることも期待できる。

    ジェネレータのネスト

    別の子ジェネレータコルーチンが返すgeneratorもRangeとなっているため、ある親ジェネレータコルーチン内部でco_yield std::ranges::elements_of構文を用いると、複数ジェネレータコルーチンのネスト構造をとることができる。 子ジェネーレタコルーチンの遅延評価によって生成される値は、親ジェネレータコルーチンから生成されたかのように振る舞う。

    generatorクラス(厳密にはgenerator::promise_type)ではジェネレータコルーチンのネスト構造を検出し、対称コルーチンとしてコルーチン間の実行フロー転送制御を効率的に行う。

    適格要件

    • テンプレートパラメータAllocatorvoidではない場合、allocator_traits<Allocator>::pointerはポインタ型であること。
    • 説明用のメンバ型valueCV修飾されないオブジェクト型であること。
    • 説明用のメンバ型referenceは参照型、またはcopy_constructibleのモデルであるCV修飾されないオブジェクト型であること。
    • 説明用のメンバ型referenceが参照型の場合にはRRef == remove_reference_t<reference>&&、それ以外の場合にはRRef == referenceとしたとき、それぞれ下記のモデルであること。

    テンプレートパラメータAllocatorvoidではない場合、Cpp17Allocatorの要件を満たすこと。

    メンバ関数

    構築・破棄

    名前 説明 対応バージョン
    (constructor) コンストラクタ C++23
    (destructor) デストラクタ C++23
    operator=(generator other) noexcept; ムーブ代入演算子 C++23

    イテレータ

    名前 説明 対応バージョン
    begin Viewの先頭を指すイテレータを取得する C++23
    end Viewの番兵を取得する C++23

    メンバ型

    名前 説明 対応バージョン
    yielded co_yield演算子オペランド型(後述) C++23
    promise_type ジェネレータコルーチンのPromise型 C++23

    using yielded =
      conditional_t<is_reference_v<reference>, reference, const reference&>;
    

    例1: 単一値の生成

    #include <generator>
    #include <ranges>
    #include <iostream>
    
    // 偶数値列を無限生成するコルーチン
    std::generator<int> evens()
    {
      int n = 0;
      while (true) {
        co_yield n;
        n += 2;
      }
    }
    
    int main()
    {
      // ジェネレータにより生成されるRangeのうち
      // 先頭から5個までの要素値を列挙する
      for (int n : evens() | std::views::take(5)) {
        std::cout << n << std::endl;
      }
    }
    

    出力

    0
    2
    4
    6
    8
    

    例2: Range要素値の逐次生成

    #include <generator>
    #include <iostream>
    #include <list>
    #include <ranges>
    #include <vector>
    
    // Rangeの要素値を逐次生成するコルーチン
    std::generator<int> ints()
    {
      int arr[] = {1, 2, 3};
      co_yield std::ranges::elements_of(arr);
      std::vector<int> vec = {4, 5, 6};
      co_yield std::ranges::elements_of(vec);
      std::list<int> lst = {7, 8, 9};
      co_yield std::ranges::elements_of(lst);
    }
    
    int main()
    {
      for (int n : ints())) {
        std::cout << n << ' ';
      }
    }
    

    出力

    1 2 3 4 5 6 7 8 9 
    

    例3: ジェネレータのネスト

    #include <generator>
    #include <iostream>
    #include <ranges>
    #include <memory>
    
    // 二分木ノード
    struct node {
      int value;
      std::unique_ptr<node> left = nullptr;
      std::unique_ptr<node> right = nullptr;
    };
    
    // 二分木を走査: 左(left)→自ノード→右(right)
    std::generator<int> traverse(const node& e)
    {
      if (e.left) {
        co_yield std::ranges::elements_of(traverse(*e.left));
      }
      co_yield e.value;
      if (e.right) {
        co_yield std::ranges::elements_of(traverse(*e.right));
      }
    }
    
    int main()
    {
      // tree:
      //    2
      //   / ¥
      //  1   4
      //     / ¥
      //    3   5
      node tree = {
        2,
        std::make_unique<node>(1),
        std::make_unique<node>(
          4,
          std::make_unique<node>(3),
          std::make_unique<node>(5)
        ),
      };
    
      for (int n: traverse(tree)) {
        std::cout << n << std::endl;
      }
    }
    

    出力

    1
    2
    3
    4
    5
    

    例4: 第2テンプレートパラメータVの利用

    #include <iostream>
    #include <generator>
    #include <ranges>
    #include <string>
    #include <string_view>
    #include <vector>
    
    // Ref=string_view, V=string のジェネレータコルーチン
    auto fizzbuzz() ->
      std::generator<std::string_view, std::string>
    {
      for (size_t i = 1; ; ++i) {
        if (i % 15 == 0) {
          co_yield "FizzBuzz";
        } else if (i % 3 == 0) {
          co_yield "Fizz";
        } else if (i % 5 == 0) {
          co_yield "Buzz";
        } else {
          co_yield std::to_string(i);
        }
      }
    }
    
    int main()
    {
      // std::ranges::to<C>() は変換元Rangeの range_value_t を利用して戻り値型を決定する。
      // ここでは std::vector<std::string> 型を導出するために、ジェネレータコルーチンの
      // 第2テンプレートパラメータ V = std::string として明示指定する必要がある。
      auto vec = fizzbuzz() | std::views::take(15) | std::ranges::to<std::vector>();
    
      // もし fizzbuzz() 戻り値型が std::generator<std::string_view> であった場合、
      // 変数 vec は std::vector<std::string_view> 型となる。このときコルーチン内部実装の
      // std::to_string() の戻り値 std::string オブジェクトはco_yield式末尾で寿命が切れるため、
      // 各要素 vec[i] に格納される std::string_view はダングリング(dangling)状態になってしまう。
    
      for (const auto& e : vec) {
        std::cout << e << std::endl;
      }
    }
    

    出力

    1
    2
    Fizz
    4
    Buzz
    Fizz
    7
    8
    Fizz
    Buzz
    11
    Fizz
    13
    14
    FizzBuzz
    

    バージョン

    言語

    • C++23

    処理系

    関連項目

    参照