
C++14: floats, bits, and constant expressions - ingve
http://brnz.org/hbr/?p=1518
======
conistonwater
This was confusing:

> _and divide by zero is explicitly named as undefined behavior in 5.6.4_

I thought, how can this possibly be true if IEEE-754 specifically defines
1.0/-0.0 == -inf.

So I looked it up, and the actual rule seems to be

> _If during the evaluation of an expression, the result is not mathematically
> defined or not in the range of representable values for its type, the
> behavior is undefined._

So divide-by-zero is only undefined behaviour if the result is not
representable. But it _is_ representable in IEEE-754 (adherence to which is
implementation-defined), so it should be fine.

That said, I still don't understand how it's not a constant expression.
Perhaps it's because in IEEE-754 it can _sometimes_ cause a divide-by-zero
exception, depending on flag settings? Because to check something is negative
zero it's enough to do something like

    
    
        x == 0 && 1/x == -inf

~~~
twoodfin
_So divide-by-zero is only undefined behaviour if the result is not
representable. But it is representable in IEEE-754 (adherence to which is
implementation-defined), so it should be fine._

That's not how I read the line of the standard you quoted. ISTM it's saying:

(Result not mathematically defined) ∨ (Result not in the range of
representable values) ⇒ Behavior is undefined

In this case, 1.0/-0.0 is mathematically undefined as C++ specifies such
things (I believe division-by-zero is called out specifically as such), and it
doesn't help that IEEE-754 can represent it anyway. It's still a useful
representation in the language because you can reach it through a series of
calculations that are all included in the range of defined behavior.

I'm also skeptical that

    
    
        x == 0 && 1/x == -inf
    

will work reliably for just that reason: I believe a compiler would be
perfectly within its rights to unconditionally treat this expression as false,
since x == 0 would imply UB.

Not at all an expert here, so would enjoy having a standards lawyer set me
straight.

~~~
conistonwater
Perhaps you're right. I just find IEEE-754's approach clear, and C++'s rule
confusing.

I would say that 1.0/-0.0 is _mathematically defined_ in the pure sense:
IEEE-754 defines a specific model of real numbers closed under such operations
[-inf,inf]\union{NaN}.

The thing is that that test should definitely work under IEEE-754, so I would
be very surprised if C++ somehow forbids you from getting the IEEE-standard
behaviour. For reference, here's a proof that it's correct:

    
    
        $ stack repl
        λ> import Data.SBV
        λ> prove $ do x <- sFloat "x"; return $ fpIsNegativeZero x <=> (x .== 0 &&& 1/x .== -infinity)
        Q.E.D.

~~~
mikeash
Undefined behavior means anything can happen, and "anything" includes
"following IEEE-754 rules." Any given C++ implementation is free to implement
this case to follow IEEE-754 and declare that this behavior is supported.

~~~
conistonwater
I see, that makes sense given how the C++ standard omits specifying floating-
point arithmetic. But isn't there still a problem, given that in the article
clang gave the error message

    
    
        error: '(1.0e+0f / -0.0f)' is not a constant expression
    

and the proposed explanation is that "undefined behavior is unusable in a
constant expression"? So it seems clang is not following IEEE-754 rules.

As I read 5.20.2.5 in N4296, undefined behaviour may not occur in constant
expressions, so clang is not free to follow IEEE-754 for the division-by-zero,
even though it can do that in regular code, because then it's UB. And anyway,
why isn't it UB to invoke UB in a constant expression?

~~~
mikeash
I think you're right, the compiler can follow IEEE-754 rules in general, but
as written it couldn't do so for constant expressions.

I find that rule to be really weird in general. Does that mean that, for
example, if you try to use "1 << 32" in a constant expression, then whether
it's actually a constant expression will depend on the target architecture? I
wonder what the rationale is there.

~~~
twoodfin
_I find that rule to be really weird in general. Does that mean that, for
example, if you try to use "1 << 32" in a constant expression, then whether
it's actually a constant expression will depend on the target architecture? I
wonder what the rationale is there._

Yes, that's right. The rationale is that it's not particularly different from
the way constant expressions in C have always worked: The compiler knows full
well whether

    
    
        static const int x = 99999999999;
    

or

    
    
        static const int x = 1 << 32;
    

represents a valid assignment within the range of int on the target platform,
and responds accordingly. constexpr just generalizes that behavior to more of
the expression machinery.

~~~
mikeash
Right, but those examples still compile, they just invoke UB. Normally you
want to avoid that, but sometimes you actually do want to, and the fact that
the result is UB gives the compiler the freedom to support additional
semantics.

