Reputation: 147
Why code below has no problem with a2 but does not compile for z1?
#include <cmath> // std::nextafter
#include <limits> // std::numeric_limits
int main ()
{
constexpr float a1 {1.f};
constexpr float a2 {std::nextafter(a1, std::numeric_limits<float>::max())};
constexpr float z0 {0.f};
constexpr float z1 {std::nextafter(z0, std::numeric_limits<float>::max())};
return 0;
}
Compiled with GCC 13.2
In file included from <source>:1:
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/cmath: In function 'int main()':
<source>:9:39: in 'constexpr' expansion of 'std::nextafter(((float)z0), std::numeric_limits<float>::max())'
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/cmath:2417:32: error: '__builtin_nextafterf(0.0f, 3.40282347e+38f)' is not a constant expression
2417 | { return __builtin_nextafterf(__x, __y); }
So GCC compiled a2 correctly but is unable to compile z1.
Note: Clang 14.0 and MSVC 19.38 have problems even with a2.
Upvotes: 8
Views: 410
Reputation: 76678
Since C++23 most C library math functions, including std::nextafter
, are marked constexpr
and generally usable in constant expression. However, there is an exception to this if the call would set errno
or would raise any floating point exception other than FE_INEXACT
(according to the semantics of ISO C Annex F as applicable to the floating point types).
This guarantees that floating point exceptions and the global errno
that were intended to be observable at runtime don't "vanish" (except for FE_INEXACT
which was excluded).
So, if you are asking why z1
doesn't compile in C++23 mode, then it is because given that float
on GCC is an IEEE 754 type and the result would be subnormal, ISO C Annex F applies and specifies std::nextafter
to raise FE_UNDERFLOW
, making the call non-eligible for constant expression evaluation under the exceptions I mentioned above.
If you are asking why a2
does compile with GCC even in C++20 or earlier mode, then it is because GCC even before C++23 handled the C standard library math functions as if they were marked constexpr
, although that's technically not conforming to the standard.
According to the standard a2
's initialization must produce a diagnostic before C++23, because the initializer calls a function that isn't marked constexpr
(and the compiler may not add it implicitly). Calling a non-constexpr
function in an expression disqualifies that expression from being a constant expression.
If you are asking why Clang and MSVC don't accept either variable, then it is probably because they haven't implemented the C++23 changes yet.
Upvotes: 4
Reputation: 38465
libstdc++13.2.0 does not seem to implement std::nextafter()
as constexpr
yet.
GCC turns std::nextafter(a1, std::numeric_limits<float>::max())
into a constant value. This is a lucky exceptional case when compilers can evaluate rare expressions as constexpr even if they are not marked as such.
GCC can't turn std::nextafter(z0, std::numeric_limits<float>::max())
into a constant value, it turns __builtin_nextafterf()
into the call to the C function nextafterf()
that can't be constexpr. See https://godbolt.org/z/v7KMrTdfW
The code can be simplified to
#include <cmath> // std::nextafter
#include <limits> // std::numeric_limits
int main ()
{
constexpr float a2 {std::nextafter(1.f, std::numeric_limits<float>::max())};
constexpr float z1 {std::nextafter(0.f, std::numeric_limits<float>::max())};
return 0;
}
with the similar Assembler result: https://godbolt.org/z/KE9vGd6j7
Upvotes: 6
Reputation: 473417
nextafter
was not required to be a constexpr
function before C++23. Since that's still a new standard, many standard library implementations have not implemented it yet.
Upvotes: 1