Repository navigation
Bug: <experimental/coroutine> will be deprecated in MSVC Build Tools 14.51 #1520
Description
Activity
StephanTLavavej commented
on Nov 4, 2025 MemberAuthorMore actionsWe've additionally found places where CppWinRT is passing deprecated
/await:cppwinrt/nuget/CppWinrtRules.Project.xml
Lines 84 to 87 in dcaa98c
<BoolProperty Name="CppWinRTEnableLegacyCoroutines" DisplayName="Enable legacy coroutines (C++17)" Description="Enables the /await compiler option (disable this if you want to pass this yourself)" Category="General" />
<AdditionalOptions Condition="'%(ClCompile.LanguageStandard)' == 'stdcpp17' And '$(CppWinRTEnableLegacyCoroutines)' != 'false'">%(AdditionalOptions) /await</AdditionalOptions> I don’t work on the C++ tooling much anymore, but I just wanted to point out that all these wonky workarounds are ancient and predate much of the coroutine standardization process. I doubt there’s any danger in simply ripping it all out at this point (Option 1).
Reacted by Stephan T. Lavavejcpp20 isn't exactly new at this point. I would also be supportive of removing pre-standardization experimental coroutine support. My vote would be that cpp20 gets standard coroutines and that cpp17 gets zero coroutines by default (with a possible option to opt into /await:strict).
We don't need to worry about cpp14 consumers because the minimum language version for cppwinrt is cpp17.
/await:strictcan act as the option itself for the opt-in. Just have cppwinrt handle#ifdef __cpp_lib_coroutineand you'll get this exact behavior.Maybe if you wanna be nice you can add an #error or #warning for people that still use /await.
Thanks for the heads up, Stephan T. Lavavej (@StephanTLavavej)! We need to start preparing right away...
There are many projects in the OS using cppwinrt with
/awaitwhere I'm confident the default is still cpp17 rather than cpp20. I'm not holding my breath on getting all those projects migrated to cpp20 soon, so let's preserve cpp17 via/await:strictand get the OS projects builds to default to/await:strict.Unfortunately, I also count hundreds of projects that will need to be updated because they have manually added
/await. Let's start a thread with the LKG tool ingestion folks and find out how far back we have support for/await:strictand other logistical considerations.I opened #1521 to fix this. It swaps out /await for /await:strict by default on C++17, which can be observed by all the existing tests still building fine :)
For the internal projects that need to be updated, I can't help however (maybe one day...)
I opened #1521 to fix this. It swaps out /await for /await:strict by default on C++17, which can be observed by all the existing tests still building fine :)
For the internal projects that need to be updated, I can't help however (maybe one day...)
In light of the big and potentially length migration process, I'm favoring Option 2 temporarily. We should update the "no coroutines" error message as suggested. Also consider an informative warning (with escape hatch) for when the legacy option is detected, to warn consumers to start migrating.
In other words, deprecate cppwinrt's support for legacy coroutines, but give consumers time to migrate before we turn off support altogether by adopting Option 1.
We should also look into some sort of deprecation or update for
$(CppWinRTEnableLegacyCoroutines)added to Microsoft.Windows.CppWinRT.targets in #1015. The targets file in the NuGet adds/awaitby default. I don't have telemetry to tell me whether consumers are predominantly using this behavior, or if they are opting out and controlling that flag themselves. But a quick search on GitHub ofCppWinRTEnableLegacyCoroutinesonly turned up a few hits whileMicrosoft.Windows.CppWinRTturned up 6300 hits, and addingawaitto the search lowered that number to ~500. So that suggests the vast majority of consumers are using the automatic behavior and just want things to work. Removing the automatic/awaitcompletely is the kind of widely disruptive breaking change that we'd reserve for a major version. Perhaps we should simply update the automatic option to supply/await:strictin the meantime.I'm not sure I get the reasoning for option 2. Users can simply hold off updating C++/WinRT until they are ready to get off legacy coroutines, since it is a NuGet package.
Agreed that we could add a better warning here (maybe even make it an
#error), but I don't feel an escape hatch for the warning is needed for the same reason as above. Not ready to upgrade yet? Stay on the old package (you'll have to stay on an old compiler too anyways).Just doing
#if defined(__cpp_lib_coroutine) #include <coroutine> #elif defined(_RESUMABLE_FUNCTIONS_SUPPORTED) #warning "Legacy coroutines are no longer supported, please upgrade to /std:c++20 or /await:strict #endif
should be plentiful.
But a quick search on GitHub of
CppWinRTEnableLegacyCoroutinesonly turned up a few hitsWorth pointing out that these hits are all disabling it. For these people, the end behavior stays the same, C++/WinRT will just start passing
/await:strictfor them (which they where already doing).So that suggests the vast majority of consumers are using the automatic behavior and just want things to work. Removing the automatic /await completely is the kind of widely disruptive breaking change that we'd reserve for a major version.
The new behavior of the PR for those customers, is that they would get automatically upgraded to C++20 coroutines, since
CppWinRTEnableCpp17Coroutinesdefaults to true. So things still just work.The one edge case I could think of, is people with
<CppWinRTEnableLegacyCoroutines>true</CppWinRTEnableLegacyCoroutines>would start getting/await:strictinstead of/await, but I can't find anybody actually doing or wanting that, especially since the option got added when the new standard coroutines got backported to C++17.I'm not sure I get the reasoning for option 2. Users can simply hold off updating C++/WinRT until they are ready to get off legacy coroutines, since it is a NuGet package.
Holding off on updating means also holding off on other bug fixes and improvements, some of which may be important or urgent. Some projects are huge and messy and might take considerable effort to manually clean all the legacy
/awaitoptions that have crept in. Not all who consume C++/WinRT do so through NuGet, but still want the updated codegen.As one small example, some customers consume the C++/WinRT headers that are distributed in the Windows SDK instead of using NuGet. If we completely rip out legacy coroutine support, all those customers that are using legacy
/awaitwill break when they get a new SDK. Ditto for many internal Windows OS projects when I need to bring the next version of C++/WinRT into some of our internal builds.Instead, I'm proposing we keep the legacy support for now, but with the minor cleanup in the codegen to not rely solely on
__has_include(<experimental/coroutine>), but instead check for the actual presence of/awaitbefore including the legacy headers. As part of this, we also improve the error message for "no coroutine support detected" and optionally introduce a build warning (with escape hatch) when legacy support is included to warn users of the impending toolset deprecation and suggest migrating to C++/20 or/await:strict.This gives customers time to clean their stuff up and/or quickly revive their CI builds when suddenly break overnight because a GitHub or Azure DevOps build runner incremented the version of the Windows SDK that is preinstalled on their image. (Yes, CI build runner images update this kind of thing (Windows SDK version) with no action on the user's end, and I'd like to limit the potential destruction).
Worth pointing out that these hits are all disabling it. For these people, the end behavior stays the same, C++/WinRT will just start passing /await:strict for them (which they where already doing).
I saw a mix of
falseandtruehits in the search. Changing the name fromCppWinRTEnableLegacyCoroutinestoCppWinRTEnableCpp17Coroutinesmeans folks who had set it tofalsewon't inherit that old value, and the new default will suddenly start adding stuff to their command line again. We don't have to change the property name when we can update the display name and description. The name is unfortunate, but it's probably better than churning the setting (unless we put this behind a major version update).Furthermore, if we change the default to
/await:strict, then the non-trivial number of projects that had manually added/awaitwill break. Before, they were getting/awaittwice: once from the NuGet injection, and once from their vcxproject/props, which is a silly when you look at the command line, but benign. Now, they'll get/await:strictfrom the NuGet added to their/awaitin their vcxproject/props. This fails to compile withcl : command line error D8016: '/await:strict' and '/await' command-line options are incompatible.Maybe that amount of breakage is worth it, compared to kicking the can down the road for the rest of the users by leaving the default as the soon-to-be-deprecated option, but we should at least be aware that this will impact a number of customers.
Changing the default is definitely desirable I think - most users do not set that property and rely on the implicit behavior, never manually invoking
std::experimentalstuff. We should favor these usages getting auto-upgraded over not breaking the minority of projects that might be using it, IMO. There is more breakage to be seen by keeping the majority of users on/awaitand those getting an update of their compiler in CI.I'm not a big fan of the escape hatch, because as you've mentioned it allows people to quickly bring CI back up. In my experience when this happens, people set the escape hatch and forget about it, kicking the can down the road for when it's fully removed. I'm having this exact thing at work right now, where a compiler upgrade introduced new warnings we had to disable and I'm the only one driving the effort to properly fix them so we can re-enable them (but I keep getting distracted with higher priority work).
In this case migration is fairly easy, there is no API differences so you can get away with a simple find and replace from
std::experimentaltostd(if you've even used anything from there - which is unlikely). Considering that, I think it's better to encourage early migration here - if you use/Wxthen you've asked for the random CI upgrades breaking things anyways :)I'm not sure there's an easy solution here that wouldn't break some amount of explicit usages, but also auto-upgrade implicitly. For example, changing the default to
false, introducing a new option for/await:strictand defaulting that totruehits the same D8016 error you mention if someone had set the legacy option totrue(and those users probably would've preferred the auto-upgrade behavior anyways).I'd be curious to see how many projects break when using my PR in an internal Windows build - I can imagine most of the breakages would be an error because they've explicitly passed
/awaitsomewhere and C++/WinRT now also passes/await:strict- there is probably not many projects usingCppWinRTEnableLegacyCoroutinessince it's fairly recent all things considered. If there was an escape hatch, you'd have to modify all those project files to silence the warning, but I would bet that just fixing the/awaitwould get most (all?) projects building again anyways.Either way, I can keep the PR up for when you guys are ready to fully tear it off. Kenny seemed in favor of that.
github-actions commented
on Nov 20, 2025 on Nov 20, 2025 – with GitHub Actions · Hidden as resolvedshow commentMore actionsStephanTLavavej commented
on Nov 20, 2025 on Nov 20, 2025 · Hidden as resolvedAuthorshow commentMore actions7 remaining items
StephanTLavavej commented
on Jan 16, 2026 on Jan 16, 2026 · Hidden as resolvedAuthorshow commentMore actionsgithub-actions commented
on Feb 18, 2026 on Feb 18, 2026 – with GitHub Actions · Hidden as resolvedshow commentMore actionsgithub-actions commented
on Mar 1, 2026 on Mar 1, 2026 – with GitHub Actions · Hidden as resolvedshow commentMore actionsUpdate. I've gotten some extra help to migrate the deprecated
/awaitto/await:strictin Windows, and I feel far better about ripping off support more actively. (Option 1). I'm going to try and move this forward now, and if we hit bumps, I'll switch to Option 2 and add an escape hatch to opt in support for the legacy experimental coroutines.Reacted by Kenny Kerr and Stephan T. LavavejAlmost there. Charles Milette (@sylveon) Worked on some nice coroutine-less fallback logic because the current C++/WinRT was overeagerly including the experimental headers, convincing itself that it had coroutine support even when no command-line option was supplied. This means we almost certainly had some number of consumers using C++/WinRT without supplying
/await, and as long as they never utteredco_await, their programs blissfully compiled and ran. (C++/WinRT doesn't generate any code that usesco_await, it merely defines the machinery that allows consumers to do so)Once we remove the overeager include, these consumers would suddenly break if the strengthened coroutine detection logic started emitting errors. I wanted to see what would happen if these consumers started sprinkling some
co_awaits into their code...Stephan T. Lavavej (@StephanTLavavej) may be amused to learn there is an error message seemingly embedded in the compiler itself that still refers to the old deprecated experimental stuff. (I'm assume it's coming from the front end, and not anywhere in the STL)
https://godbolt.org/z/5zGGGqr3Y
<source>(10): error C3773: Use of 'co_await' in this context is a non-conforming extension in C++17 <source>(10): note: Please use '/await' command-line option to enable relevant extensions <source>(10): error C3774: cannot find 'std::experimental': Please include <experimental/coroutine> header
I've filed a Developer Community issue for this, hoping to remove this additional tripping hazard for all users (not just C++/WinRT users) as wedraggently encourage them away from their legacy coroutines.https://developercommunity.visualstudio.com/t/Error-message-suggests-experimental-aw/11060319
StephanTLavavej commented
on Mar 15, 2026 MemberAuthorMore actionsI took a brief look, but more compiler changes may be necessary than a simple message fix so I'll let a real compiler dev investigate.
Reacted by Ryan Shepherd
Version
master
Summary
I work on the Visual C++ team, where we regularly build popular open-source projects, including cppwinrt, with development versions of the MSVC Build Tools in order to find and fix regressions before they can ship and cause trouble for you. This also allows us to provide advance notice of breaking changes, which is the case here.
In the MSVC Build Tools 14.50 shipping in Visual Studio 2026 18.0, the
/awaitcompiler option is officially being deprecated. This is a command-line deprecation warning that does not break builds (even under/WX):In the MSVC Build Tools 14.51 shipping in some future update to Visual Studio 2026, I am additionally deprecating the
<experimental/coroutine>,<experimental/resumable>, and<experimental/generator>headers, as the first two have been superseded by C++20<coroutine>and the third has been superseded by C++23<generator>. This is a "hard deprecation" implemented as astatic_assert. It can be acknowledged and silenced with an escape hatch macro, but future removal is coming. (PR: microsoft/STL#5804 )We've found that cppwinrt conditionally includes
<experimental/coroutine>in an unusual way. It begins by detecting support for Standard coroutines and prefers to include<coroutine>(good), but otherwise falls back to including<experimental/coroutine>. Interestingly, this is guarded by__has_include(<experimental/coroutine>)(which is always true, at least until I physically remove the header in the future), but not by detecting the/awaitcompiler option (which is indicated by the_RESUMABLE_FUNCTIONS_SUPPORTEDpredefined macro):cppwinrt/strings/base_includes.h
Lines 50 to 78 in dcaa98c
This has affected several projects in our Real World Code test suite (MuseScore which uses cppwinrt, Qgroundcontrol which uses qtspeech which uses cppwinrt, Qt6 which uses cppwinrt). Interestingly, these projects don't appear to be compiling with deprecated
/await- they are simply dragging in cppwinrt, which drags in<experimental/coroutine>, which now triggers my hard deprecation error. (I believe that previously, the experimental coroutine machinery wouldn't actually have been usable without/await, but nothing was trying to use it.)I am not exactly sure how cppwinrt should change. I think there are a few options:
Option 1: Require Standard coroutines
You could simply require
<coroutine>, drop the codepath for<experimental/coroutine>, and adjust your error message that currently says "Try enabling C++20 in your compiler." to additionally mention that/await:strictcan be used to opt-in to Standard coroutines even for C++14/17 mode (this is less intrusive for projects that have difficulty quickly migrating to C++20).Option 2: Detect deprecated
/awaitInstead of
__has_include(<experimental/coroutine>), check for_RESUMABLE_FUNCTIONS_SUPPORTED. This would block "plain vanilla" usage of cppwinrt (i.e. usage in C++14/17 mode with neither/awaitnor/await:strict), forcing users to choose either the Standard or the deprecated path. Again it would be good to adjust the error message to mention/await:strict.Option 3: Detect deprecated
/await, otherwise don't errorAgain check for
_RESUMABLE_FUNCTIONS_SUPPORTED, but instead of emitting a compiler error for users in plain vanilla C++14/17 mode, simply don't include anything. It appears that several projects are managing minimal usage of cppwinrt without actually needing coroutines, as indicated by how they were compiling in plain vanilla C++14/17 mode, dragging in cppwinrt, which included<experimental/coroutine>(before my hard deprecation error), and then did nothing with it that actually needed coroutine support. I am not exactly sure if other code would need to be adjusted, but this is potentially the least invasive option for third-party projects that could allow them to continue compiling without needing to add/await:strict(or migrate to C++20).Macro detection example
For completeness, here is how MSVC's macros work:
(Here I'm using
__cpp_impl_coroutineto detect compiler support for Standard coroutines, but it's fine and probably better for cppwinrt to use our library macro__cpp_lib_coroutine.)Reproducible example
Expected behavior
No response
Actual behavior
No response
Additional comments
No response