File: state.yo

package info (click to toggle)
c%2B%2B-annotations 12.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 13,044 kB
  • sloc: cpp: 24,337; makefile: 1,517; ansic: 165; sh: 121; perl: 90
file content (133 lines) | stat: -rw-r--r-- 7,283 bytes parent folder | download
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.
    )