C++ Logo

std-proposals

Advanced search

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

From: Jan Schultke <janschultke_at_[hidden]>
Date: Sat, 17 Jan 2026 16:58:47 +0100
On Sat, 17 Jan 2026 at 16:46, Thiago Macieira via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On Saturday, 17 January 2026 03:22:24 Pacific Standard Time Jan Schultke
> via
> Std-Proposals wrote:
> > I've decided to tackle the issue in a proposal:
> > https://isocpp.org/files/papers/D3969R0.html
> >
> > Thanks for the feedback, it's been quite helpful in making this draft and
> > addressing possible design alternatives.
>
> Feedback:
>
> In the introduction:
> Please introduce that you're talking about x86 and 80-bit
> extended-precision
> long double before you talk about the bug. There are several architectures
> where it isn't.
>
> Is it UB to *cast* from long double to __int128? Or is it UB to observe
> the
> padding bits on the destination?
>
> Suppose for example one did:
> auto a = std::bit_cast<unsigned __int128>(0.0L);
> auto b = std::bit_cast<std::array<uint16_t, 8>>(a);
>
> The value of the 128-bit integer was never observed, it was only used as a
> source to casting to an array of 16 bytes. And observing the non-padding
> bytes/words in that array shouldn't be UB, should it?
>

It's UB according to [bit.cast]. Copying padding bits from the original
into the value representation of the destination makes the value
indeterminate, and this makes the call to bit_cast UB. Observing padding
bits has nothing to do with it.


> I wouldn't write "is an alternative spelling for std::unreachable". Please
> elaborate on how you're reaching that conlusion: this operation is always
> UB
> and therefore the compiler is allowed to assume didn't happen, therefore
> we
> conclude that it is unreachable.
>

Well, I think it may be worth adding an explanation of the relevant
[bit.cast] wording in the introduction. I don't see any other way of
phrasing it though; std::bit_cast<__int128, long double> and
std::unreachable have the same behavior.


> In the design:
>
> I'm not satisfied with the arguments for leaving std::bit_cast unchanged
> for
> byte arrays. What's the point of accessing the indeterminate padding bytes
> in
> byte form? Since they are indeterminate, the program also has
> indeterminate
> behaviour if one uses them in a control expression (I believe it turns UB
> too). The only two things one *can* do to the indeterminate bytes are to
> copy
> them to other bytes or to overwrite them with determinate bytes.
>

Well yeah, the current design allows you to copy them around, and not much
else. I don't see much motivation for changing that status quo though, and
the problem described in the paper doesn't have to do with the case of
casting to byte arrays.

But then under as-if rule, the compiler can see whether one did any of that
> and avoid emitting the pad-clearing.
>
> As for using an older version of std::bit_cast, I don't see a problem. The
> __cpp_lib_bit_cast macro would get updated to a new value, so application
> developers would know the behaviour has been updated. If they would know
> to
> use std::bit_cast_clear_padding(), they would also know to check the value
> of
> the macro.
>

People diligently checking feature-test macros is a very optimistic
assumption. The issue is that if we only change bit_cast, then the function
compiles either way, whether it has UB or the compiler is recent and it's
well-defined. If you're forced to spell it std::bit_cast_zero_padding, it
either compiles and does the right thing, or it doesn't compile.



> I would agree with the surprise that it does clear when one didn't expect
> it.
> However, the end result is that std::bit_cast is relegated to the niche
> use-
> cases and std::bit_cast_clear_padding is the general one, superior in
> almost
> every aspect, and requires adding #if everywhere.
>

It would only require adding #if for a few versions, just like any other
feature. One should always prioritize the overall design, which lives for
decades rather than the temporary transition state.

I don't think that std::bit_cast_zero_padding makes for a better "default"
option anyway. std::bit_cast would be used in the vast majority of cases
where you don't have padding issues, like casting float to int, or casting
a type to a byte array.

std::bit_cast_zero_padding solves a very specific edge case where you have
padding bits in the source type and need to give them some arbitrary value
in the destination type. I don't think this is common.

Received on 2026-01-17 15:59:02