-
Notifications
You must be signed in to change notification settings - Fork 233
/
Generator.h
626 lines (544 loc) · 21.1 KB
/
Generator.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
/*
* Copyright (c) 2023, Alibaba Group Holding Limited;
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ASYNC_SIMPLE_CORO_GENERATOR_H
#define ASYNC_SIMPLE_CORO_GENERATOR_H
#if defined(__clang__) && __clang_major__ == 15
// See: https://github.com/alibaba/async_simple/issues/372
#warning "Clang 15 is not supported for Generator due to some issues."
#endif
#if __has_include(<generator>)
#include <generator>
namespace async_simple::coro {
template <class Ref, class V = void, class Allocator = void>
using Generator = std::generator<Ref, V, AlloAllocator>;
} // namespace async_simple::coro
namespace async_simple::ranges {
template <class R, class Alloc = std::allocator<std::byte>>
using elements_of = std::ranges::elements_of<R, Alloc>;
} // namespace async_simple::ranges
#else
#include <cassert>
#include <concepts>
#include <cstddef>
#include <exception>
#include <ranges>
#include <type_traits>
#include <utility>
#include "async_simple/Common.h"
#include "async_simple/coro/PromiseAllocator.h"
#include "async_simple/experimental/coroutine.h"
namespace async_simple::ranges {
// For internal use only, for compatibility with lower version standard
// libraries
namespace internal {
// clang-format off
template <typename T>
concept range = requires(T& t) {
std::ranges::begin(t);
std::ranges::end(t);
};
template <class T>
using iterator_t = decltype(std::ranges::begin(std::declval<T&>()));
template <range R>
using sentinel_t = decltype(std::ranges::end(std::declval<R&>()));
template <range R>
using range_value_t = std::iter_value_t<iterator_t<R>>;
template <range R>
using range_reference_t = std::iter_reference_t<iterator_t<R>>;
template <class T>
concept input_range = range<T> && std::input_iterator<iterator_t<T>>;
} // namespace internal
// clang-format on
#ifdef _MSC_VER
#define EMPTY_BASES __declspec(empty_bases)
#ifdef __clang__
#define NO_UNIQUE_ADDRESS
#else
#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#endif
#else
#define EMPTY_BASES
#define NO_UNIQUE_ADDRESS [[no_unique_address]]
#endif
template <class R, class Alloc = std::allocator<std::byte>>
struct elements_of {
NO_UNIQUE_ADDRESS R range;
NO_UNIQUE_ADDRESS Alloc allocator{};
};
template <class R, class Alloc = std::allocator<std::byte>>
elements_of(R&&, Alloc = {}) -> elements_of<R&&, Alloc>;
} // namespace async_simple::ranges
namespace async_simple::coro::detail {
template <class yield>
struct gen_promise_base;
} // namespace async_simple::coro::detail
namespace async_simple::coro {
// clang-format off
/**
* Implementation comes from [P2502R2]
* (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2502r2.pdf)
*
* Generator: Synchronous Coroutine Generator for Ranges.
* Synchronous generators are an important use case for coroutines.
* The Generator class represents a sequence of synchronously produced
* values where the values are produced by a coroutine. Generator is a
* move-only view which models input_range and has move-only iterators.
* This is because the coroutine state is a unique resource (even if the
* coroutine handle is copyable).
*
* Values are produced by using the 'co_yield' keyword.
* Using the 'co_await' keyword is not allowed In the body of Generator.
* The end of the sequence is indicated by executing 'co_return;' either
* explicitly or by letting execution run off the end of the coroutine.
* For example:
*
* Generator<int> answer() {
* co_yield 42;
* co_return;
* }
*
* Generator has 3 template parameters: Generator<Ref, V = void, Allocator = void>
* From Ref and V, we derive types:
* using value = conditional_t<is_void_v<V>, remove_cvref_t<Ref>, V>;
* using reference = conditional_t<is_void_v<V>, Ref&&, Ref>;
* using yielded = conditional_t<is_reference_v<reference>, reference, const reference&>;
*
* • value is a cv-unqualified object type that specifies the value type of the
* generator’s range and iterators
* • reference specifies the reference type (not necessarily a core language
* reference type) of the generator’s range and iterators, and it is the return
* value of the opeartor* member function of the generator's iterators.
* • yielded (necessarily a reference type) is the type of the parameter to the
* primary overload of yield_value in the generator’s associated promise type.
*
* Generator<meow>
* Our expectation is that 98% of use cases will need to specify only one
* parameter. The resulting Generator:
* • has a value type of std::remove_cvref<meow>
* • has a reference type of meow, if it is reference type, or meow&& otherwise
* • has a yielded type of reference, because `std::is_reference_v<reference>`
* must be established at this time, but users don't need to care about this.
*
* // Following examples show difference between:
//
// If I co_yield a... reference value
// X / X&& | X& | const X&
// ------------+------------+-----------
// - Generator<X> (same as Generator<X&&>) X&& X
// - Generator<const X&> ref | ref | ref const X& const X
// - Generator<X&&> ref | 1 copy | 1 copy X&& X
// - Generator<X&> ill-formed | ref | ill-formed X& X
//
*
* Generator<int> (same as Generator<int&&>)
* Generator<int&&> xvalue_example() {
* [1] co_yield 1;
* [2] int x{2};
* [3] co_yield x; // well-formed: generated element is copy of lvalue
* [4] const int y{3};
* [5] co_yield y; // same as above
* [6] int z{4};
* [7] co_yield std::move(z); // pass by rvalue reference.
* }
*
* // Generator<int>::iterator operator* return type:
* // -> Generator<int>::reference -> int&&
*
* for (auto&& i : xvalue_example()) { // auto -> int&&, no copy, no move
* std::cout << i << std::endl; // The variable z can still be used
* // after the sequence number 7
* }
*
* for (auto i : xvalue_example()) { // auto -> int
* std::cout << i << std::endl; // The variable z cannot be used
* // after the sequence number 7,
* // because it is moved. But the
* // variable x can still be used
* // after the sequence number 3,
* // because the value returned by
* // the iterator is the copied.
* }
*
* // Pass by reference, no copy.
* Generator<const int&> const_lvalue_example() {
* co_yield 1; // OK
* const int x{2};
* co_yield x; // OK
* co_yield std::move(x); // OK: same as above
* int y{3};
* co_yield y; // OK
* }
*
* Geneartor<int&> lvalue_example() {
* co_yield 1; // ill-formed: prvalue -> non-const lvalue
* int x{2};
* co_yield x; // OK
* co_yield std::move(x); // ill-formed: xvalue -> non-const lvalue
* const int y{3};
* co_yield y; // ill-formed: const lvalue -> non-const lvalue
* }
*
* Generator<meow, woof>
* For the rare user who needs generator to step outside the box and use a
* proxy reference type, or who needs to generate a range whose iterators
* yield prvalues for whatever reason, we have two-argument generator.
* If woof is void, this is generator<meow>. Otherwise, the resulting generator:
*
* • has a value type of woof
* • has a reference type of meow
* // TODO: ...
*
* For example:
* // value_type = std::string_view
* // reference = std::string_view
* // Generator::iterator operator* return type: std::string_view
* // This can be expensive for types that are expensive to copy,
* // but can provide a small performance win for types that are cheap to copy
* // (like built-in integer types).
* Generator<std::string_view, std::string_view> string_views() {
* co_yield "foo";
* co_yield "bar";
* }
*
* // value_type = std::string
* // reference = std::string_view
* Generator<std::string_view, std::string> strings() {
* co_yield "start";
* std::string s;
* for (auto sv : string_views()) {
* s = sv;
* s.push_back('!');
* co_yield s;
* }
* co_yield "end";
* }
*
* // conversion to a vector of strings
* // If the value_type was string_view, it would convert to a vector of string_view,
* // which would lead to undefined behavior operating on elements of v that were
* // invalidated while iterating through the generator.
* auto v = std::ranges::to<vector>(strings());
*
* Allocator support: See async_simple/coro/PromiseAllocator.h
*
* Recursive generator
* A "recursive generator" is a coroutine that supports the ability to directly co_yield
* a generator of the same type as a way of emitting the elements of that generator as
* elements of the current generator.
*
* Example: A generator can co_yield other generators of the same type
*
* Generator<const std::string&> delete_rows(std::string table, std::vector<int> ids) {
* for (int id : ids) {
* co_yield std::format("DELETE FROM {0} WHERE id = {1};", table, id);
* }
* }
*
* Generator<const std::string&> all_queries() {
* co_yield std::ranges::elements_of(delete_rows("user", {4, 7, 9 10}));
* co_yield std::ranges::elements_of(delete_rows("order", {11, 19}));
* }
*
* Example: A generator can also be used recursively
*
* Generator<int, int> visit(TreeNode& tree) {
* if (tree.left)
* co_yield ranges::elements_of{visit(*tree.left)};
* co_yield tree.value;
* if (tree.right)
* co_yield ranges::elements_of{visit(*tree.right)};
* }
*
* In addition to being more concise, the ability to directly yield a nested generator
* has some performance benefits compared to iterating over the contents of the nested
* generator and manually yielding each of its elements.
*
* Yielding a nested generator allows the consumer of the top-level coroutine to directly
* resume the current leaf generator when incrementing the iterator, whereas a solution
* that has each generator manually iterating over elements of the child generator requires
* O(depth) coroutine resumptions/suspensions per element of the sequence.
*
* Example: Non-recursive form incurs O(depth) resumptions/suspensions per element
* and is more cumbersome to write:
*
* Generator<int, int> slow_visit(TreeNode& tree) {
* if (tree.left) {
* for (int x : slow_visit(*tree.left))
* co_yield x;
* }
* co_yield tree.value;
* if (tree.right) {
* for (int x : slow_visit(*tree.right))
* co_yield x;
* }
* }
*
*/
// clang-format on
template <class Ref, class V = void, class Allocator = void>
class Generator {
private:
using value =
std::conditional_t<std::is_void_v<V>, std::remove_cvref_t<Ref>, V>;
using reference = std::conditional_t<std::is_void_v<V>, Ref&&, Ref>;
class iterator;
// clang-format off
static_assert(
std::same_as<std::remove_cvref_t<value>, value> &&
std::is_object_v<value>,
"generator's value type must be a cv-unqualified object type");
static_assert(std::is_reference_v<reference> ||
(std::is_object_v<reference> &&
std::same_as<std::remove_cv_t<reference>, reference> &&
std::copy_constructible<reference>),
"generator's second argument must be a reference type or a "
"cv-unqualified "
"copy-constructible object type");
// clang-format on
public:
class promise_type;
using Handle = std::coroutine_handle<promise_type>;
using yielded = std::conditional_t<std::is_reference_v<reference>,
reference, const reference&>;
Generator(Generator&& other) noexcept
: _coro(std::exchange(other._coro, nullptr)) {}
Generator& operator=(Generator&& other) noexcept;
~Generator();
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
[[nodiscard]] iterator begin();
[[nodiscard]] std::default_sentinel_t end() const noexcept { return {}; }
private:
explicit Generator(Handle coro) noexcept;
private:
Handle _coro = nullptr;
template <class Yield>
friend struct detail::gen_promise_base;
};
namespace detail {
template <class yielded>
struct gen_promise_base {
protected:
struct NestInfo {
std::exception_ptr except_;
std::coroutine_handle<gen_promise_base> parent_;
std::coroutine_handle<gen_promise_base> root_;
};
template <class R2, class V2, class Alloc2>
struct NestedAwaiter {
NestInfo nested_;
Generator<R2, V2, Alloc2> gen_;
explicit NestedAwaiter(Generator<R2, V2, Alloc2>&& gen) noexcept
: gen_(std::move(gen)) {}
bool await_ready() noexcept { return !gen_._coro; }
template <class Promise>
std::coroutine_handle<> await_suspend(
std::coroutine_handle<Promise> handle) noexcept {
auto target = std::coroutine_handle<gen_promise_base>::from_address(
gen_._coro.address());
nested_.parent_ =
std::coroutine_handle<gen_promise_base>::from_address(
handle.address());
auto& current = handle.promise();
if (current.info_) {
nested_.root_ = current.info_->root_;
} else {
nested_.root_ = nested_.parent_;
}
nested_.root_.promise().top_ = target;
target.promise().info_ = std::addressof(nested_);
return target;
}
void await_resume() {
if (nested_.except_) {
std::rethrow_exception(std::move(nested_.except_));
}
}
};
struct ElementAwaiter {
std::remove_cvref_t<yielded> value_;
constexpr bool await_ready() const noexcept { return false; }
template <class Promise>
constexpr void await_suspend(
std::coroutine_handle<Promise> handle) noexcept {
auto& current = handle.promise();
current.value_ = std::addressof(value_);
}
constexpr void await_resume() const noexcept {}
};
struct FinalAwaiter {
bool await_ready() const noexcept { return false; }
template <class Promise>
std::coroutine_handle<> await_suspend(
std::coroutine_handle<Promise> handle) noexcept {
auto& current = handle.promise();
if (!current.info_) {
return std::noop_coroutine();
}
auto previous = current.info_->parent_;
current.info_->root_.promise().top_ = previous;
current.info_ = nullptr;
return previous;
}
void await_resume() noexcept {}
};
template <class Ref, class V, class Alloc>
friend class async_simple::coro::Generator;
std::add_pointer_t<yielded> value_ = nullptr;
std::coroutine_handle<gen_promise_base> top_ =
std::coroutine_handle<gen_promise_base>::from_promise(*this);
NestInfo* info_ = nullptr;
};
} // namespace detail
template <class Ref, class V, class Allocator>
class Generator<Ref, V, Allocator>::promise_type
: public detail::gen_promise_base<yielded>,
public detail::PromiseAllocator<Allocator> {
public:
using Base = detail::gen_promise_base<yielded>;
std::suspend_always initial_suspend() noexcept { return {}; }
// When info is `nullptr`, equivalent to std::suspend_always
auto final_suspend() noexcept { return typename Base::FinalAwaiter{}; }
std::suspend_always yield_value(yielded val) noexcept {
this->value_ = std::addressof(val);
return {};
}
// clang-format off
auto yield_value(const std::remove_reference_t<yielded>& lval) requires
std::is_rvalue_reference_v<yielded> && std::constructible_from<
std::remove_cvref_t<yielded>, const std::remove_reference_t<yielded>& >
{ return typename Base::ElementAwaiter{lval}; }
// clang-format on
// clang-format off
template <class R2, class V2, class Alloc2, class Unused>
requires std::same_as<typename Generator<R2, V2, Alloc2>::yielded, yielded>
auto yield_value(
ranges::elements_of<Generator<R2, V2, Alloc2>&&, Unused> g) noexcept {
// clang-format on
return typename Base::template NestedAwaiter<R2, V2, Alloc2>{
std::move(g.range)};
}
// clang-format off
template <class R, class Alloc>
requires std::convertible_to<ranges::internal::range_reference_t<R>, yielded>
auto yield_value(ranges::elements_of<R, Alloc> r) noexcept {
// clang-format on
auto nested = [](std::allocator_arg_t, Alloc,
ranges::internal::iterator_t<R> i,
ranges::internal::sentinel_t<R> s)
-> Generator<yielded, ranges::internal::range_value_t<R>, Alloc> {
for (; i != s; ++i) {
co_yield static_cast<yielded>(*i);
}
};
return yield_value(ranges::elements_of{
nested(std::allocator_arg, r.allocator, std::ranges::begin(r.range),
std::ranges::end(r.range))});
}
void return_void() noexcept {}
void await_transform() = delete;
void unhandled_exception() {
if (this->info_) {
this->info_->except_ = std::current_exception();
} else {
throw;
}
}
Generator get_return_object() noexcept {
return Generator(Generator::Handle::from_promise(*this));
}
};
template <class Ref, class V, class Allocator>
class Generator<Ref, V, Allocator>::iterator {
public:
using value_type = value;
using difference_type = std::ptrdiff_t;
explicit iterator(Handle coro) noexcept : _coro(coro) {}
~iterator() {
if (_coro) {
if (!_coro.done()) {
// TODO: error log
}
_coro.destroy();
_coro = nullptr;
}
}
iterator(iterator&& rhs) noexcept
: _coro(std::exchange(rhs._coro, nullptr)) {}
iterator& operator=(iterator&& rhs) {
logicAssert(!_coro, "Should not own a coroutine handle");
_coro = std::exchange(rhs._coro, nullptr);
}
explicit operator bool() const noexcept { return _coro && !_coro.done(); }
[[nodiscard]] bool operator==(std::default_sentinel_t) const {
return !_coro || _coro.done();
}
reference operator*() const {
logicAssert(
this->operator bool(),
"Should have a coroutine handle or the coroutine has not ended");
assert(_coro.promise().top_.promise().value_ &&
"value pointer is nullptr");
return static_cast<yielded>(*_coro.promise().top_.promise().value_);
}
iterator& operator++() {
logicAssert(this->operator bool(),
"Can't increment generator end iterator");
_coro.promise().top_.resume();
return *this;
}
void operator++(int) { ++(*this); }
private:
Handle _coro = nullptr;
};
template <class Ref, class V, class Allocator>
Generator<Ref, V, Allocator>::Generator(Handle coro) noexcept : _coro(coro) {}
template <class Ref, class V, class Allocator>
Generator<Ref, V, Allocator>& Generator<Ref, V, Allocator>::operator=(
Generator&& other) noexcept {
if (_coro) {
if (!_coro.done()) {
// TODO: [Warning] the coroutine is not done!
}
_coro.destroy();
}
_coro = std::exchange(other._coro, nullptr);
return *this;
}
template <class Ref, class V, class Allocator>
Generator<Ref, V, Allocator>::~Generator() {
if (_coro) {
if (!_coro.done()) {
// TODO: log
}
_coro.destroy();
_coro = nullptr;
}
}
template <class Ref, class V, class Allocator>
typename Generator<Ref, V, Allocator>::iterator
Generator<Ref, V, Allocator>::begin() {
logicAssert(!!_coro, "Can't call begin on moved-from generator");
_coro.resume();
return iterator(std::exchange(_coro, nullptr));
}
} // namespace async_simple::coro
template <class Ref, class V, class Allocator>
inline constexpr bool
std::ranges::enable_view<async_simple::coro::Generator<Ref, V, Allocator>> =
true;
#endif // __has_include(<generator>)
#endif // ASYNC_SIMPLE_CORO_GENERATOR_H