Can std::bit_cast be applied to an empty object?
Asked Answered
W

1

5

Is it allowed to apply std::bit_cast to an object of empty class, converting it to some not-empty class of the same size? And especially is it allowed to do in a constant expression?

I was surprised to find that the following simple program

#include <bit>

struct A {};

struct B { unsigned char b; };

// ok in MSVC and GCC, fail in Clang
static_assert( 0 == std::bit_cast<B>(A{}).b );

is accepted by both GCC and MSVC, and only Clang complains:

error: static assertion expression is not an integral constant expression

Online demo: https://gcc.godbolt.org/z/cGW3Mq3je

What is the correct behavior here according to the standard?

Wnw answered 31/3, 2024 at 20:10 Comment(0)
T
7

From [bit.cast]/2:

[...] A bit in the value representation of the result is indeterminate if it does not correspond to a bit in the value representation of from [...]. For each bit in the value representation of the result that is indeterminate, the smallest object containing that bit has an indeterminate value; the behavior is undefined unless that object is of unsigned ordinary character type or std​::​byte type. [...]

So, in your case, the behavior of the expression is undefined, but not because of the std::bit_cast itself.

Because the bits of b do not correspond to any bit of the value representation of A{}, they are indeterminate and the value of b itself is consequently indeterminate. Only because b has type unsigned char, this is not immediately undefined behavior.

However, for the comparison with 0 ==, you then read the value of b, which causes undefined behavior because its value is indeterminate.

Of course, because you are using the comparison as the condition in a static_assert, the program will not have undefined behavior, but will be ill-formed instead, because the expression is not a constant expression. In a constant expression any lvalue-to-rvalue conversion on an indeterminate value is disallowed. The compiler needs to diagnose this and therefore only Clang is conforming.

Thirzia answered 31/3, 2024 at 20:39 Comment(5)
Thanks! To better understand your explanation, in another example with bit-fields: gcc.godbolt.org/z/3W5onY955, is GCC wrong here and is MSVC right?Wnw
@Wnw I think GCC and Clang are correct: All but 7 bits of the source object are padding bits, i.e. not part of he value representation. So the corresponding bits in the result will be indeterminate and the whole int value indeterminate, which is UB as I quoted above.Thirzia
@Wnw As an aside, aggregate initialization such as A{1} or A{} does not guarantee that padding bits/bytes are zeroed anyway. And even worse, I am not sure whether value of padding is ever stable (even if the standard does specifies zero initialization of padding in some instances). See e.g. my older question #71188118.Thirzia
Thanks. Indeed, x=std::bit_cast<int>(A{1}) has all but 7 indeterminate bits. But in the surrounding std::bit_cast<A>(x) these bits must be ignored and never read. So I thought that it made the program valid.Wnw
@Wnw That might work with unsigned char then, but as I quoted in my answer, for types other than std::byte or unsigned ordinary character types, the cast itself is undefined if it would leave an object with an indeterminate value. That's I suppose in order to allow for trap representations.Thirzia

© 2022 - 2025 — McMap. All rights reserved.