[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