C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Fixing std::bit_cast padding bit issues

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Fri, 23 Jan 2026 09:32:11 -0500
On Fri, Jan 23, 2026 at 12:56 AM Jan Schultke <janschultke_at_[hidden]>
wrote:

>
> Good point. IIUC, the x87 `fstpt` instruction (used by GCC's codegen)
>> writes only 10 bytes, not 16 bytes.
>> And Clang's codegen *explicitly* writes only 10 bytes, not 16 bytes,
>> using integer `movw`.
>>
>> Now, *at constexpr time,* GCC does act as if it's zeroing the padding
>> bits: https://godbolt.org/z/o6Gcen1P5
>> And EDG 6.8's __builtin_bit_cast seems to be doing the same thing,
>> zeroing the padding bits: https://gcc.godbolt.org/z/GEMYbhdx8
>>
>
> It's worth noting that both GCC and Clang are correct because this is a
> case of undefined behavior during constant evaluation.
> https://eel.is/c++draft/bit.cast#4.2 states that the behavior is
> undefined when you get a padding bit from the source into a non-padding bit
> within the destination.
>

...Unless the destination is `unsigned char`, which it is in my example
above. In that case, (4.1) <https://eel.is/c++draft/bit.cast#4.1.sentence-1>
applies, and the value of the unsigned char is indeterminate instead.


> The one-liner repro for the divergence is:
>
> constexpr auto x = __builtin_bit_cast(__int128, 0.0L); // GCC OK, Clang
> error
>
> Whereas Clang rejects the bit_cast entirely. IIUC, Clang is using logic
> similar to Jan's to insist that this std::bit_cast has UB so it shouldn't
> compile at constexpr time.
>
>
> Clang rejects that code, but simply on the basis that constant evaluation
> hits something that's UB. Clang is already allowed to do that.
>

Ah, we're both wrong. This example has nothing to do with UB. Clang is
acting on [expr.const]/21 <https://eel.is/c++draft/expr.const#21.2.2>,
which says that if a result (like the result of that bit_cast, which
becomes the result of `g()`) has any indeterminate value bits, then it's
not a constant expression; and therefore we can't use the result of `g()`
to initialize `constexpr Dest a`.


> What I'm suggesting is that the call to std::bit_cast should be
> ill-formed because it's unconditionally UB, and thus indicative of a
> programmer error, even if never evaluated.
>

Yes, I know you're suggesting that; but that isn't the right thing to do.
What you should do is what Thiago et al. have been saying: simply propose
the following patch to [bit.cast] <https://eel.is/c++draft/bit.cast>.

*Returns:* An object of type To. Implicitly creates objects nested within
> the result ([intro.object]). Each bit of the value representation of the
> result is equal to the corresponding bit in the object *value*
> representation of `from`. Padding bits of the result are unspecified. For
> the result and each object created within it, if there is no value of the
> object's type corresponding to the value representation produced, the
> behavior is undefined. If there are multiple such values, which value is
> produced is unspecified. A bit in the value representation of the result is *zero
> if it corresponds to a padding bit of `from`; otherwise, its value is*
> indeterminate if it does not correspond to a bit in the value
> representation of `from` or corresponds to a bit for which the smallest
> enclosing object is not within its lifetime or has an indeterminate value
> ([basic.indet]) . A bit in the value representation of the result *;
> otherwise, it* is erroneous if it corresponds to a bit for which the
> smallest enclosing object has an erroneous value. For each bit *b* in the
> value representation of the result that is indeterminate or erroneous, let
> *u* be the smallest object containing that bit enclosing *b*:

(4.1) If *u* is of unsigned ordinary character type or `std::byte`
type, *u* has
> an indeterminate value if any of the bits in its value representation are
> indeterminate, or otherwise has an erroneous value.
> (4.2) Otherwise, if *b* is indeterminate, the behavior is undefined.
> (4.3) Otherwise, the behavior is erroneous, and the result is as specified
> above.

The result does not otherwise contain any indeterminate or erroneous values.


That not only makes the code have well-defined behavior, but also makes
similar code (e.g. with `array<int,4>` in place of `array<unsigned
char,16>`) have well-defined behavior too.
This wording change *does* require GCC and Clang to make a change to their
runtime codegen, as Thiago pointed out, but that's fine. Any user program
that works in C++26 will continue to work exactly the same after this
wording patch, since the only thing this patch does is to change the values
you get when you use bit_cast to inspect an object's padding bits (which
aren't under your control anyway).

[...]

> GCC accepts that code, which it's allowed to do. It's simply undefined
> behavior at compile time.
>

No it's not. It currently (in C++26) produces an indeterminate value, which
is not a constant expression, which must be rejected as the initializer for
a constexpr variable.


I think I do see what you're saying about unconditional UB, though, in the
case where `Dest` is not an array<unsigned char,16> but rather
array<int,16> and some of the bits of the incoming object are indeterminate
or erroneous. Is it *possible* to have indeterminate or erroneous bits at
constexpr time (except in this weird case, the result of `std::bit_cast` of
padding bits, which the patch above eliminates)? If so, then I agree it
would be good to also add either this patch to [bit.cast]
<https://eel.is/c++draft/bit.cast>:

(4.1) If *u* is of unsigned ordinary character type or `std::byte`
type, *u* has
> an indeterminate value if any of the bits in its value representation are
> indeterminate, or otherwise has an erroneous value.
> (4.2) Otherwise, if *b* is indeterminate, the behavior is undefined *and
> the result of `bit_cast` is not a constant expression ([expr.const])*.
> (4.3) Otherwise, the behavior is erroneous, and the result is as specified
> above.


or — probably cleaner — this patch to [expr.const]/9.8
<https://eel.is/c++draft/expr.const#9.8> instead:

> (9.8) an operation that would have undefined or erroneous behavior as
> specified in [intro] through [cpp];
> *(9.x) an operation that would have undefined or erroneous behavior as
> specified in [bit.cast];*


We can't make the former patch say "*and the call to `bit_cast` is
ill-formed*" because in general the compiler *can't tell* whether at
runtime the input bits will have indeterminate values (or be outside their
lifetimes). But if it's all happening at constexpr time then the compiler
can know and reject the expression.

–Arthur

Received on 2026-01-23 14:32:28