Details
-
Epic
-
Resolution: Unresolved
-
P2: Important
-
None
-
None
-
None
-
C++20 coroutines @ Qt
Description
C++20 added coroutines, a generalisation of functions. While function executions have only entry and exit, coroutine executions can in addition be suspended and resumed. The C++20 variant of coroutines are stack-less, meaning they can only suspend themselves, they cannot be suspended from within a called function or coroutine. As a consequence, all functions between the caller and the suspend point have to be coroutines, too. This means, in particular, that coroutines cannot be callbacks for regular functions.
That said, however, a coroutine is just a normal function. So everything you know about functions (ABI, export/import, public/private, virtual or not, ...) applies to coroutines, too. What makes a function body a coroutine is that it uses one of the coroutine keywords:
- co_return ends the co-routine
- co_yield suspends the co-routine, returning a result
- co_await checks whether the argument can be resumed, if so, does it and returns the result, otherwise suspends this coroutine
The compiler then takes the coroutine body and transforms it into a function object (think lambda function) that contains a state machine which remembers which part of the function body to execute next when called (again, = "resumed"). This function object also contains the local variables, at least those whose lifetime crosses a suspension point. This coroutine frame is then stored in a coroutine_handle, which needs to be returned by to the caller somehow, usually in the return type. The actual function that users then call is now only a factory for the coroutine embedded in the return type, which is why a coroutine is just a regular function: the magic is in the return value.
The other side of the medal is co_await, which uses a specific protocol to talk to awaitables, which are, generally, those return values from functions acting a coroutine factories.
C++20 does not come with much of library support for coroutines. There's basically just coroutine_handle, the smart pointer for the coroutine frame, and suspend_always and suspend_never, trivial awaitable types.
Reference: https://en.cppreference.com/w/cpp/language/coroutines
A video of your truly explaining coroutines as an API principle: https://www.youtube.com/watch?v=tvdwYwTyrig
The different aspects of coroutines to Qt are:
- We already have types that represent coroutines, and these should be awaitable in a C++20 program. It's dangerously close to being a bug to have lazy sequences that are not awaitable, but, I guess, still on the feature side of the feature/bug boundary.
- QFuture is an async generator of a lazy sequence of items (or just one)
- QStringTokenizer is a synchronous generator
- We have a lot of async APIs that would benefit from being made awaitable. The difference to the first point is that here, we need to add new API (QIODevice::awaitableRead()) whereas there we have the types already, and just need to ensure they work with operator co_await.
- I/O
- signals
- timers
- Finally, once you start looking, lazy sequences are everywhere. Literally. Whereever we currently take or return an owning container, we could take and return lazy sequences instead
- QString::split etc
- QXmlStreamReader etc
- iteration over non-linear sequences:
- QSettings/JSON/CBOR key/value pair
- QObject hierarchy
- QTreeView/Widget
- ...
- ...
Attachments
Issue Links
- is required for
-
QTBUG-99243 Initiative: Qt and C++20
- Open
-
QTBUG-109361 C++20 is required for the development and building of Qt itself (Phase II)
- Open
- relates to
-
QTBUG-122714 QDialog::exec() using Coroutines instead of QEventLoop
- Reported