blob: 682b8e859acf0e7b7fc3d5bdd324ad2fb87c0f72 (
plain)
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
|
// -*- c++ -*-
#pragma once
// The code in this file has been taken directly from:
// https://en.cppreference.com/w/cpp/language/coroutines
#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>
template <typename T, std::size_t S>
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.
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() { }
void* operator new(std::size_t n)
{
static char buf[S];
std::cout << "alloc!\n";
if(n < S) throw std::bad_alloc();
return buf;
}
};
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;
}
}
};
|