November 8th, 2021

Restoring support for pre-standardization coroutine free awaiters for even older code bases

A customer had a large code base that was originally written in C++/CX, and they have been gradually converting it to C++/WinRT. They managed to get most of it converted, but the XAML binding is a major obstacle because you have to do it all at once, and they have a lot of XAML.

They found that with the upgrade to version 2.0.200729.8 of C++/WinRT, they lost the ability to co_await an IAsyncAction^. What they discovered is that C++/WinRT dropped support for pre-standardization coroutine free awaiters. They tried the modernizer from the article, but couldn’t get it to work.

Okay, the first mistake is mine. I inadvertently relied upon a C++20 feature in the make_cpp20_await_adapter function:

    template<typename Awaitable>
    auto make_cpp20_await_adapter(Awaitable& awaitable)
    {
        return cpp20_await_adapter<Awaitable>(awaitable);
    }

This takes advantage of the treatment of a parenthesized list as an aggregate initializer if a class lacks a constructor. To get this code to work on C++17, we’ll have to use the braced constructor instead.

    template<typename Awaitable>
    auto make_cpp20_await_adapter(Awaitable& awaitable)
    {
        return cpp20_await_adapter<Awaitable>{ awaitable };
    }

I’ve retroactively updated the article to incorporate this fix.

The last piece of the puzzle is realizing that some old versions of pplawait.h use free awaiters and therefore require modernization. (Version 14.16.27023 uses free awaiters, but version 14.29.30133 uses standard awaiters. I don’t know exactly when the change happened.)

If you can’t upgrade to a newer version of pplawait.h, you can add modernizers:

namespace Windows::Foundation
{
    auto operator co_await(IAsyncAction^ x)
    {
        return ::modernizer::make_cpp20_await_adapter(x);
    }

    template<typename T>
    auto operator co_await(IAsyncOperation<T>^ x)
    {
        return ::modernizer::make_cpp20_await_adapter(x);
    }

    template<typename P>
    auto operator co_await(IAsyncActionWithProgress<P>^ x)
    {
        return ::modernizer::make_cpp20_await_adapter(x);
    }

    template<typename T, typename P>
    auto operator co_await(IAsyncOperationWithProgress<T, P>^ x)
    {
        return ::modernizer::make_cpp20_await_adapter(x);
    }
}
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • 紅樓鍮 · Edited

    I wonder whether the co_await operators would better accept references to handles:

    auto operator co_await(IAsyncAction ^const &);

    I think copying and destroying a handle correspond to calling AddRef resp. Release on the pointed-to object, IIRC about C++/CX handles.