C++ Coroutines Two: Electric Boogaloo: co_await a QNetworkReply*

If you haven't read my previous blog post on coroutines in C++, you want to do that before reading this blog post.

In the last blog post, I explained how to construct an awaitable Future type.

If you've been tinkering with coroutines, you may have tried the following thing to allow awaiting on a pointer type:

auto operator await(QNetworkReply* it) {
    // ...
}

As far as the C++ compiler is concerned, this ain't kosher, because you're trying to define an operator for a primitive type (a pointer). Your compiler will probably tell you that this needs to be done on a class or enum type.

But that leaves the question of “how do I make a QNetworkReply* co_awaitable if I can't define co_await on a pointer type?” It is possible.

Your promise_type object has more jobs than what I covered in the last blog post. One of them is to potentially provide an await_transform function.

An await_transform function essentially “preprocesses” any values being co_awaited before the compiler attempts to look for a co_await implementation.

In code, if await_transform is defined on a promise_type, any co_await that looks like this:

auto response = co_await pendingValue;

will actually become:

auto response = co_await await_transform(pendingValue);

This is how we co_await a type you can't provide an operator co_await for: transform it into a type you can.

To integrate a QNetworkReply* into our coroutine types from before, this means we need to make an await_transform function that takes a QNetworkReply* and return a future. This is fairly simple.

auto await_transform(QNetworkReply* it) {
    auto future = Future();

    QObject::connect(it, &QNetworkReply::finished, it, [future, it]() {
        if (it->error() == QNetworkReply::NoError) {
            future.succeed(it);
        } else {
            future.fail(it);
        }
    });

    return future;
}

Make a future, mark as success if the reply succeeds, mark as failure if it doesn't succeed. Easy peasy.

Plonk this function in your promise_type from before.

This now allows you to do this:

Future fetch(QString url) {
    auto response = co_await nam.get(url);
    if (response.error() != QNetworkReply::NoError) {
        co_return response.readAll();
    }
    co_return false;
}

You can implement an await_transform for any input you like, and return anything you like, as long as you can co_await it.

The full code for this blog post can be found in a single-file format at https://invent.kde.org/-/snippets/1716. Compile with C++20 and -fcoroutines-ts -stdlib=libc++ for clang, and -fcoroutines for gcc.

A full library built on the style of coroutines introduced here (with type safe template variants) + other goodies for asynchronous Qt is available at https://invent.kde.org/cblack/croutons.

That's all for this blog post. Stay tuned for more C++-related shenanigans.

Contact Me

If you didn't understand anything here, please feel free to come to me and ask for clarification.

Or, want to talk to me about other coroutine stuff I haven't discussed in these blog posts (or anything else you might want to talk about)?

Contact me here:

Telegram: https://t.me/pontaoski Matrix: #pontaoski:tchncs.de (prefer unencrypted DMs)

Tags: #libre