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)を表現する。
特殊化されたgenerator
はview
およびinput_range
のモデルである。
generator
はムーブのみ可能なオブジェクトである。
戻り値型generator
のコルーチン(以下、ジェネレータコルーチン)ではco_yield
式を用いて値を生成する。co_yield std::ranges::elements_of(rng)
式を用いると、ジェネレータコルーチンから入れ子Range(rng
)の各要素を逐次生成する。
ジェネレータコルーチンではco_await
式を利用できない。
ジェネレータコルーチンは遅延評価される。
ジェネレータコルーチンが返すgenerator
オブジェクトを利用するコード(以下、ジェネレータ利用側)において、先頭要素を指すイテレータを取得する(begin
)、またはイテレータのインクリメント操作を行うまでジェネレータコルーチンは再開(resume)されない。
ジェネレータコルーチン本体処理においてco_yield
式に到達すると、生成値を保持してから中断(suspend)しジェネレータ利用側へと制御を戻す。
ジェネレータ利用側ではイテレータの間接参照を行うことで、ジェネレータによる生成値を取得する。
説明用メンバ
generator
では下記の説明用メンバ型を定義する。
value
:conditional_t<is_void_v<V>, remove_cvref_t<Ref>, V>
reference
:conditional_t<is_void_v<V>, Ref&&, Ref>
iterator
: ジェネレータが返すイテレータ型。
generator
およびpromise_type
とiterator
の動作説明のため、下記の説明用メンバを用いる。
coroutine_
:coroutine_handle<promise_type>
型のコルーチンハンドルactive_
:unique_ptr<stack<coroutine_handle<>>>
型のアクティブスタック
第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&&
には束縛できないためコンパイルエラーとなる。
Ref \X |
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
)ではジェネレータコルーチンのネスト構造を検出し、対称コルーチンとしてコルーチン間の実行フロー転送制御を効率的に行う。
適格要件
- テンプレートパラメータ
Allocator
がvoid
ではない場合、allocator_traits<Allocator>::pointer
はポインタ型であること。 - 説明用のメンバ型
value
はCV修飾されないオブジェクト型であること。 - 説明用のメンバ型
reference
は参照型、またはcopy_constructible
のモデルであるCV修飾されないオブジェクト型であること。 - 説明用のメンバ型
reference
が参照型の場合にはRRef
==remove_reference_t<reference>&&
、それ以外の場合にはRRef
==reference
としたとき、それぞれ下記のモデルであること。common_reference_with<reference&&, value&>
common_reference_with<reference&&, RRef&&>
common_reference_with<RRef&&, const value&>
テンプレートパラメータAllocator
がvoid
ではない場合、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
処理系
- Clang:
- GCC:
- ICC:
- Visual C++:
関連項目
参照
- P2502R2
std::generator
: Synchronous Coroutine Generator for Ranges - P2787R1 pmr::generator - Promise Types are not Values
- P0981R0 Halo: coroutine Heap Allocation eLision Optimization: the joint response
- ジェネレータコルーチン実装において、動的メモリ確保が省略最適化される条件の議論。
std::generator
の設計に反映されている。
- ジェネレータコルーチン実装において、動的メモリ確保が省略最適化される条件の議論。