[libcxx-commits] [libcxx] [libc++] Make isfinite/isinf/isnan/isnormal constexpr on Windows (PR #214360)

Victor Vianna via libcxx-commits libcxx-commits at lists.llvm.org
Thu Aug 6 03:46:22 PDT 2026


https://github.com/victorvianna updated https://github.com/llvm/llvm-project/pull/214360

>From 9844c21f50ee8948c0f27c0d0a6d1878874978d9 Mon Sep 17 00:00:00 2001
From: Victor Hugo Vianna Silva <victor.vianna10 at gmail.com>
Date: Wed, 5 Aug 2026 21:10:25 +0100
Subject: [PATCH 1/2] [libc++] Make the floating-point classification traits
 constexpr on Windows

P0533R9 makes the floating-point classification traits usable in constant
expressions in C++23. On Windows they were not: <cmath> forwarded them to
the UCRT's non-constexpr declarations.

The UCRT declares them in two shapes, which need different workarounds:

 - isfinite, isinf, isnan and isnormal are unconstrained function
   templates. The std::__math::__ucrt overloads that work around a
   separate UCRT bug for integer arguments were constrained on
   is_integral_v, so floating-point calls fell through to the UCRT.
   Constraining them on is_arithmetic_v and forwarding to std::__math
   covers both, using the same more-constrained tiebreak that the
   two-argument traits in that namespace already rely on.

 - signbit is a set of non-template overloads, which no template of ours
   can outrank. Declare ours with _LIBCPP_PREFERRED_OVERLOAD instead, the
   way <math.h> already handles fpclassify, keeping the previous template
   form as a fallback for when the attribute is unavailable.

Every test that would have caught this was UNSUPPORTED: windows. They all
pass there now, so drop the marker from the five std tests and from the
P0533R9 reference test.

Fixes #214358
---
 libcxx/docs/ReleaseNotes/24.rst               |  4 ++
 libcxx/include/__math/traits.h                | 60 ++++++++++++++-----
 libcxx/include/math.h                         |  5 +-
 .../c.math/constexpr-cxx23-clang.pass.cpp     |  3 -
 .../std/numerics/c.math/isfinite.pass.cpp     |  3 -
 .../test/std/numerics/c.math/isinf.pass.cpp   |  3 -
 .../test/std/numerics/c.math/isnan.pass.cpp   |  3 -
 .../std/numerics/c.math/isnormal.pass.cpp     |  3 -
 .../test/std/numerics/c.math/signbit.pass.cpp |  3 -
 9 files changed, 54 insertions(+), 33 deletions(-)

diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index e9a36ae94392f..cf13b8d1b568c 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -51,6 +51,10 @@ Implemented Papers
 Improvements and New Features
 -----------------------------
 
+- On Windows, ``std::isfinite``, ``std::isinf``, ``std::isnan``, ``std::isnormal`` and ``std::signbit`` are now usable
+  in constant expressions in C++23, as required by P0533R9. They previously resolved to the UCRT's non-``constexpr``
+  declarations (`Github <https://github.com/llvm/llvm-project/issues/214358>`__).
+
 Deprecations and Removals
 -------------------------
 
diff --git a/libcxx/include/__math/traits.h b/libcxx/include/__math/traits.h
index f32dc47ab1b1b..7d8db4685e3e4 100644
--- a/libcxx/include/__math/traits.h
+++ b/libcxx/include/__math/traits.h
@@ -25,9 +25,27 @@ namespace __math {
 
 // signbit
 
-// The universal C runtime (UCRT) in the WinSDK provides floating point overloads
-// for std::signbit(). By defining our overloads as templates, we can work around
-// this issue as templates are less preferred than non-template functions.
+// The universal C runtime (UCRT) in the WinSDK provides non-template floating point overloads for std::signbit(), and
+// a non-template function can't outrank another non-template function. We used to declare ours as templates so that
+// the UCRT's would win, but that leaves std::signbit non-constexpr in C++23 on Windows, which P0533R9 requires it to
+// be. _LIBCPP_PREFERRED_OVERLOAD makes ours win instead, the same way <math.h> handles fpclassify, which the UCRT
+// declares in exactly the same shape. Without the attribute we fall back to templates, keeping the old behavior.
+#ifdef _LIBCPP_PREFERRED_OVERLOAD
+[[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_PREFERRED_OVERLOAD bool signbit(float __x) _NOEXCEPT {
+  return __builtin_signbit(__x);
+}
+
+[[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI _LIBCPP_PREFERRED_OVERLOAD bool
+signbit(double __x) _NOEXCEPT {
+  return __builtin_signbit(__x);
+}
+
+[[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI _LIBCPP_PREFERRED_OVERLOAD bool
+signbit(long double __x) _NOEXCEPT {
+  return __builtin_signbit(__x);
+}
+#else
 template <class = void>
 [[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool signbit(float __x) _NOEXCEPT {
   return __builtin_signbit(__x);
@@ -42,6 +60,7 @@ template <class = void>
 [[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool signbit(long double __x) _NOEXCEPT {
   return __builtin_signbit(__x);
 }
+#endif
 
 template <class _A1, __enable_if_t<is_integral<_A1>::value, int> = 0>
 [[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool signbit(_A1 __x) _NOEXCEPT {
@@ -195,28 +214,41 @@ isunordered(_A1 __x, _A2 __y) _NOEXCEPT {
 
 #if defined(_LIBCPP_MSVCRT) && _LIBCPP_STD_VER >= 20
 namespace __ucrt {
+// The UCRT declares these as unconstrained, non-constexpr function templates, so it isn't only integer arguments that
+// need handling here: without a candidate of our own, floating-point calls resolve to the UCRT's templates too and
+// std::isfinite & friends stay non-constexpr in C++23, violating P0533R9.
+//
+// Constraining ours on is_arithmetic_v covers both. The UCRT's templates and ours have equivalent
+// parameter-type-lists, so neither is more specialized and the more-constrained one wins ([temp.func.order]), which is
+// the same mechanism the two-argument traits below already rely on.
+//
+// Note that we forward to the __math overloads but intentionally do not pull them into this namespace with a
+// using-declaration: the __math ones are constrained through a defaulted template parameter rather than a
+// requires-clause, so their template-parameter-lists are not equivalent to the UCRT's, neither is more specialized,
+// the more-constrained tiebreak does not apply, and integral calls become ambiguous.
+
 template <class _A1>
-  requires is_integral_v<_A1>
-[[nodiscard]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isfinite(_A1) noexcept {
-  return true;
+  requires is_arithmetic_v<_A1>
+[[nodiscard]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isfinite(_A1 __x) noexcept {
+  return __math::isfinite(__x);
 }
 
 template <class _A1>
-  requires is_integral_v<_A1>
-[[nodiscard]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isinf(_A1) noexcept {
-  return false;
+  requires is_arithmetic_v<_A1>
+[[nodiscard]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isinf(_A1 __x) noexcept {
+  return __math::isinf(__x);
 }
 
 template <class _A1>
-  requires is_integral_v<_A1>
-[[nodiscard]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isnan(_A1) noexcept {
-  return false;
+  requires is_arithmetic_v<_A1>
+[[nodiscard]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isnan(_A1 __x) noexcept {
+  return __math::isnan(__x);
 }
 
 template <class _A1>
-  requires is_integral_v<_A1>
+  requires is_arithmetic_v<_A1>
 [[nodiscard]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool isnormal(_A1 __x) noexcept {
-  return __x != 0;
+  return __math::isnormal(__x);
 }
 
 template <class _A1, class _A2>
diff --git a/libcxx/include/math.h b/libcxx/include/math.h
index 9d0642398b7d4..36461b43cae86 100644
--- a/libcxx/include/math.h
+++ b/libcxx/include/math.h
@@ -392,6 +392,7 @@ namespace __math {
 // To workaround this, we use _LIBCPP_PREFERRED_OVERLOAD to make our overloads stronger than those overloads, which is
 // necessary for constexpr addition in C++23. When _LIBCPP_PREFERRED_OVERLOAD is not supported, we use template
 // overloads as a workaround to avoid conflicts.
+// std::__math::signbit in <__math/traits.h> has the same problem and uses the same workaround.
 
 #      ifdef _LIBCPP_PREFERRED_OVERLOAD
 [[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23
@@ -459,7 +460,9 @@ using std::__math::isunordered;
 
 #      if defined(_LIBCPP_MSVCRT) && _LIBCPP_STD_VER >= 20
 // MS UCRT incorrectly defines some functions in a way not working with integer types. Until C++20, this was worked
-// around by -fdelayed-template-parsing. Since C++20, we can use standard feature "requires" instead.
+// around by -fdelayed-template-parsing. Since C++20, we can use standard feature "requires" instead. The UCRT
+// definitions are also not constexpr, so the __ucrt overloads are constrained on all of is_arithmetic_v rather than
+// just the integer types, which makes them usable in constant expressions as P0533R9 requires since C++23.
 
 // TODO: Remove the workaround once UCRT fixes these functions. Note that this doesn't seem planned as of 2025-07 per
 // https://developercommunity.visualstudio.com/t/10294165.
diff --git a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
index beca4907284ad..77689375e68c5 100644
--- a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
@@ -19,9 +19,6 @@
 // REQUIRES: clang
 // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
 
-// We don't control the implementation of these functions on windows
-// UNSUPPORTED: windows
-
 // Missing some math functions.
 // XFAIL: LLVM-LIBC-FIXME
 
diff --git a/libcxx/test/std/numerics/c.math/isfinite.pass.cpp b/libcxx/test/std/numerics/c.math/isfinite.pass.cpp
index 3d5be61634334..f8eb651d449dc 100644
--- a/libcxx/test/std/numerics/c.math/isfinite.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/isfinite.pass.cpp
@@ -8,9 +8,6 @@
 
 // bool isfinite(floating-point-type x); // constexpr since C++23
 
-// We don't control the implementation on windows
-// UNSUPPORTED: windows
-
 #include <cassert>
 #include <cmath>
 #include <limits>
diff --git a/libcxx/test/std/numerics/c.math/isinf.pass.cpp b/libcxx/test/std/numerics/c.math/isinf.pass.cpp
index e5169e8056c2e..a0681efc114ae 100644
--- a/libcxx/test/std/numerics/c.math/isinf.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/isinf.pass.cpp
@@ -8,9 +8,6 @@
 
 // bool isfinite(floating-point-type x); // constexpr since C++23
 
-// We don't control the implementation on windows
-// UNSUPPORTED: windows
-
 #include <cassert>
 #include <cmath>
 #include <limits>
diff --git a/libcxx/test/std/numerics/c.math/isnan.pass.cpp b/libcxx/test/std/numerics/c.math/isnan.pass.cpp
index e4ccab1243e56..9c9cd631ee389 100644
--- a/libcxx/test/std/numerics/c.math/isnan.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/isnan.pass.cpp
@@ -8,9 +8,6 @@
 
 // bool isfinite(floating-point-type x); // constexpr since C++23
 
-// We don't control the implementation on windows
-// UNSUPPORTED: windows
-
 #include <cassert>
 #include <cmath>
 #include <limits>
diff --git a/libcxx/test/std/numerics/c.math/isnormal.pass.cpp b/libcxx/test/std/numerics/c.math/isnormal.pass.cpp
index 79465b3122d1b..75f6e52e7147d 100644
--- a/libcxx/test/std/numerics/c.math/isnormal.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/isnormal.pass.cpp
@@ -8,9 +8,6 @@
 
 // bool isfinite(floating-point-type x); // constexpr since C++23
 
-// We don't control the implementation on windows
-// UNSUPPORTED: windows
-
 #include <cassert>
 #include <cmath>
 #include <limits>
diff --git a/libcxx/test/std/numerics/c.math/signbit.pass.cpp b/libcxx/test/std/numerics/c.math/signbit.pass.cpp
index ce219b3a35c0b..f4d16a854edf0 100644
--- a/libcxx/test/std/numerics/c.math/signbit.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/signbit.pass.cpp
@@ -8,9 +8,6 @@
 
 // bool signbit(floating-point-type x); // constexpr since C++23
 
-// We don't control the implementation on windows
-// UNSUPPORTED: windows
-
 // GCC warns about signbit comparing `bool_v < 0`, which we're testing
 // ADDITIONAL_COMPILE_FLAGS(gcc): -Wno-bool-compare
 

>From 993c8f4d37fd95008c05f631920bbe0d35df7217 Mon Sep 17 00:00:00 2001
From: Victor Vianna <victor.vianna10 at gmail.com>
Date: Thu, 6 Aug 2026 11:46:13 +0100
Subject: [PATCH 2/2] Update libcxx/include/__math/traits.h

Co-authored-by: A. Jiang <de34 at live.cn>
---
 libcxx/include/__math/traits.h | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)

diff --git a/libcxx/include/__math/traits.h b/libcxx/include/__math/traits.h
index 7d8db4685e3e4..0609d243ffa71 100644
--- a/libcxx/include/__math/traits.h
+++ b/libcxx/include/__math/traits.h
@@ -214,18 +214,9 @@ isunordered(_A1 __x, _A2 __y) _NOEXCEPT {
 
 #if defined(_LIBCPP_MSVCRT) && _LIBCPP_STD_VER >= 20
 namespace __ucrt {
-// The UCRT declares these as unconstrained, non-constexpr function templates, so it isn't only integer arguments that
-// need handling here: without a candidate of our own, floating-point calls resolve to the UCRT's templates too and
-// std::isfinite & friends stay non-constexpr in C++23, violating P0533R9.
-//
-// Constraining ours on is_arithmetic_v covers both. The UCRT's templates and ours have equivalent
-// parameter-type-lists, so neither is more specialized and the more-constrained one wins ([temp.func.order]), which is
-// the same mechanism the two-argument traits below already rely on.
-//
-// Note that we forward to the __math overloads but intentionally do not pull them into this namespace with a
-// using-declaration: the __math ones are constrained through a defaulted template parameter rather than a
-// requires-clause, so their template-parameter-lists are not equivalent to the UCRT's, neither is more specialized,
-// the more-constrained tiebreak does not apply, and integral calls become ambiguous.
+// The UCRT declares these as unconstrained, non-constexpr function templates.
+// In order to support constexpr addition in P0533R9, libc++ provides constrained overloads for all arithmetic types as
+// a workaround. These overloads are preferred to unconstrained ones.
 
 template <class _A1>
   requires is_arithmetic_v<_A1>



More information about the libcxx-commits mailing list