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?

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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?
@Fedor 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.
@Fedor 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 stackoverflow.com/questions/71187618/….
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.
@Fedor 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.