Tomáš Nadrchal
Tomáš Nadrchal

Reputation: 147

Why is std::nextafter not constant expression?

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

Answers (3)

user17732522
user17732522

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

3CxEZiVlQ
3CxEZiVlQ

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

Nicol Bolas
Nicol Bolas

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

Related Questions