2/25/2009

02-25-09 - Cast with union

Sometimes I need to cast an object to another as a "bit cast" , not a "value cast" (I don't know the good terms for this). The classic way is :

template <typename t_to, typename t_fm>
t_to & same_size_bit_cast( t_fm & from )
{
    COMPILER_ASSERT( sizeof(t_to) == sizeof(t_fm) );
    return *( (t_to *) &from );    
}

Which works fine except that I guess it freaks out GCC and might cause aliasing problems and such. (maybe I can just mark that up right and not have trouble?).

The union cast :


template <typename t_to, typename t_fm>
t_to same_size_bit_cast( t_fm from )
{
    RR_COMPILER_ASSERT( sizeof(t_to) == sizeof(t_fm) );
    union _bit_cast_union
    {
        t_fm fm;
        t_to to;
    };
    _bit_cast_union converter = { from };
    return converter.to;
}

*almost* works. It works some times, like on basic types it works no problem. If t_to or t_fm has constructors or assignment operators though, it refuses to compile which is damn annoying (it would be handle in C++ if there was a way to say "I want an instance of this object here but don't run any constructors or destructors, just give me the member layout without the associated functions"). Often the whole reason I'm doing this is because I want to convert a more complex object into something I can just treat as a bag of bits. (for example if you want to use the windows Interlocked64 ops to work an LFSList type, you need to cast from a struct to a LONGLONG).

What's worse is that if t_fm or t_to has certain type qualifiers that get attached in the template argument deduction, it fails, and it becomes a big mess of casting back on the user side to figure it out and get that stuff off. (qualifiers like const or volatile or will make the union illegal).

Furthermore there's a nasty difference in that the latter gives you a *copy* while the first gives you a reinterpret pointer to the same memory that you can fiddle with, which is what I'd rather have, but I guess that's verboten these days.

3 comments:

Autodidactic Asphyxiation said...

Have you tried just using memcpy? At least on gcc it is a compiler intrinsic, and for small, constant copies it should do what you want.

cbloom said...

Hmm, that's disturbingly simple and rational.

Autodidactic Asphyxiation said...

It also has the advantage of actually being technically correct (and not just practically so) based on a strict reading of the C language spec. Type punning (except char* in certain cases) is actually not guaranteed to work because of strict aliasing.

old rants