Date: Fri, 23 Jan 2026 16:23:44 +0100
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.
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.
> *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.
> –Arthur
>
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.
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.
> *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.
> –Arthur
>
Received on 2026-01-23 15:24:02