For something closer to the original topic, consider:

    
    
        static const double x = 1.0/0.0;
    

That's UB, but the compiler is free to declare that it follows IEEE-754 and
fully support this. The equivalent with constexpr doesn't allow that same
freedom, and the compiler _must_ consider 1.0/0.0 to not be a constexpr.

~~~
twoodfin
Ah, I see what you mean. A few years of warnings-as-errors mode made me forget
that the most bizarre constant expressions (not constexprs) will compile even
if their results or what you do with them are complete nonsense.

I'd argue that the constexpr behavior is an improvement, in that it enables
errors for large swaths of obviously buggy expressions without breaking
backward compatibility. If the committee wants to remove float-division-by-0
from the set of UB expressions, they should do that independently of the rules
for constexprs/constant expressions (as some Googling gives me the impression
the C standard did with Annex F).

Generally, I think compiler vendors should not be distinguishing themselves by
"reliably" implementing some "defined" behavior for what the standard declares
to be UB[1]. At that point you're effectively implementing a proprietary
language extension. "Implementation-defined" parts of the standard—and
tooling/libraries, of course—are where vendors should be differentiating.

[1] I had a very traumatic experience with HP-UX and its configurable null-
pointer-dereference semantics that may be biasing me here.

------
satbyy
I think the first example is sorta invalid because there's a typo:

    
    
        return *(uint32_t*)f;
    

should read as:

    
    
        return *(uint32_t*)(&f);
    

With this change, the code indeed compiles fine (with g++ 5.3.1; not sure of
5.3.0, which the author used)

~~~
Kristine1975
With clang 3.7.0 this results in "constexpr function never produces a constant
expression", since the C-style pointer cast is essentially a reinterpret_cast
(C++14 Standard §5.4p4.4), which is not a "core constant expression" (C++14
Standard §5.20p2.13) and thus must not appear in a constexpr function: C++14
Standard §7.1.5p5:

 _... if no argument values exist such that an invocation of the function or
constructor could be an evaluated subexpression of a core constant expression,
the program is ill-formed; no diagnostic required._

clang seems to go the extra mile and produces a diagnostic.

Incidentally the pointer cast would violate C++'s aliasing rules and thus
invoke undefined behavior.

P.S: The C++14 Standard is available here: [http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2014/n429...](http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf) (never mind the "draft"
status)

~~~
satbyy
Interestingly, g++ gives out a warning (correctly) when compiled with -O2, but
not by default (-O0):

    
    
        warning: dereferencing type-punned pointer will break strict-aliasing rules  [-Wstrict-aliasing]
    

So, you're right indeed.

------
Someone
I would expect one can export a constexpr function from a library (that seems
necessary to me if one wants to write (most of) the C++ standard library in
C++).

If so, a workaround would be to hack together a library that exports a
constexpr function that converts floats to their bit representation (that
might require assembly or binary editing of a library containing a function
that does that by casting) and calling it from the C++ constexpr. Such a
workaround would support negative zero and NaNs.

------
twoodfin
Love this kind of hacking at the edges of the standard.

Which of the attempted schemes are legal (implementation-defined) C++ when
used at runtime? All of them? I'm pretty sure the memcpy() approach has to be
kosher, at least.

EDIT:

Don't have a copy of the standard handy, but cppreference[1] suggests using
reinterpret_cast<> is UB.

[1]
[http://en.cppreference.com/w/cpp/language/reinterpret_cast](http://en.cppreference.com/w/cpp/language/reinterpret_cast)

------
wnoise
If signbit() were compatible with constexpr, it would work for negative zero.
Similarly, frexp() could determine the exponent, though the interface itself
is a problem.

------
eps
> _Maybe you want to run a fast inverse square root at compile time. Or maybe
> you want to do something that is actually useful._

Careful now. Making questionable jokes about the work of the great Carmack?
Thin ice here, very thin.

~~~
sebastic
I'm glad you're aware of Carmack's association with the fast inverse square
root (hopefully you also know that it doesn't originate from him) but the joke
here isn't the denigration of the code itself.

The joke is that the original need for a fast inv sqrt is moot at compile-
time.

~~~
eps
Ah, _compile-time_... missed that, sorry. And, no, I didn't know it wasn't a
Carmack's code (looked it up just now).

~~~
Dylan16807
Also, chips have a dedicated fast inverse square root function these days,
much faster than doing the steps yourself.

