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.typeofis 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
unionLet's look at how the approach using
unionworks first:We are creating a compound literal from an anonymous union made of
Tand whatever type the given expression has. We initialize this literal to.b= ...using designated initializers and then access the.amember of typeT.The
typeof(T)is necessary if we want to pun function pointers, arrays, etc., due to C's type syntax.Implementation using
memcpyThis 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)where123is an rvalueTmemcpyreturns the destination operand, so we can cast the result totypeof(T)*and then dereference that pointer.We can completely eliminate
typeofhere 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_caston 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_assertis 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__.