File: asyncgenerator.md

package info (click to toggle)
qcoro 0.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 1,700 kB
  • sloc: cpp: 8,573; python: 32; xml: 26; makefile: 23; sh: 15
file content (124 lines) | stat: -rw-r--r-- 4,362 bytes parent folder | download | duplicates (3)
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
<!--
SPDX-FileCopyrightText: 2022 Daniel Vrátil <dvratil@kde.org>

SPDX-License-Identifier: GFDL-1.3-or-later
-->

# QCoro::AsyncGenerator<T>

{{ doctable("Coro", "QCoroAsyncGenerator") }}

```cpp
template<typename T> class QCoro::AsyncGenerator
```

`AsyncGenerator<T>` is fundamentally identical to [`QCoro::Generator<T>`][qcoro-generator].
The only difference is that the value is produced asynchronously. Asynchronous
generator is used exactly the same way as synchronous generators, except that the
result of `AsyncGenerator<T>::begin()` must be `co_await`ed, and incrementing
the returned iterator must be `co_await`ed as well.

```cpp
QCoro::AsyncGenerator<uint8_t> lottoNumbersGenerator(int count) {
    Hat hat; // Hat with numbers
    Hand hand; // Hand to pull numbers out of the hat
    for (int i = 0; i < count; ++i) {
        const uint8_t number = co_await hand.pullNumberFrom(hat);
        co_yield number; // guaranteed to be a winning number
    }
}

QCoro::Task<> winningLottoNumbers() {
    const auto makeMeRich = lottoNumbersGenerator(10);
    // We must co_await begin() to obtain the initial iterator
    auto winningNumber = co_await makeMeRich.begin();
    std::cout << "Winning numbers: ";
    while (winningNumber != makeMeRich.end()) {
        std::cout << *winningNumber;
        // And we must co_await increment
        co_await ++(winningNumber);
    }
}
```

You might be wondering why are we `co_await`ing `AsyncGenerator<T>::begin()` and
`AsyncGeneratorIterator<T>::operator++()`, and not just the value (the result of
dereferencing the iterator) - it would surely make the code simpler and more intuitive.
The simple reason is that we are `co_await`ing the next iterator (which either holds
a value or is past-the-end iterator) rather than the value itself. Once we have the
iterator, we must check whether it's valid or not, because paste-the-end iterator is
not dereferencable. That's why the `AsyncGenerator<T>::begin()` and
`AsyncGeneratorIterator<T>::operator++()` operations are asynchronous, rather than
`AsyncGeneratorIterator<T>::operator*()`.

#### Exceptions

When the generator coroutine throws an exception, the exception will be rethrown from
the `operator++()` (or from generator's `begin()`) function when they are `co_await`ed.

Afterwards, the iterator is considered invalid and the generator is finished and may not
be used anymore.

```cpp
QCoro::AsyncGenerator<int> generatorThatThrows() {
    while (true) {
        const int val = co_await generateRandomNumber();
        if (val == 0) {
            throw std::runtime("Division by zero!");
        }
        co_yield 42 / val;
    }
}

QCoro::Task<> divide42ForNoReason(std::size_t count) {
    auto generator = generatorThatThrows();
    std::vector<int> numbers;
    try {
        // Might throw if generator generates 0 immediately.
        auto it = co_await generator.begin();
        while (numbers.size() < count) {
            numbers.push_back(*it);
            co_await ++it; // might throw
        }
    } catch (const std::runtime_error &e) {
        // We were unable to generate all numbers
    }
}
```

### QCORO_FOREACH

```cpp
#define QCORO_FOREACH(value, generator)
```

The example in previous chapter shows one example of looping over values produced
by an asynchronous generator with a `while` loop. This is how it would look like
with a `for` loop:

```cpp
auto generator = createGenerator();
for (auto it = co_await generator.begin(), end = generator.end(); it != end; co_await ++it) {
    const QString &value = *it;
    ...//do something with value
}
```

Sadly, it's not possible to use the generator with a ranged-based for loop. While
initially the proposal for C++ coroutines did contain `for co_await` construct, it
was removed as the committee was [concerned that it was making assumptions about the
future interface of asynchronous generators][p0664r8c35].

For that reason, QCoro provides `QCORO_FOREACH` macro, which is very similar to
[`Q_FOREACH`][qdoc-qforeach] and works exactly like the for-loop in the example
above:

```cpp
QCORO_FOREACH(const QString &value, createGenerator()) {
    ... // do something with value
}
```

[qcoro-generator]: ./generator.md
[p0664r8c35]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0664r8.html#35
[qdoc-qforeach]: https://doc.qt.io/qt-5/qtglobal.html#Q_FOREACH