Skip to content

QCoro::AsyncGenerator

ModuleCoro
Include
#include <QCoroAsyncGenerator>
CMake
target_link_libraries(myapp QCoro::Coro)
QMake
QT += QCoroCoro
template<typename T> class QCoro::AsyncGenerator

AsyncGenerator<T> is fundamentally identical to QCoro::Generator<T>. 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_awaited, and incrementing the returned iterator must be co_awaited as well.

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

void 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 << *winningNumger;
        // And we must co_await increment
        co_await ++(winningNumber);
    }
}

You might be wondering why are we co_awaiting 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_awaiting 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*().

QCORO_FOREACH

#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:

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.

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

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