Is it possible to implement something similar to C++20's std::bit_cast
in C? It would be a lot more convenient than using union
or casting pointers to different types and dereferencing.
If you had a bit_cast
, then implementing some floating point functions would be easier:
float Q_rsqrt( float number )
{
int i = 0x5f3759df - ( bit_cast(int, number) >> 1 );
float y = bit_cast(float, i);
y = y * ( 1.5f - ( number * 0.5f * y * y ) );
y = y * ( 1.5f - ( number * 0.5f * y * y ) );
return y;
}
See also Fast inverse square root
The naive solution is:
#define bit_cast(T, ...) (*(T*) &(__VA_ARGS__))
But it has major problems:
- it is undefined behavior because it violates strict aliasing
- it doesn't work for bit-casting rvalues because we are taking the address of the second operand directly
- it doesn't make sure that the operands have the same size
Can we implement a bit_cast
without these issues?
It is possible in non-standard standard C, thanks to
typeof
.typeof
is also a further proposed feature for C23, so it may become possible in standard C23. One of the solutions below makes some sacrifices which allow C99 compliance.Implementation Using
union
Let's look at how the approach using
union
works first:We are creating a compound literal from an anonymous union made of
T
and whatever type the given expression has. We initialize this literal to.b= ...
using designated initializers and then access the.a
member of typeT
.The
typeof(T)
is necessary if we want to pun function pointers, arrays, etc., due to C's type syntax.Implementation using
memcpy
This implementation is slightly longer, but has the advantage of relying only on C99, and can even work without the use of
typeof
:We are copying from one compound literal to another and then accessing the destination's value:
bit_cast(float, 123)
where123
is an rvalueT
memcpy
returns the destination operand, so we can cast the result totypeof(T)*
and then dereference that pointer.We can completely eliminate
typeof
here and make this C99-compliant, but there are downsides:We are now taking the address of the expression directly, so we can't use
bit_cast
on rvalues anymore. We are usingT*
withouttypeof
, so we can no longer convert to function pointers, arrays, etc.Implementing Size Checking (since C11)
As for the last issue, which is that we don't verify that both operands have the same size: We can use
_Static_assert
(since C11) to make sure of that. Unfortunately,_Static_assert
is a declaration, not an expression, so we have to wrap it up:We are creating a compound literal that contains the assertion and discarding the expression.
We can easily integrate this in the previous two implementations using the comma operator:
Known and Unfixable Issues
Because of how macros work, we can not use this if the punned type contains a comma:
This doesn't work because macros ignore square brackets and the
1]
would not be considered part of the type, but would go into__VA_ARGS__
.