From dbc0fb99247e07c1a7628226882778e3cb337665 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 16 Jan 2026 14:43:47 -0800 Subject: [PATCH 1/6] Sync with OS commit 84854a6c2beef7430ac8886c7553166e9153d825 --- include/wil/Tracelogging.h | 47 +++- include/wil/coroutine.h | 23 +- include/wil/resource.h | 463 +++++++++++++++++++++++++++++++- include/wil/result.h | 4 +- include/wil/result_macros.h | 12 +- include/wil/result_originate.h | 2 + include/wil/stl.h | 46 ++++ include/wil/win32_helpers.h | 138 +++++++--- include/wil/wistd_type_traits.h | 5 + 9 files changed, 681 insertions(+), 59 deletions(-) diff --git a/include/wil/Tracelogging.h b/include/wil/Tracelogging.h index 2839667a4..ed51cd59c 100644 --- a/include/wil/Tracelogging.h +++ b/include/wil/Tracelogging.h @@ -530,8 +530,8 @@ class TraceLoggingProvider : public details::IFailureCallback static bool WasAlreadyReportedToTelemetry(long failureId) WI_NOEXCEPT { static long volatile s_lastFailureSeen = -1; - auto wasSeen = (s_lastFailureSeen == failureId); - s_lastFailureSeen = failureId; + long oldValue = InterlockedExchange(&s_lastFailureSeen, failureId); + auto wasSeen = (oldValue == failureId); return wasSeen; } @@ -4731,6 +4731,49 @@ WIL_WARN_DEPRECATED_1612_PRAGMA("IMPLEMENT_TRACELOGGING_CLASS") varName8, \ TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA), \ TelemetryPrivacyDataTag(PrivacyTag)) +#define DEFINE_COMPLIANT_CRITICAL_DATA_EVENT_PARAM9( \ + EventId, \ + PrivacyTag, \ + VarType1, \ + varName1, \ + VarType2, \ + varName2, \ + VarType3, \ + varName3, \ + VarType4, \ + varName4, \ + VarType5, \ + varName5, \ + VarType6, \ + varName6, \ + VarType7, \ + varName7, \ + VarType8, \ + varName8, \ + VarType9, \ + varName9) \ + DEFINE_TRACELOGGING_EVENT_PARAM9( \ + EventId, \ + VarType1, \ + varName1, \ + VarType2, \ + varName2, \ + VarType3, \ + varName3, \ + VarType4, \ + varName4, \ + VarType5, \ + varName5, \ + VarType6, \ + varName6, \ + VarType7, \ + varName7, \ + VarType8, \ + varName8, \ + VarType9, \ + varName9, \ + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA), \ + TelemetryPrivacyDataTag(PrivacyTag)) #define DEFINE_COMPLIANT_CRITICAL_DATA_EVENT_CV(EventId, PrivacyTag) \ DEFINE_TRACELOGGING_EVENT_CV(EventId, TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA), TelemetryPrivacyDataTag(PrivacyTag)) diff --git a/include/wil/coroutine.h b/include/wil/coroutine.h index d2f50c7ec..6bf868321 100644 --- a/include/wil/coroutine.h +++ b/include/wil/coroutine.h @@ -328,7 +328,7 @@ struct result_holder result_holder(result_holder const&) = delete; void operator=(result_holder const&) = delete; - ~result_holder() noexcept(false) + ~result_holder() noexcept { if (restricted_error && g_pfnDestroyRestrictedErrorInformation) { @@ -342,10 +342,9 @@ struct result_holder result.wrap.~result_wrapper(); break; case result_status::error: - // Rethrow unobserved exception. Delete this line to - // discard unobserved exceptions. - if (result.error) - std::rethrow_exception(result.error); + // Discard unobserved exception. There is nowhere to report it, + // and abandonment might validly happen if an exception occurs + // before the owner of the task can await it. result.error.~exception_ptr(); } } @@ -532,11 +531,25 @@ struct task_promise : promise_base this->emplace_value(wistd::forward(value)); } + // Workaround a bug in MSVC where it does not properly deduce the return type. + // Using a constraint is simpler and does not have the issue. +#if _HAS_CXX20 + + void return_value(T const& value) requires (!wistd::is_reference_v) + { + this->emplace_value(value); + } + +#else + template wistd::enable_if_t, Dummy> return_value(T const& value) { this->emplace_value(value); } + +#endif + }; template <> diff --git a/include/wil/resource.h b/include/wil/resource.h index 16621f1d8..ff47d66a3 100644 --- a/include/wil/resource.h +++ b/include/wil/resource.h @@ -23,6 +23,7 @@ #pragma warning(push) #pragma warning(disable : 26135 26110) // Missing locking annotation, Caller failing to hold lock #pragma warning(disable : 4714) // __forceinline not honored +#pragma warning(disable : 4820) // padding added after data member #ifndef __WIL_RESOURCE #define __WIL_RESOURCE @@ -1038,10 +1039,12 @@ If the type you're wrapping is a system type, you can share the code by declarin to [GitHub](https://github.com/microsoft/wil/). Otherwise, if the type is local to your project, declare it locally. @tparam ValueType: The type of array you want to manage. -@tparam ArrayDeleter: The type of the function to clean up the array. Takes one parameter of type T[] or T*. Return values are - ignored. This is called in the destructor and reset functions. +@tparam ArrayDeleter: The type of the function to clean up the array. Takes one parameter of type T[] or T*, and optionally a + second parameter of type SizeType specifying the number of elements being deleted. Return values are ignored. This is + called in the destructor and reset functions. @tparam ElementDeleter: The type of the function to clean up the array elements. Takes one parameter of type T. Return values are ignored. This is called in the destructor and reset functions. +@tparam SizeType: The type for holding the element count. Defaults to size_t. ~~~ void GetSomeArray(_Out_ size_t*, _Out_ NOTMYTYPE**); @@ -1057,12 +1060,12 @@ destroy(p); wil::unique_any_array_ptr myArray; GetSomeArray(myArray.size_address(), &myArray); ~~~ */ -template +template class unique_any_array_ptr { public: typedef ValueType value_type; - typedef size_t size_type; + typedef SizeType size_type; typedef ptrdiff_t difference_type; typedef ValueType* pointer; typedef const ValueType* const_pointer; @@ -1086,7 +1089,7 @@ class unique_any_array_ptr return *this; } - unique_any_array_ptr(pointer ptr, size_t size) WI_NOEXCEPT : m_ptr(ptr), m_size(size) + unique_any_array_ptr(pointer ptr, SizeType size) WI_NOEXCEPT : m_ptr(ptr), m_size(size) { } @@ -1232,13 +1235,27 @@ class unique_any_array_ptr if (m_ptr) { reset_array(ElementDeleter()); - ArrayDeleter()(m_ptr); + + // If the deleter has overloads that can accept either just the pointer, or the pointer and size, we prioritize the + // pointer-only version, since that is the version we initially supported. And if we can't invoke it with either + // parameter set, we'll allow the compiler to still try to invoke the pointer-only version and cause it to emit an + // error message that will tell the developer what the mismatch is. + if constexpr (wistd::is_invocable_v || + !wistd::is_invocable_v) + { + ArrayDeleter()(m_ptr); + } + else + { + ArrayDeleter()(m_ptr, m_size); + } + m_ptr = nullptr; m_size = size_type{}; } } - void reset(pointer ptr, size_t size) WI_NOEXCEPT + void reset(pointer ptr, SizeType size) WI_NOEXCEPT { reset(); m_ptr = ptr; @@ -1394,9 +1411,9 @@ namespace details } // namespace details /// @endcond -template +template using unique_array_ptr = - unique_any_array_ptr::type, ArrayDeleter, typename details::element_traits::deleter>; + unique_any_array_ptr::type, ArrayDeleter, typename details::element_traits::deleter, SizeType>; /** Adapter for single-parameter 'free memory' for `wistd::unique_ptr`. This struct provides a standard wrapper for calling a platform function to deallocate memory held by a @@ -2778,6 +2795,434 @@ inline bool handle_wait(HANDLE hEvent, DWORD dwMilliseconds = INFINITE, BOOL bAl return (status == WAIT_OBJECT_0); } +/** Functions to wait for multiple synchronization objects (handles). + +wait_all(), wait_any() and their _fastfail and _nothrow variants, provide a convenient way to wait for +multiple synchronization objects to be signaled. wait_all() waits until all objects are signaled and +wait_any() waits until any object is signaled. + +Both functions accept any combination of: + +- Raw HANDLE values +- WIL smart handle types (unique_handle, unique_event, etc) +- WRL handle wrappers (Mutex, Semaphore, etc) +- Any type that provides access to an underlying HANDLE through .get() or .Get() +- Any type that provides an overload of the get_object_handle() function returning a HANDLE + +The functions return a wait_result or wait_result_alertable object containing details about the wait +operation. + +The examples below try to touch on all possible uses of wait_all() and wait_any(): + +@code +wil::unique_event evt1; +wil::unique_event evt2; + +// Wait for all events to be signaled, timeout after 1 second +auto result = wil::wait_all(1000, evt1, evt2); +if (result) { + // Abandoned objects can be checked via result.abandoned(). Abandoned objects are signaled and + // wait_all/wait_any will return success for those. + if (result.abandoned()) { + // Handle abandoned object at result.index() + } else { + // All events signaled + } +} else if (result.timed_out()) { + // Handle timeout +} + +// Wait for any event to be signaled with no timeout +wil::unique_mutex mtx1; +auto result = wil::wait_any_failfast(evt1, evt2, mtx1); +if (result) { + // Abandoned objects can be checked via result.abandoned(). Abandoned objects are signaled and + // wait_all/wait_any will return success for those. + if (result.abandoned()) { + // Handle abandoned object at result.index() + } else { + // The signaled object is identified by result.index(), representing the N'th object + // in the argument list (0-based), i.e, evt1 is index 0, evt2 is index 1 and mtx1 is index 2. + } +} + +// Using an alertable wait (usage of wait traits template argument is required) and showing how to +// add support for custom types in the wait_all/wait_any functions. +// Please see the documentation for WaitForMultipleObjectsEx (specifically the bAlertable parameter) +// at https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjectsex +// for more details about alertable waits. +struct CustomType +{ + [...] + HANDLE get_handle() const WI_NOEXCEPT + { + // Return the underlying HANDLE for this custom type + return m_handle; + } + + HANDLE m_handle; +}; + +// Provide a get_object_handle() overload so CustomType can participate in wait_all/wait_any +HANDLE get_object_handle(const CustomType& custom) WI_NOEXCEPT +{ + return custom.get_handle(); +} + +CustomType custom1{...}; +HANDLE h1 = CreateEvent(...); +wil::unique_event h2{...}; +wil::unique_semaphore h3{...}; +[...] +auto result = wil::wait_all_nothrow(custom1, h1, h2, h3); +if (result) { + // All handles signaled or abandoned (which are also considered signaled). More granular checks + // can be performed with result.signaled() and result.abandoned() +} else if (result.alerted()) { + // Wait was interrupted by APC +} +@endcode + +@param timeout_milliseconds How long to wait in milliseconds. If using one of the overloads without +a timeout, INFINITE is used internally. +@param objects One or more handles/objects to wait on (max MAXIMUM_WAIT_OBJECTS) +@return A wait_result or wait_result_alertable containing the wait status +*/ + +struct wait_result +{ + constexpr wait_result(DWORD status = WAIT_FAILED) WI_NOEXCEPT : m_status(status) + { + if (signaled() || abandoned()) + { + m_hr = S_OK; + } + else if (timed_out()) + { + m_hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + } + } + + // This constructor is used for failure cases (WAIT_FAILED) only! + constexpr wait_result(HRESULT hr) WI_NOEXCEPT : m_status(WAIT_FAILED), m_hr(hr) + { + __FAIL_FAST_ASSERT__(FAILED(hr)); + } + + WI_NODISCARD constexpr bool signaled() const WI_NOEXCEPT + { + // status should be between WAIT_OBJECT_0 (inclusive) and WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS + // (exclusive). However, because WAIT_OBJECT_0 is 0 and since status is a DWORD (unsigned), + // it will always be >= WAIT_OBJECT_0. Adding that check would actually cause a compiler error. + return m_status < (WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS); + } + + WI_NODISCARD constexpr bool abandoned() const WI_NOEXCEPT + { + return ((m_status >= WAIT_ABANDONED_0) && (m_status < WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS)); + } + + WI_NODISCARD constexpr explicit operator bool() const WI_NOEXCEPT + { + return signaled() || abandoned(); + } + + WI_NODISCARD constexpr bool timed_out() const WI_NOEXCEPT + { + return m_status == WAIT_TIMEOUT; + } + + WI_NODISCARD constexpr DWORD index() const WI_NOEXCEPT + { + if (signaled()) + { + return m_status - WAIT_OBJECT_0; + } + + if (abandoned()) + { + return m_status - WAIT_ABANDONED_0; + } + + // index() should only be called if the object was signaled or abandoned. + FAIL_FAST(); + + // Unreachable, but avoids compiler errors + return static_cast(-1); + } + + WI_NODISCARD constexpr DWORD status() const WI_NOEXCEPT + { + return m_status; + } + + WI_NODISCARD constexpr HRESULT hresult() const WI_NOEXCEPT + { + return m_hr; + } + +protected: + DWORD m_status = WAIT_FAILED; + HRESULT m_hr = S_FALSE; +}; + +struct wait_result_alertable : wait_result +{ + // These constructors are needed by wait_for_multiple_objects_ex() in order to have an homogeneous + // return type. wait_for_multiple_objects_ex() needs to be able to construct a wait_result or + // wait_result_alertable from a DWORD or HRESULT. The default argument values allow us + // to default construct a wait_result_alertable object. + constexpr wait_result_alertable(DWORD status = WAIT_FAILED) WI_NOEXCEPT : wait_result(status) + { + if (alerted()) + { + m_hr = HRESULT_FROM_WIN32(ERROR_ALERTED); + } + } + + // This constructor is used for failure cases (WAIT_FAILED) only! + constexpr wait_result_alertable(HRESULT hr) WI_NOEXCEPT : wait_result(hr) + { + } + + WI_NODISCARD constexpr bool alerted() const WI_NOEXCEPT + { + return m_status == WAIT_IO_COMPLETION; + } +}; + +// Wait traits + +struct wait_traits +{ + using result_type = wait_result; + constexpr static BOOL is_alertable = FALSE; +}; + +struct wait_alertable_traits +{ + using result_type = wait_result_alertable; + constexpr static BOOL is_alertable = TRUE; +}; + +namespace details +{ + template + struct wait_all_traits : alertable_policy_traits + { + constexpr static BOOL is_waiting_for_all = TRUE; + }; + + template + struct wait_any_traits : alertable_policy_traits + { + constexpr static BOOL is_waiting_for_all = FALSE; + }; + + template + inline auto wait_for_multiple_objects_ex(const HANDLE* lpHandles, DWORD nCount, DWORD dwMilliseconds) + { + __FAIL_FAST_ASSERT__(lpHandles != nullptr); + __FAIL_FAST_ASSERT__(nCount > 0); + __FAIL_FAST_ASSERT__(nCount <= MAXIMUM_WAIT_OBJECTS); + + using wait_result_type = typename wait_traits::result_type; + + const DWORD status = + ::WaitForMultipleObjectsEx(nCount, lpHandles, wait_traits::is_waiting_for_all, dwMilliseconds, wait_traits::is_alertable); + if (status == WAIT_FAILED) + { + const auto hr = HRESULT_FROM_WIN32(::GetLastError()); + err_policy::HResult(hr); + return wait_result_type{hr}; + } + + return wait_result_type{status}; + } + + // This section implements tag dispatching and SFINAE to detect types that can supply a HANDLE + // value. It handles these cases: + // + // 1. A raw HANDLE value itself + // 2. Objects with a .get() method returning HANDLE + // 3. Objects with a .Get() method returning HANDLE + // + // Additional type patterns can be supported by adding overloads here or in client code. + + // Priority tag used for tag dispatching to determine which method to invoke. + // Higher numerical priorities take precedence. If SFINAE excludes a higher priority, + // the next lower priority is considered as a match. + template + struct priority_tag : priority_tag + { + }; + + template <> + struct priority_tag<0> + { + }; + + // Overload for .get() + template + constexpr auto get_object_handle_impl(const T& object, priority_tag<3>) + -> wistd::enable_if_t, HANDLE> + { + return object.get(); + } + + // Overload for .Get() + template + constexpr auto get_object_handle_impl(const T& object, priority_tag<2>) + -> wistd::enable_if_t, HANDLE> + { + return object.Get(); + } + + // Overload for HANDLE itself + constexpr HANDLE get_object_handle_impl(HANDLE handle, priority_tag<1>) + { + return handle; + } + + // Catch-all fallback to present a nicer error message + template + constexpr HANDLE get_object_handle_impl(const T&, priority_tag<0>) + { + static_assert( + sizeof(T) == 0, + "get_object_handle: unsupported type T. Cannot extract HANDLE from T. " + "Consider adding a new get_object_handle overload for type T."); + return nullptr; + } + + template + constexpr HANDLE get_object_handle(const T& object) + { + return get_object_handle_impl(object, priority_tag<3>{}); + } + + template + auto wait_for_multiple_objects(DWORD timeout_milliseconds, const TObjects&... objects) + { + constexpr auto object_count = sizeof...(objects); + static_assert(object_count > 1, "wait_for_multiple_objects expects at least 2 waitable objects"); + static_assert(object_count <= MAXIMUM_WAIT_OBJECTS, "wait_for_multiple_objects expects no more than MAXIMUM_WAIT_OBJECTS waitable objects"); + + const HANDLE handles[] = {get_object_handle(objects)...}; + return wait_for_multiple_objects_ex(handles, ARRAYSIZE(handles), timeout_milliseconds); + } +} // namespace details + +// +// wait_all variants +// + +// exception throwing versions + +#ifdef WIL_ENABLE_EXCEPTIONS +template +auto wait_all(DWORD timeout_milliseconds, TObjects&&... objects) +{ + return details::wait_for_multiple_objects, err_exception_policy>( + timeout_milliseconds, wistd::forward(objects)...); +} + +template +auto wait_all(TObjects&&... objects) +{ + return details::wait_for_multiple_objects, err_exception_policy>( + INFINITE, wistd::forward(objects)...); +} +#endif // WIL_ENABLE_EXCEPTIONS + +// fail_fast versions + +template +auto wait_all_failfast(DWORD timeout_milliseconds, TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_failfast_policy>( + timeout_milliseconds, wistd::forward(objects)...); +} + +template +auto wait_all_failfast(TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_failfast_policy>( + INFINITE, wistd::forward(objects)...); +} + +// returncode versions + +template +auto wait_all_nothrow(DWORD timeout_milliseconds, TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_returncode_policy>( + timeout_milliseconds, wistd::forward(objects)...); +} + +template +auto wait_all_nothrow(TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_returncode_policy>( + INFINITE, wistd::forward(objects)...); +} + +// +// wait_any variants +// + +// exception throwing versions + +#ifdef WIL_ENABLE_EXCEPTIONS +template +auto wait_any(DWORD timeout_milliseconds, TObjects&&... objects) +{ + return details::wait_for_multiple_objects, err_exception_policy>( + timeout_milliseconds, wistd::forward(objects)...); +} + +template +auto wait_any(TObjects&&... objects) +{ + return details::wait_for_multiple_objects, err_exception_policy>( + INFINITE, wistd::forward(objects)...); +} +#endif // WIL_ENABLE_EXCEPTIONS + +// fail_fast versions + +template +auto wait_any_failfast(DWORD timeout_milliseconds, TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_failfast_policy>( + timeout_milliseconds, wistd::forward(objects)...); +} + +template +auto wait_any_failfast(TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_failfast_policy>( + INFINITE, wistd::forward(objects)...); +} + +// returncode versions + +template +auto wait_any_nothrow(DWORD timeout_milliseconds, TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_returncode_policy>( + timeout_milliseconds, wistd::forward(objects)...); +} + +template +auto wait_any_nothrow(TObjects&&... objects) WI_NOEXCEPT +{ + return details::wait_for_multiple_objects, err_returncode_policy>( + INFINITE, wistd::forward(objects)...); +} + +// Event support + enum class EventOptions { None = 0x0, diff --git a/include/wil/result.h b/include/wil/result.h index 7dd1b990d..e4f873659 100644 --- a/include/wil/result.h +++ b/include/wil/result.h @@ -32,9 +32,11 @@ #error This header is not supported in kernel-mode. #endif -// The updated behavior of running init-list ctors during placement new is proper & correct, disable the warning that requests developers verify they want it #pragma warning(push) +// The updated behavior of running init-list ctors during placement new is proper & correct, disable the warning that requests developers verify they want it #pragma warning(disable : 4351) +// remove this after fixing - volatile access of '' is subject to /volatile: setting; consider using __iso_volatile_load/store intrinsic functions +#pragma warning(disable : 4746) namespace wil { diff --git a/include/wil/result_macros.h b/include/wil/result_macros.h index a167678da..95e859eeb 100644 --- a/include/wil/result_macros.h +++ b/include/wil/result_macros.h @@ -319,11 +319,11 @@ WI_ODR_PRAGMA("WIL_FreeMemory", "0") #define __R_INFO_ONLY(CODE) \ __R_IF_CALLERADDRESS(_ReturnAddress() __R_IF_COMMA) \ __R_IF_LINE(__R_LINE_VALUE) \ - __R_IF_FILE(__R_COMMA __R_FILE_VALUE) __R_IF_FUNCTION(__R_COMMA __FUNCTION__) __R_IF_CODE(__R_COMMA CODE) + __R_IF_FILE(__R_COMMA __R_FILE_VALUE) __R_IF_FUNCTION(__R_COMMA __FUNCTION__) __R_IF_CODE(__R_COMMA CODE) // NOLINT(bugprone-lambda-function-name) #define __R_INFO(CODE) __R_INFO_ONLY(CODE) __R_IF_TRAIL_COMMA #define __R_INFO_NOFILE_ONLY(CODE) \ __R_IF_CALLERADDRESS(_ReturnAddress() __R_IF_COMMA) \ - __R_IF_LINE(__R_LINE_VALUE) __R_IF_FILE(__R_COMMA "wil") __R_IF_FUNCTION(__R_COMMA __FUNCTION__) __R_IF_CODE(__R_COMMA CODE) + __R_IF_LINE(__R_LINE_VALUE) __R_IF_FILE(__R_COMMA "wil") __R_IF_FUNCTION(__R_COMMA __FUNCTION__) __R_IF_CODE(__R_COMMA CODE) // NOLINT(bugprone-lambda-function-name) #define __R_INFO_NOFILE(CODE) __R_INFO_NOFILE_ONLY(CODE) __R_IF_TRAIL_COMMA #define __R_FN_PARAMS_ONLY \ __R_IF_CALLERADDRESS(void* callerReturnAddress __R_IF_COMMA) \ @@ -1410,8 +1410,10 @@ WI_ODR_PRAGMA("WIL_FreeMemory", "0") #define CATCH_LOG_RETURN() \ catch (...) \ { \ - __pragma(warning(suppress : 4297)); \ + __pragma(warning(push)) \ + __pragma(warning(disable : 4297)) \ LOG_CAUGHT_EXCEPTION(); \ + __pragma(warning(pop)) \ return; \ } #define CATCH_LOG_MSG(fmt, ...) \ @@ -1423,8 +1425,10 @@ WI_ODR_PRAGMA("WIL_FreeMemory", "0") #define CATCH_LOG_RETURN_MSG(fmt, ...) \ catch (...) \ { \ - __pragma(warning(suppress : 4297)); \ + __pragma(warning(push)) \ + __pragma(warning(disable : 4297)) \ LOG_CAUGHT_EXCEPTION_MSG(fmt, ##__VA_ARGS__); \ + __pragma(warning(pop)) \ return; \ } #define CATCH_FAIL_FAST() \ diff --git a/include/wil/result_originate.h b/include/wil/result_originate.h index 4d36ea7ec..23d3ae009 100644 --- a/include/wil/result_originate.h +++ b/include/wil/result_originate.h @@ -121,11 +121,13 @@ namespace details /// @endcond } // namespace wil +#ifndef RESULT_SUPPRESS_STATIC_INITIALIZERS // Automatically call RoOriginateError upon error origination by including this file WI_HEADER_INITIALIZATION_FUNCTION(ResultStowedExceptionInitialize, [] { ::wil::SetOriginateErrorCallback(::wil::details::RaiseRoOriginateOnWilExceptions); ::wil::SetFailfastWithContextCallback(::wil::details::FailfastWithContextCallback); return 1; }) +#endif // RESULT_SUPPRESS_STATIC_INITIALIZERS #endif // __WIL_RESULT_ORIGINATE_INCLUDED diff --git a/include/wil/stl.h b/include/wil/stl.h index d2b11babc..a79816aa1 100644 --- a/include/wil/stl.h +++ b/include/wil/stl.h @@ -226,6 +226,52 @@ inline namespace literals #endif // __cpp_lib_string_view >= 201606L +#if __WI_LIBCPP_STD_VER >= 17 +// This is a helper that allows one to construct a functor that has an overloaded operator() +// composed from the operator()s of multiple lambdas. It is most useful as the "visitor" for a +// std::visit call on a std::variant. A lambda for each type in the variant, and optionally one +// generic lambda, can be provided to perform compile time visitation of the std::variant. +// +// Example: +// std::variant theVariant; +// std::visit(wil::overloaded{ +// [](int theInt) +// { +// // Handle int. +// }, +// [](double theDouble) +// { +// // Handle double. +// }, +// [](auto boolOrVoidPtr) +// { +// // This will receive all the remaining types. Alternatively, handle each type with +// // a lambda that accepts that type. If all types are not handled, you get a +// // compile-time error, which makes std::visit superior to an if-else ladder that +// // tries to handle each type in the variant. +// }}, +// theVariant); +// +template +struct overloaded final : T... +{ + using T::operator()...; + + // This allows one to use the () syntax to construct the visitor, instead of {}. Both are + // equivalent, and the choice ultimately boils down to preference of style. + template + constexpr explicit overloaded(Fs&&... fs) : + T{std::forward(fs)}... + { + } +}; + +// Deduction guide to aid CTAD. +template +overloaded(T...) -> overloaded; + +#endif // __WI_LIBCPP_STD_VER >= 17 + } // namespace wil #endif // WIL_ENABLE_EXCEPTIONS diff --git a/include/wil/win32_helpers.h b/include/wil/win32_helpers.h index 88d5d6113..b576fafa5 100644 --- a/include/wil/win32_helpers.h +++ b/include/wil/win32_helpers.h @@ -38,7 +38,7 @@ #endif /// @cond -#if __cpp_lib_bit_cast >= 201806L +#if WIL_USE_STL && (__cpp_lib_bit_cast >= 201806L) #define __WI_CONSTEXPR_BIT_CAST constexpr #else #define __WI_CONSTEXPR_BIT_CAST inline @@ -209,7 +209,7 @@ namespace filetime { constexpr unsigned long long to_int64(const FILETIME& ft) WI_NOEXCEPT { -#if __cpp_lib_bit_cast >= 201806L +#if WIL_USE_STL && (__cpp_lib_bit_cast >= 201806L) return std::bit_cast(ft); #else // Cannot reinterpret_cast FILETIME* to unsigned long long* @@ -220,7 +220,7 @@ namespace filetime __WI_CONSTEXPR_BIT_CAST FILETIME from_int64(unsigned long long i64) WI_NOEXCEPT { -#if __cpp_lib_bit_cast >= 201806L +#if WIL_USE_STL && (__cpp_lib_bit_cast >= 201806L) return std::bit_cast(i64); #else static_assert(sizeof(i64) == sizeof(FILETIME), "sizes don't match"); @@ -651,36 +651,71 @@ string_type QueryFullProcessImageNameW(HANDLE processHandle = GetCurrentProcess( THROW_IF_FAILED((wil::QueryFullProcessImageNameW(processHandle, flags, result))); return result; } +#endif // WIL_ENABLE_EXCEPTIONS #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) -// Lookup a DWORD value under HKLM\...\Image File Execution Options\ -inline DWORD GetCurrentProcessExecutionOption(PCWSTR valueName, DWORD defaultValue = 0) +namespace details { - auto filePath = wil::GetModuleFileNameW(); - if (auto lastSlash = wcsrchr(filePath.get(), L'\\')) + // Lookup a DWORD value under HKLM\...\Image File Execution Options\ + inline HRESULT GetCurrentProcessExecutionOptionNoThrow(PCWSTR valueName, DWORD defaultValue, DWORD* result) { - const auto fileName = lastSlash + 1; - auto keyPath = wil::str_concat( - LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\)", fileName); - DWORD value{}, sizeofValue = sizeof(value); - if (::RegGetValueW( - HKEY_LOCAL_MACHINE, - keyPath.get(), - valueName, + *result = defaultValue; + + wil::unique_cotaskmem_string filePath; + RETURN_IF_FAILED(wil::GetModuleFileNameW(nullptr, filePath)); + if (auto lastSlash = wcsrchr(filePath.get(), L'\\')) + { + const auto fileName = lastSlash + 1; + wil::unique_cotaskmem_string keyPath; + RETURN_IF_FAILED(wil::str_concat_nothrow( + keyPath, LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\)", fileName)); + DWORD value{}, sizeofValue = sizeof(value); + if (::RegGetValueW( + HKEY_LOCAL_MACHINE, + keyPath.get(), + valueName, #ifdef RRF_SUBKEY_WOW6464KEY - RRF_RT_REG_DWORD | RRF_SUBKEY_WOW6464KEY, + RRF_RT_REG_DWORD | RRF_SUBKEY_WOW6464KEY, #else - RRF_RT_REG_DWORD, + RRF_RT_REG_DWORD, #endif - nullptr, - &value, - &sizeofValue) == ERROR_SUCCESS) - { - return value; + nullptr, + &value, + &sizeofValue) == ERROR_SUCCESS) + { + *result = value; + } } + return S_OK; } - return defaultValue; +} // namespace details + +#ifdef WIL_ENABLE_EXCEPTIONS +inline DWORD GetCurrentProcessExecutionOption(PCWSTR valueName, DWORD defaultValue = 0) +{ + DWORD result{}; + THROW_IF_FAILED(details::GetCurrentProcessExecutionOptionNoThrow(valueName, defaultValue, &result)); + return result; +} +#endif // WIL_ENABLE_EXCEPTIONS + +// No easy return mechanism for err_returncode_policy so skipping it. +inline DWORD GetCurrentProcessExecutionOptionNoThrow(PCWSTR valueName, DWORD defaultValue = 0) +{ + DWORD result{}; + if (FAILED(details::GetCurrentProcessExecutionOptionNoThrow(valueName, defaultValue, &result))) + { + return defaultValue; + } + return result; +} + +inline DWORD GetCurrentProcessExecutionOptionFailFast(PCWSTR valueName, DWORD defaultValue = 0) +{ + DWORD result{}; + FAIL_FAST_IF_FAILED(details::GetCurrentProcessExecutionOptionNoThrow(valueName, defaultValue, &result)); + return result; } #ifndef DebugBreak // Some code defines 'DebugBreak' to garbage to force build breaks in release builds @@ -694,31 +729,58 @@ inline DWORD GetCurrentProcessExecutionOption(PCWSTR valueName, DWORD defaultVal // missing or 0 -> don't break // 1 -> wait for the debugger, continue execution once it is attached // 2 -> wait for the debugger, break here once attached. -inline void WaitForDebuggerPresent(bool checkRegistryConfig = true) +namespace details { - for (;;) + template + inline void WaitForDebuggerPresent(bool checkRegistryConfig) { - auto configValue = checkRegistryConfig ? GetCurrentProcessExecutionOption(L"WaitForDebuggerPresent") : 1; - if (configValue == 0) + for (;;) { - return; // not configured, don't wait - } + DWORD configValue{1}; + if (checkRegistryConfig) + { + // err_returncode_policy will continue running after this line on failure. The default value of zero + // will apply in that case so we will still behave reasonably. + error_policy::HResult(details::GetCurrentProcessExecutionOptionNoThrow(L"WaitForDebuggerPresent", 0, &configValue)); + } - if (IsDebuggerPresent()) - { - if (configValue == 2) + if (configValue == 0) { - DebugBreak(); // debugger attached, SHIFT+F11 to return to the caller + return; // not configured, don't wait } - return; // debugger now attached, continue executing + + if (IsDebuggerPresent()) + { + if (configValue == 2) + { + DebugBreak(); // debugger attached, SHIFT+F11 to return to the caller + } + return; // debugger now attached, continue executing + } + Sleep(500); } - Sleep(500); } +} // namespace details + +#ifdef WIL_ENABLE_EXCEPTIONS +inline void WaitForDebuggerPresent(bool checkRegistryConfig = true) +{ + details::WaitForDebuggerPresent(checkRegistryConfig); } -#endif -#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) +#endif // WIL_ENABLE_EXCEPTIONS -#endif +inline void WaitForDebuggerPresentNoThrow(bool checkRegistryConfig = true) +{ + (void)details::WaitForDebuggerPresent(checkRegistryConfig); +} + +inline void WaitForDebuggerPresentFailFast(bool checkRegistryConfig = true) +{ + details::WaitForDebuggerPresent(checkRegistryConfig); +} + +#endif // DebugBreak +#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) /** Retrieve the HINSTANCE for the current DLL or EXE using this symbol that the linker provides for every module. This avoids the need for a global HINSTANCE variable diff --git a/include/wil/wistd_type_traits.h b/include/wil/wistd_type_traits.h index 081718852..ea3dbcd65 100644 --- a/include/wil/wistd_type_traits.h +++ b/include/wil/wistd_type_traits.h @@ -1189,9 +1189,14 @@ using type_identity_t = typename type_identity<_Tp>::type; // is_signed +__WI_PUSH_WARNINGS +__WI_MSVC_DISABLE_WARNING(4296) // error C4296: '<': expression is always false + template ::value> struct __libcpp_is_signed_impl : public __WI_LIBCPP_BOOL_CONSTANT(_Tp(-1) < _Tp(0)){}; +__WI_POP_WARNINGS + template struct __libcpp_is_signed_impl<_Tp, false> : public true_type { From d01200331c04cd9f9b19e5e0679bc0033ef1495b Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 16 Jan 2026 16:02:30 -0800 Subject: [PATCH 2/6] Run clang-format on changes --- include/wil/coroutine.h | 4 ++-- include/wil/resource.h | 5 ++--- include/wil/result_macros.h | 14 ++++---------- include/wil/stl.h | 5 ++--- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/include/wil/coroutine.h b/include/wil/coroutine.h index 6bf868321..7381fd636 100644 --- a/include/wil/coroutine.h +++ b/include/wil/coroutine.h @@ -535,7 +535,8 @@ struct task_promise : promise_base // Using a constraint is simpler and does not have the issue. #if _HAS_CXX20 - void return_value(T const& value) requires (!wistd::is_reference_v) + void return_value(T const& value) + requires(!wistd::is_reference_v) { this->emplace_value(value); } @@ -549,7 +550,6 @@ struct task_promise : promise_base } #endif - }; template <> diff --git a/include/wil/resource.h b/include/wil/resource.h index cdcfafbac..8f97e3fdf 100644 --- a/include/wil/resource.h +++ b/include/wil/resource.h @@ -23,7 +23,7 @@ #pragma warning(push) #pragma warning(disable : 26135 26110) // Missing locking annotation, Caller failing to hold lock #pragma warning(disable : 4714) // __forceinline not honored -#pragma warning(disable : 4820) // padding added after data member +#pragma warning(disable : 4820) // padding added after data member #ifndef __WIL_RESOURCE #define __WIL_RESOURCE @@ -1248,8 +1248,7 @@ class unique_any_array_ptr // pointer-only version, since that is the version we initially supported. And if we can't invoke it with either // parameter set, we'll allow the compiler to still try to invoke the pointer-only version and cause it to emit an // error message that will tell the developer what the mismatch is. - if constexpr (wistd::is_invocable_v || - !wistd::is_invocable_v) + if constexpr (wistd::is_invocable_v || !wistd::is_invocable_v) { ArrayDeleter()(m_ptr); } diff --git a/include/wil/result_macros.h b/include/wil/result_macros.h index 7406fc96c..6da7ed2a7 100644 --- a/include/wil/result_macros.h +++ b/include/wil/result_macros.h @@ -1426,11 +1426,8 @@ WI_ODR_PRAGMA("WIL_FreeMemory", "0") #define CATCH_LOG_RETURN() \ catch (...) \ { \ - __pragma(warning(push)) \ - __pragma(warning(disable : 4297)) \ - LOG_CAUGHT_EXCEPTION(); \ - __pragma(warning(pop)) \ - return; \ + __pragma(warning(push)) __pragma(warning(disable : 4297)) LOG_CAUGHT_EXCEPTION(); \ + __pragma(warning(pop)) return; \ } #define CATCH_LOG_MSG(fmt, ...) \ catch (...) \ @@ -1441,11 +1438,8 @@ WI_ODR_PRAGMA("WIL_FreeMemory", "0") #define CATCH_LOG_RETURN_MSG(fmt, ...) \ catch (...) \ { \ - __pragma(warning(push)) \ - __pragma(warning(disable : 4297)) \ - LOG_CAUGHT_EXCEPTION_MSG(fmt, ##__VA_ARGS__); \ - __pragma(warning(pop)) \ - return; \ + __pragma(warning(push)) __pragma(warning(disable : 4297)) LOG_CAUGHT_EXCEPTION_MSG(fmt, ##__VA_ARGS__); \ + __pragma(warning(pop)) return; \ } #define CATCH_FAIL_FAST() \ catch (...) \ diff --git a/include/wil/stl.h b/include/wil/stl.h index 32830fefc..33434875a 100644 --- a/include/wil/stl.h +++ b/include/wil/stl.h @@ -319,9 +319,8 @@ struct overloaded final : T... // This allows one to use the () syntax to construct the visitor, instead of {}. Both are // equivalent, and the choice ultimately boils down to preference of style. - template - constexpr explicit overloaded(Fs&&... fs) : - T{std::forward(fs)}... + template + constexpr explicit overloaded(Fs&&... fs) : T{std::forward(fs)}... { } }; From da0652044789b4a15958c9c7e45e9efff09ba48a Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Tue, 20 Jan 2026 09:35:44 -0800 Subject: [PATCH 3/6] Sync build break fix --- include/wil/resource.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/wil/resource.h b/include/wil/resource.h index 8f97e3fdf..e965c7286 100644 --- a/include/wil/resource.h +++ b/include/wil/resource.h @@ -187,7 +187,8 @@ namespace details } __forceinline static bool is_valid(pointer_storage value) WI_NOEXCEPT { - return (static_cast(value) != invalid_value()); + // NOLINTNEXTLINE(performance-no-int-to-ptr): There's no provenance concealment because this isn't a valid pointer + return (static_cast(value) != (pointer)invalid); } }; From fe42e3c1743f71e16b2c44b6b2ffa1a53f978ac8 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Tue, 20 Jan 2026 14:14:16 -0800 Subject: [PATCH 4/6] Another round of build fixes --- include/wil/resource.h | 32 ++++++++++++++++---------------- include/wil/result_macros.h | 4 ++++ include/wil/stl.h | 4 ++-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/wil/resource.h b/include/wil/resource.h index e965c7286..a87f398f9 100644 --- a/include/wil/resource.h +++ b/include/wil/resource.h @@ -1870,7 +1870,7 @@ namespace details inline string_view_t view_from_string(PCWSTR str) { - return string_view_t{str, str ? wcslen(str) : 0}; + return string_view_t{str, str ? wcslen(str) : 0u}; } template @@ -4314,11 +4314,11 @@ class event_watcher_t : public storage_t template event_watcher_t( unique_any_t, from_err_policy>>&& eventHandle, - event_watcher_options flags, + event_watcher_options optFlags, wistd::function&& callback) { static_assert(wistd::is_same::value, "this constructor requires exceptions or fail fast; use the create method"); - create(wistd::move(eventHandle), flags, wistd::move(callback)); + create(wistd::move(eventHandle), optFlags, wistd::move(callback)); } template @@ -4329,10 +4329,10 @@ class event_watcher_t : public storage_t { } - event_watcher_t(_In_ HANDLE eventHandle, event_watcher_options flags, wistd::function&& callback) + event_watcher_t(_In_ HANDLE eventHandle, event_watcher_options optFlags, wistd::function&& callback) { static_assert(wistd::is_same::value, "this constructor requires exceptions or fail fast; use the create method"); - create(eventHandle, flags, wistd::move(callback)); + create(eventHandle, optFlags, wistd::move(callback)); } event_watcher_t(_In_ HANDLE eventHandle, wistd::function&& callback) : @@ -4340,10 +4340,10 @@ class event_watcher_t : public storage_t { } - event_watcher_t(event_watcher_options flags, wistd::function&& callback) + event_watcher_t(event_watcher_options optFlags, wistd::function&& callback) { static_assert(wistd::is_same::value, "this constructor requires exceptions or fail fast; use the create method"); - create(flags, wistd::move(callback)); + create(optFlags, wistd::move(callback)); } event_watcher_t(wistd::function&& callback) : event_watcher_t(event_watcher_options::none, wistd::move(callback)) @@ -4353,10 +4353,10 @@ class event_watcher_t : public storage_t template result create( unique_any_t, event_err_policy>>&& eventHandle, - event_watcher_options flags, + event_watcher_options optFlags, wistd::function&& callback) { - return err_policy::HResult(create_take_hevent_ownership(eventHandle.release(), flags, wistd::move(callback))); + return err_policy::HResult(create_take_hevent_ownership(eventHandle.release(), optFlags, wistd::move(callback))); } template @@ -4374,7 +4374,7 @@ class event_watcher_t : public storage_t } // Creates the event that you will be watching. - result create(event_watcher_options flags, wistd::function&& callback) + result create(event_watcher_options optFlags, wistd::function&& callback) { unique_event_nothrow eventHandle; HRESULT hr = eventHandle.create(EventOptions::ManualReset); // auto-reset is supported too. @@ -4382,7 +4382,7 @@ class event_watcher_t : public storage_t { return err_policy::HResult(hr); } - return err_policy::HResult(create_take_hevent_ownership(eventHandle.release(), flags, wistd::move(callback))); + return err_policy::HResult(create_take_hevent_ownership(eventHandle.release(), optFlags, wistd::move(callback))); } // Input is an event handler that is duplicated into this class. @@ -4391,14 +4391,14 @@ class event_watcher_t : public storage_t return create(eventHandle, event_watcher_options::none, wistd::move(callback)); } - result create(_In_ HANDLE eventHandle, event_watcher_options flags, wistd::function&& callback) + result create(_In_ HANDLE eventHandle, event_watcher_options optFlags, wistd::function&& callback) { unique_event_nothrow ownedHandle; if (!DuplicateHandle(GetCurrentProcess(), eventHandle, GetCurrentProcess(), &ownedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { return err_policy::LastError(); } - return err_policy::HResult(create_take_hevent_ownership(ownedHandle.release(), flags, wistd::move(callback))); + return err_policy::HResult(create_take_hevent_ownership(ownedHandle.release(), optFlags, wistd::move(callback))); } // Provide access to the inner event and the very common SetEvent() method on it. @@ -4438,18 +4438,18 @@ class event_watcher_t : public storage_t // To avoid template expansion (if unique_event/unique_event_nothrow forms were used) this base // create function takes a raw handle and assumes its ownership, even on failure. - HRESULT create_take_hevent_ownership(_In_ HANDLE rawHandleOwnershipTaken, event_watcher_options flags, wistd::function&& callback) + HRESULT create_take_hevent_ownership(_In_ HANDLE rawHandleOwnershipTaken, event_watcher_options optFlags, wistd::function&& callback) { __FAIL_FAST_ASSERT__(rawHandleOwnershipTaken != nullptr); // invalid parameter unique_event_nothrow eventHandle(rawHandleOwnershipTaken); wistd::unique_ptr watcherState( - new (std::nothrow) details::event_watcher_state{wistd::move(callback), wistd::move(eventHandle), nullptr, flags}); + new (std::nothrow) details::event_watcher_state{wistd::move(callback), wistd::move(eventHandle), nullptr, optFlags}); RETURN_IF_NULL_ALLOC(watcherState); watcherState->m_threadPoolWait.reset(CreateThreadpoolWait(wait_callback, watcherState.get(), nullptr)); RETURN_LAST_ERROR_IF(!watcherState->m_threadPoolWait); storage_t::reset(watcherState.release()); // no more failures after this, pass ownership - if (WI_IsFlagClear(flags, event_watcher_options::manual_start)) + if (WI_IsFlagClear(optFlags, event_watcher_options::manual_start)) { start(); } diff --git a/include/wil/result_macros.h b/include/wil/result_macros.h index 6da7ed2a7..c59ac971f 100644 --- a/include/wil/result_macros.h +++ b/include/wil/result_macros.h @@ -38,8 +38,12 @@ /// @cond #if defined(_PREFAST_) +#ifdef __cplusplus #define __WI_ANALYSIS_ASSUME(_exp) _Analysis_assume_(static_cast(_exp)) #else +#define __WI_ANALYSIS_ASSUME(_exp) _Analysis_assume_(_exp) +#endif +#else #ifdef RESULT_DEBUG #define __WI_ANALYSIS_ASSUME(_exp) ((void)0) #else diff --git a/include/wil/stl.h b/include/wil/stl.h index 33434875a..fa9188cd6 100644 --- a/include/wil/stl.h +++ b/include/wil/stl.h @@ -211,12 +211,12 @@ class basic_zstring_view : public std::basic_string_view { } - template ::value && has_size::value>* = nullptr> + template ::value && has_size::value && std::is_same_v>* = nullptr> constexpr basic_zstring_view(TSrc const& src) noexcept : std::basic_string_view(src.c_str(), src.size()) { } - template ::value && !has_size::value>* = nullptr> + template ::value && !has_size::value && std::is_same_v>* = nullptr> constexpr basic_zstring_view(TSrc const& src) noexcept : std::basic_string_view(src.c_str()) { } From e7bf1cffe56becda499ed18629ae01fdf1ce5ce2 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Tue, 20 Jan 2026 14:19:44 -0800 Subject: [PATCH 5/6] Test fix --- tests/StlTests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/StlTests.cpp b/tests/StlTests.cpp index 4fbdfc021..4ae7678e6 100644 --- a/tests/StlTests.cpp +++ b/tests/StlTests.cpp @@ -235,6 +235,7 @@ TEST_CASE("StlTests::TestZWStringView", "[stl][zstring_view]") // Test constructing from a type that has a c_str() method only struct string_with_c_str { + using value_type = wchar_t; constexpr PCWSTR c_str() const { return L"hello"; From d11326622eeddd68c1c7a20557b978972ba4fd28 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Wed, 21 Jan 2026 10:30:30 -0800 Subject: [PATCH 6/6] Work around an unfortunate linker issue --- include/wil/stl.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/wil/stl.h b/include/wil/stl.h index fa9188cd6..86cc188c3 100644 --- a/include/wil/stl.h +++ b/include/wil/stl.h @@ -333,6 +333,9 @@ overloaded(T...) -> overloaded; } // namespace wil +// This suppression is a temporary workaround to allow libraries built with C++20 to link into binaries built with +// earlier standard versions such as C++17. This appears to be an issue even when this specialization goes unused +#ifndef WIL_SUPPRESS_STD_FORMAT_USE #if (__WI_LIBCPP_STD_VER >= 20) && WI_HAS_INCLUDE(, 1) // Assume present if C++20 #include template @@ -340,6 +343,7 @@ struct std::formatter, TChar> : std::formatter