[libcxx] std::isfinite, std::isinf, std::isnan and std::isnormal are not constexpr on Windows with C++23
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
- [Summary](#summary)
- [Reproducing](#reproducing)
- [Cause](#cause)
- [Why it was never caught](#why-it-was-never-caught)
## Summary
P0533R9 makes the floating-point classification traits usable in constant
expressions in C++23. With libc++ they are, on Linux and macOS. On Windows they
are not — `` forwards them to the UCRT's non-constexpr overloads, and
`-std=c++23` has no bearing on that.
```c++
#include
static_assert(std::isfinite(1.0f));
```
```
error: static assertion expression is not an integral constant expression
2 | static_assert(std::isfinite(1.0f));
| ^~~~~~~~~~~~~~~~~~~
note: non-constexpr function 'isfinite' cannot be used in a constant expression
ucrt\corecrt_math.h(435,32): note: declared here
435 | _Check_return_ inline bool isfinite(_In_ _Ty _X) throw()
| ^
```
Same for `isinf`, `isnan` and `isnormal`.
## Reproducing
**Reproduced with:** clang trunk `23.0.0git` (`53d18800eda3`), llvm-project at
`5270be7020cb`, Windows SDK 10.0.28000.0, `/std:c++23preview`,
`x86_64-pc-windows-msvc`.
repro.cpp (click to expand)
```c++
#include
#include
#if __cplusplus < 202302L
# error "This repro requires C++23 (/std:c++23preview, /std:c++latest or -std=c++23)."
#endif
// ---------------------------------------------------------------------------
// The bug. P0533R9 makes all of these constexpr in C++23.
// ---------------------------------------------------------------------------
static_assert(std::isfinite(1.0f));
static_assert(std::isfinite(1.0));
static_assert(std::isfinite(1.0L));
static_assert(!std::isfinite(std::numeric_limits::infinity()));
static_assert(!std::isinf(1.0f));
static_assert(!std::isinf(1.0));
static_assert(!std::isinf(1.0L));
static_assert(std::isinf(std::numeric_limits::infinity()));
static_assert(!std::isnan(1.0f));
static_assert(!std::isnan(1.0));
static_assert(!std::isnan(1.0L));
static_assert(std::isnan(std::numeric_limits::quiet_NaN()));
static_assert(std::isnormal(1.0f));
static_assert(std::isnormal(1.0));
static_assert(std::isnormal(1.0L));
static_assert(!std::isnormal(std::numeric_limits::denorm_min()));
// Same through the global namespace, which is what exposes.
static_assert(::isfinite(1.0f));
static_assert(!::isinf(1.0));
static_assert(!::isnan(1.0L));
static_assert(::isnormal(1.0));
int main() { return 0; }
```
```bat
clang-cl /c /nologo /TP /std:c++23preview -Xclang -nostdinc++ ^
-I\include\c++\v1 repro.cpp
```
Note that Compiler Explorer **cannot** run this one: there is no libc++-on-Windows
configuration. No C++ compiler entry exposes a `stdlib` override; under clang-cl,
`-stdlib=libc++` is ignored with `warning: unknown argument ignored in clang-cl`
and `_LIBCPP_VERSION` stays undefined. Cross-compiling from CE's Linux clangs
with `--target=x86_64-pc-windows-msvc` fails with `'cmath' file not found`, since
those hosts have no UCRT. This was requested in
[compiler-explorer#1967](https://github.com/compiler-explorer/compiler-explorer/issues/1967)
— the clang-cl half shipped, the libc++ half did not.
## Cause
The UCRT declares these traits in the **global** namespace as non-constexpr,
unconstrained function templates (`corecrt_math.h`, Windows Kits 10.0.28000.0):
```c++
extern "C++" {
template
_Check_return_ inline bool isfinite(_In_ _Ty _X) throw() { return fpclassify(_X) <= 0; }
// ... same shape for isinf, isnan, isnormal
}
```
libc++'s `` deliberately does **not** re-export its own overloads there:
```c++
// libcxx/include/math.h
// The MSVC runtime already provides these functions as templates
#ifndef _LIBCPP_MSVCRT
using std::__math::isfinite;
// ...
#endif
```
so the only floating-point candidates in scope are the UCRT's. `` then
does `using ::isfinite _LIBCPP_USING_IF_EXISTS;`, which makes `std::isfinite`
resolve to the non-constexpr UCRT template.
The only thing libc++ currently adds on Windows is a set of `requires`-constrained
**integral** overloads in `std::__math::__ucrt`, working around a separate UCRT
bug: its `fpclassify` overload set is ambiguous for integers, so `isfinite(1)`
fails to compile inside the UCRT's own body. See
[VS developer community #10294165](https://developercommunity.visualstudio.com/t/10294165).
## Why it was never caught
Every test that would have caught this is `UNSUPPORTED: windows`:
- `libcxx/test/std/numerics/c.math/{isfinite,isinf,isnan,isnormal,signbit}.pass.cpp`
— "We don't control the implementation on windows"
- `libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp`
— "We don't control the implementation of these functions on windows"
Those exclusions are justified for other reasons (see *Out of scope* below), but
they took the constexpr-ness of these four traits down with them.
Contributor guide
Research direction
Start with libcxx/include/math.h and cmath.h, then reproduce the C++23 static assertions with clang-cl on Windows using the provided repro.cpp. Review the Windows exclusions in libcxx/test/std/numerics/c.math/{isfinite,isinf,isnan,isnormal,signbit}.pass.cpp and libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp; done means the four traits are constexpr on Windows without regressing integral handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers, operating-systems, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100