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
|
The class tt(Handler::State) keeps track of the coroutine's state. It must
publicly be known as the class tt(Handler::promise_type), which can be
realized using a public using-declaration associating a more appropriately
named class with `tt(promise_type)'.
In the current example the class name tt(State) is used, having the following
interface:
verbinsert(-as4 demo/fibocoro/fibo/state.2)
This tt(State) class doesn't declare a constructor, so its default
constructor is used. It's also possible to declare and define the default
constructor. Alternatively, by declaring and defining a constructor that has
the same parameters as its coroutine (or parameters that can be initialized by
the coroutine's parameters) that constructor is called when the coroutine
returns its handling object. E.g., if a coroutine's signature is
verb( Handler coro(int value, string const &str);)
and the tt(State) class has a constructor
verb( Handler::State::State(int value, string const &str);)
then that constructor is called. tt(State's) default constructor is called
if such a constructor is not available. In addition to calling tt(State's)
constructor a coroutine can also use an em(awaiter) to pass arguments to the
handler's tt(State) class. This method is covered in section ref(SETSTATE).
The data member tt(d_value) and member function tt(value()) are specifically
used by the class tt(Fibo), and other coroutine state classes might declare
other members. The remaining members are required, but the members returning
hi(suspend_always (std::)) tt(std::suspend_always) could also be declared
as members returning hi(suspend_never (std::)) tt(std::suspend_never).
By returning the (empty) structs tt(suspend_always) the coroutine's actions
are suspended until resumed. In practice tt(suspend_always) is used, and so
the tt(..._suspend) members can be declared tt(static), using these basic
implementations:
verb( inline std::suspend_always Fibo::State::initial_suspend()
{
return {};
}
inline std::suspend_always Fibo::State::final_suspend() noexcept
{
return {};
})
Likewise, the tt(unhandled_exception) member can be declared static when
it simply retrows exceptions that may be thrown by the coroutine:
verb( inline void Fibo::State::unhandled_exception()
{ // don't forget: #include <future>
std::rethrow_exception(std::current_exception());
})
The (required) member tt(Fibo::State::get_return_object) returns an
object of the coroutine's handling class (so: tt(Fibo)). The recipe is:
itemization(
it() pass the current object (which is the coroutine's state
object) to the member tt(from_promise) of an anonymous object of the
class tt(std::coroutine_handle<State>);
it() that anonymous object is a em(handle) which is passed as argument to
the constructor of the coroutine's handler class (i.e., the class
tt(Fibo));
it() this tt(Fibo) object is then returned by
tt(State::get_return_object).
)
Here is tt(Fibo::State::get_return_object's) implementation:
verb( inline Fibo Fibo::State::get_return_object()
{
return Fibo{ std::coroutine_handle<State>::from_promise(*this) };
})
The member tt(Fibo:State::yield_value) can be overloaded for different
argument types. In our tt(Fibo::State) there's only one tt(yield_value)
member, storing its parameter value in the tt(State::d_value) data member. It
also suspends the coroutine's execution as it returns tt(std::suspend_always):
verb( inline std::suspend_always Fibo::State::yield_value(size_t value)
{
d_value = value;
return {};
})
Now that the coroutine's handling class and its tt(State) subclass have been
covered, let's have a closer look at what happens when the tt(main) function
from the introductory section is executed. Here's tt(main) once again:
verbinsert(-ans4 demo/fibocoro/main.3)
When called with argument `2' the following happens:
itemization(
it() at line 2 the program starts;
it() at line 3 it looks as though tt(fiboCoroutine) is called (see the
lref(introduction section)(FIBOCORO) for its definition),
but before that:
itemization(
itt(State::get_return_object) is called, returning a tt(Fibo)
object. Note here that tt(fiboCoroutine) itself nowhere returns a
tt(Fibo) object, even though its definition suggests that it does.
The tt(get_return_object) member em(does) call tt(Fibo's)
constructor though, and this tt(Fibo) object is returned at line
3.
it() immediately after constructing the tt(Fibo) object the
coroutine's execution is suspended (as
tt(Fibo::State::initial_suspend) is automatically called).
)
Next, still at line 3, the returned tt(Fibo) object is assigned to
tt(fibo). The current implementation uses tt(auto fibo = ...), but
tt(Fibo fibo = ...) may also be used. Using tt(auto) might be
attractive if the name of the coroutine handling class's type is
rather convoluted;
itt(main's) execution continues at line 12 where tt(fibo.next()) is
called;
itt(fibo.next()) resumes the coroutine by calling tt(d_handle.resume()).
As this is the very first time that the coroutine is explicitly called
it now starts its execution at the first statement written by the
coroutine's author;
it() the coroutine continues its execution until it reaches a
tt(co_yield, co_await), or tt(co_return) keyword. In this case it's
tt(co_yield), returning the current fibonacci value (tt(size_t ret));
it() since a tt(State::yield_value) member whose parameter type matches
tt(ret's) type exists, that member is called, receiving tt(ret's)
value as its argument. As tt(yield_value) returns
tt(std::suspend_always) the coroutine is again suspended;
it() once suspended control is returned to tt(Fibo::next's) next
statement, where itt(Fibo's d_handle) uses its member tt(promise()) to
reach the handler's tt(State) object, allowing tt(next) to return
tt(State::value()) (which is the most recently computed fibonacci
value, stored in the tt(State)
object when tt(State::yield_value) was called).
tt(Fibo::next()) returns that value;
it() after completing line 12 the tt(for)-statement continues at line 7,
once again reaching line 12.
it() at subsequent iterations the coroutine continues beyond the statement
where it was suspended (at its tt(co_yield) statement). So it doesn't
continue at the coroutine's first statement but continues its
execution by performing the statements of its tt(while (true))
statement until again reaching its tt(co_yield) statement, as
described above;
it() after two iteration the tt(for)-statement ends, and just before the
program ends (at line 15) tt(Fibo's) destructor is automatically
called, returning the memory allocated for the coroutine's data to the
common pool.
)
|