-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathGenerator.hpp
executable file
·79 lines (67 loc) · 2.68 KB
/
Generator.hpp
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
// Generator.hpp : https://en.cppreference.com/w/cpp/language/coroutines
#if defined(__clang__) && (__clang_major__ < 14) // workaround
#include <experimental/coroutine>
namespace std {
using namespace experimental;
}
#else
#include <coroutine>
#endif
#include <exception>
template<typename T>
struct Generator {
// The class name 'Generator' is our choice and it is not required for coroutine magic.
// Compiler recognizes coroutine by the presence of 'co_yield' keyword.
// You can use name 'MyGenerator' (or any other name) instead as long as you include
// nested struct promise_type with 'MyGenerator get_return_object()' method.
// Note: You need to adjust class constructor/destructor names too when choosing to
// rename class.
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
struct promise_type { // required
T value_;
std::exception_ptr exception_;
Generator get_return_object() {
return Generator(handle_type::from_promise(*this));
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { exception_ = std::current_exception(); } // saving
// exception
template<std::convertible_to<T> From> // C++20 concept
std::suspend_always yield_value(From &&from) {
value_ = std::forward<From>(from); // caching the result in promise
return {};
}
void return_void() {}
};
handle_type h_;
Generator(handle_type h) : h_(h) {}
~Generator() { h_.destroy(); }
explicit operator bool() {
fill(); // The only way to reliably find out whether or not we finished coroutine,
// whether or not there is going to be a next value generated (co_yield) in
// coroutine via C++ getter (operator () below) is to execute/resume coroutine
// until the next co_yield point (or let it fall off end).
// Then we store/cache result in promise to allow getter (operator() below to
// grab it without executing coroutine).
return !h_.done();
}
T operator()() {
fill();
full_ = false; // we are going to move out previously cached
// result to make promise empty again
return std::move(h_.promise().value_);
}
private:
bool full_ = false;
void fill() {
if (!full_) {
h_();
if (h_.promise().exception_)
std::rethrow_exception(h_.promise().exception_);
// propagate coroutine exception in called context
full_ = true;
}
}
};