File: await-async-string.cpp

package info (click to toggle)
qcoro 0.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,700 kB
  • sloc: cpp: 8,573; python: 32; xml: 26; makefile: 23; sh: 15
file content (204 lines) | stat: -rw-r--r-- 7,380 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// SPDX-FileCopyrightText: 2021 Daniel Vrátil <dvratil@kde.org>
//
// SPDX-License-Identifier: MIT

#include "qcoro/coroutine.h"

#include <QCoreApplication>
#include <QMetaObject>
#include <QTimer>

#include <chrono>
#include <iostream>
#include <memory>
#include <string>

using namespace std::chrono_literals;

class FutureString : public QObject {
    Q_OBJECT
public:
    explicit FutureString(const QString &str) : mStr(str) {
        QTimer::singleShot(1s, this, [this]() {
            mReady = true;
            Q_EMIT ready();
        });
    }

    bool isReady() const {
        return mReady;
    }
    QString result() const {
        return mStr;
    }
Q_SIGNALS:
    void ready();

private:
    bool mReady = false;
    QString mStr;
};

// Awaiter is a concept that provides the await_* methods below, which are used by the
// co_await expression.
// Type is Awaitable if it supports the `co_await` operator.
//
// When compiler sees a `co_await <expr>`, it first tries to obtain an Awaitable type for
// the expression result result type:
//  - first by checking if the current coroutine's promise type has `await_transform()`
//    that for given type returns an Awaitable
//  - if it does not have await_transform, it treats the result type as awaitable.
// Thus, if the current promise type doesn't have compatible `await_transform()` and the
// type itself is not Awaitable, it cannot be `co_await`ed.
//
// If the Awaitable object has `operator co_await` overload, it calls it to obtain the
// Awaiter object. Otherwise the Awaitable object is used as an Awaiter.
//
class FutureStringAwaiter {
public:
    explicit FutureStringAwaiter(const std::shared_ptr<FutureString> value) noexcept
        : mFuture(value) {
        std::cout << "FutureStringAwaiter constructed." << std::endl;
    }
    ~FutureStringAwaiter() {
        std::cout << "FutureStringAwaiter destroyed." << std::endl;
    }

    // Called by compiler when starting co_await to check whether the awaited object is by any
    // chance already ready, so that we could avoid the suspend-resume dance.
    bool await_ready() noexcept {
        std::cout << "FutureStringAwaiter::await_ready() called." << std::endl;
        return mFuture->isReady();
    }
    // Called to tell us that the awaiting coroutine was suspended.
    // We use the awaitingCoroutine handle to resume the suspended coroutine once the
    // co_awaited coroutine is finished.
    void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept {
        std::cout << "FutureStringAwaiter::await_suspend() called." << std::endl;
        QObject::connect(mFuture.get(), &FutureString::ready,
                         [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); });
    }
    // Called when the co_awaiting coroutine is resumed. Returns result of the
    // co_awaited expression.
    QString await_resume() noexcept {
        std::cout << "FutureStringAwaiter::await_resume() called." << std::endl;
        return mFuture->result();
    }

private:
    std::shared_ptr<FutureString> mFuture;
};

class FutureStringAwaitable {
public:
    FutureStringAwaitable(const std::shared_ptr<FutureString> value) noexcept : mFuture(value) {
        std::cout << "FutureStringAwaitable constructed." << std::endl;
    }

    ~FutureStringAwaitable() {
        std::cout << "FutureStringAwaitable destroyed." << std::endl;
    }

    FutureStringAwaiter operator co_await() {
        std::cout << "FutureStringAwaitable::operator co_await() called." << std::endl;
        return FutureStringAwaiter{mFuture};
    }

private:
    std::shared_ptr<FutureString> mFuture;
};

class VoidPromise {
public:
    explicit VoidPromise() {
        std::cout << "VoidPromise constructed." << std::endl;
    }

    ~VoidPromise() {
        std::cout << "VoidPromise destroyed." << std::endl;
    }

    struct promise_type {
        explicit promise_type() {
            std::cout << "VoidPromise::promise_type constructed." << std::endl;
        }

        ~promise_type() {
            std::cout << "VoidPromise::promise_type destroyed." << std::endl;
        }

        // Says whether the coroutine body should be executed immediately (`suspend_never`)
        // or whether it should be executed only once the coroutine is co_awaited.
        std::suspend_never initial_suspend() const noexcept {
            return {};
        }
        // Says whether the coroutine should be suspended after returning a result
        // (`suspend_always`) or whether it should just end and the frame pointer and everything
        // should be destroyed.
        std::suspend_never final_suspend() const noexcept {
            return {};
        }

        // Called by the compiler during initial coroutine setup to obtain the object that
        // will be returned from the coroutine when it is suspended.
        // Sicne this is a promise type for VoidPromise, we return a VoidPromise.
        VoidPromise get_return_object() {
            std::cout << "VoidPromise::get_return_object() called." << std::endl;
            return VoidPromise();
        }

        // Called by the compiler when an exception propagates from the coroutine.
        // Alternatively, we could declare `set_exception()` which the compiler would
        // call instead to let us handle the exception (e.g. propagate it)
        void unhandled_exception() {
            std::terminate();
        }

        // The result of the promise. Since our promise is void, we must implement `return_void()`.
        // If our promise would be returning a value, we would have to implement `return_value()`
        // instead.
        void return_void() const noexcept {};

        FutureStringAwaitable await_transform(const std::shared_ptr<FutureString> &future) {
            std::cout << "VoidPromise::await_transform called." << std::endl;
            return FutureStringAwaitable{future};
        }
    };
};

std::shared_ptr<FutureString> regularFunction() {
    return std::make_shared<FutureString>(QStringLiteral("Hello World!"));
}

// This function co_awaits, therefore it's a co-routine and must
// have a promise type to return to the caller.
VoidPromise myCoroutine() {
    // 1. Compiler creates a new coroutine frame `f`
    // 2. Compiler obtains a return object from the promise.
    //    The promise is of type `std::coroutine_traits<CurrentFunctionReturnType>::promise_type`,
    //    which is `CurrentFunctionReturnType::promise_type` (if there is no specialization for
    //    `std::coroutine_traits<CurrentFunctionReturnType>`)
    // 3. Compiler starts execution of the coroutine body by calling `resume()` on the
    //    current coroutine's std::coroutine_handle (obtained from the promise by
    //    `std::coroutine_handle<decltype(f->promise)>::from_promise(f->promise)

    std::cout << "myCoroutine() started." << std::endl;
    const auto result = co_await regularFunction();
    std::cout << "Result successfully co_await-ed: " << result.toStdString() << std::endl;

    qApp->quit();
}

int main(int argc, char **argv) {

    QCoreApplication app(argc, argv);
    QMetaObject::invokeMethod(&app, myCoroutine);
    QTimer t;
    QObject::connect(&t, &QTimer::timeout, &app, []() { std::cout << "Tick" << std::endl; });
    t.start(100ms);
    return app.exec();

    return 0;
}

#include "await-async-string.moc"