Instead of replacing the awaiter so it doesn’t retrieve the results, we can go ahead and collect the results, and then return them. The Windows Runtime doesn’t have a convenient way to return a strongly-typed heterogeneous collection. Structures must be declared in metadata, and returning a vector of IInspectables is not strongly-typed.
Fortunately, we can use our friend simple_task, which has since been added to the Windows Implementation Library as wil::.
template<typename... Results>
wil::task<std::tuple<Results>>
when_all_with_results(
winrt::Windows::Foundation::IAsyncOperation<Results>... asyncs)
{
co_return std::make_tuple(co_await asyncs...);
}
auto [result1, result2] =
co_await when_all_with_results(Do1Async(), Do2Async());
We wish we could have written
template<typename... Asyncs>
wil::task<auto>
when_all_with_results(Asyncs... asyncs)
{
co_return std::make_tuple(co_await asyncs...);
}
but there is currently no facility in the C++ language for this sort of weirdo template placeholder usage.
The above formulation does limit you to IAsyncOperation<T>, so you cannot use other awaitables with when_all_with_results, like IAsyncÂOperationÂWithÂProgress<T, P>. Adding IAsyncÂOperationÂWithÂProgress<T, P> support isn’t so bad, because the result type is available from both IAsyncÂOperation<T> and IAsyncÂOperationÂWithÂProgress<T, P> by checking the return type of GetÂResult().
template<typename... Asyncs>
wil::task<std::tuple<
decltype(std::declval<Asyncs>().GetResults())...>>
when_all_with_results(Asyncs... asyncs)
{
co_return std::make_tuple(co_await asyncs...);
}
Or, taking advantage of trailing return types so we don’t need to go through the hassle of declval:
template<typename... Asyncs>
auto
when_all_with_results(Asyncs... asyncs) ->
wil::task<std::tuple<
decltype(asyncs.GetResults())...>>
{
co_return std::make_tuple(co_await asyncs...);
}
Extending support to other types of awaitables, such as wil::task, means having to fire up a lot of infrastructure to figure out what the co_await return type is.
template<typename T>
using await_result = decltype(std::declval<
awaiter_finder::type<T>>().await_resume());
template<typename... Asyncs>
wil::task<std::tuple<await_result<Asyncs>...>>
when_all_with_results(Asyncs... async)
{
co_return std::make_tuple(co_await async...);
}
Great, you solved one problem but introduced at least two new ones.
First problem is that one of these awaitables might produce a C++ reference. This wasn’t a problem with IAsyncOperation, since that never produces a C++ reference, but arbitrary awaitables might do that. Another problem is that one of the async values might be an awaitable that completes with void. You can’t put a void inside a tuple.
We’ll look more closely at these problems next time.
0 comments