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 12:05:20 -0500
On Fri, Jan 23, 2026 at 10:23 AM Jan Schultke <janschultke_at_[hidden]>
wrote:

> On Fri, Jan 23, 2026, 15:32 Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> wrote:
>
>> 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.
>>
>
> Yes, of course.
>
>
>>
>>> 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`.
>>
>
> [bit.cast] explicitly points out the UB in its effects, so I don't think
> this applies. The UB already happens inside the function.
>

But in my example, with `Dest` being `array<unsigned char, 16>`, there is
no UB. Neither inside the function nor outside of it.
https://godbolt.org/z/o6Gcen1P5


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>.
>>
>
> I think both options are viable.
>

Yes, both options are things you *can* do, but only one is what you *should*
do.


*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.
>>
>
> We can apply what I suggest in my draft though because I only propose to
> diagnose the degenerate form, where casting always results in UB regardless
> of input, and whether this happens is known.
>

Can you post your proposed wording again?
My impression is that you're concerned mainly with
    long double s = 3.14;
    std::array<int,4> d = std::bit_cast<std::array<int,4>>(s);
which today is UB; I propose (in the P/R displayed above) to make it
well-defined; my impression is that you propose to make it ill-formed,
which is strictly less useful.

–Arthur

>

Received on 2026-01-23 17:05:39