1

I want to take a stack variable and reinterpret cast it into an unsigned integer type of the same size in bytes. For example, I might want to take double value and cast it to an uint64_t, with the catch that the bits are not modified. And I want to do it in a generic fashion.

If I was dealing with pointers, I would use a reinterpret_cast<uint64_t*>(double_ptr).

I have come up with a solution, which uses a dirty hack on reinterpret_cast, and is effective, but it requires quite a lot of meta-programming to get a fairly simple outcome.

The question: is there a better way to do this? I am sure that there is, and that I am making this more complicated than need be.

I did think about using a templated union of type T and appropriately sized int_t, but that seemed even hackier, and seemed to play with undefined behavior.

edit I understand that the standard doesn't specify that double should be 64 bits, as pointed out in the comments. But with a generic approach, I will be able to get an unsigned integral type the same size as double, however big that is.

#include <iostream>

template <typename T, std::size_t S>
struct helper {};

template <typename T>
struct helper<T, 1> {
    using type = uint8_t;
};
template <typename T>
struct helper<T, 2> {
    using type = uint16_t;
};
template <typename T>
struct helper<T, 4> {
    using type = uint32_t;
};
template <typename T>
struct helper<T, 8> {
    using type = uint64_t;
};

template <typename T>
using int_type = typename helper<T, sizeof(T)>::type;

template <typename T>
int_type<T> caster(T value) {
    int_type<T> v;
    *reinterpret_cast<T*>(&v) = value;
    return v;
}

int main(void) {
    {
    auto val = caster(0.);
    static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    {
    auto val = caster(0.f);
    static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    {
    auto val = caster(-0.);
    static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    {
    auto val = caster(-0.f);
    static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    return 0;
}

compiling the code above with gcc gives:

> g++ --version
g++ (GCC) 4.8.2 20131016 (Cray Inc.)

> g++ -std=c++11 test.cpp && ./a.out
64 0
32 0
64 9223372036854775808
32 2147483648
jww
  • 97,681
  • 90
  • 411
  • 885
bcumming
  • 1,075
  • 2
  • 8
  • 17
  • 1
    Whether this is doable at all depends on whether you want to restrict yourself to one particular system and compiler. There is no standards-compliant cross-platform solution. It's not even guaranteed that `double` is as large as a `uint64_t`. – Christian Hackl Feb 20 '15 at 16:12
  • If we ignore the conditional statements in the static_assert() statements, where assumptions about the size of float and double are made, the size of a double is not assumed to be 64 bit (though, in reality, I am happy to assume that floats and doubles are 32 and 64 bits) – bcumming Feb 20 '15 at 16:18
  • can't you just cast a pointer to the bits and then dereference it. (like you'd do with a member function pointer)? – gbjbaanb Feb 20 '15 at 16:49
  • Why do you want to cast it to integer type? – Adam Sosnowski Feb 20 '15 at 17:30
  • It's called [*type punning*](http://en.wikipedia.org/wiki/Type_punning) and is usually done using unions. – Some programmer dude Feb 20 '15 at 18:54
  • @gbjbaanb that is effectively what I am doing, though my castor() function actually makes a copy. I am interested in ways to do this without the pointer casting. – bcumming Feb 20 '15 at 19:22
  • 2
    @JoachimPileborg using a union was my first idea. Then I found that the C++ standard explicitly states that the union member used to read from a union must be the same as the member last written to. Indeed, if I used auto to access a field in a union, it would always cast to the last written fields, not the type of the field I was accessing. Otherwise you get undefined behaviour. This example you show is for C99, which explicitly allows this technique. – bcumming Feb 20 '15 at 19:27

2 Answers2

7

If you don't want to have undefined behavior due to violating the aliasing restrictions (C++11 3.10/10) then you need to access the object representations as characters:

template <typename T>
int_type<T> caster(const T& value) {
    int_type<T> v;
    static_assert(sizeof(value) == sizeof(v), "");
    std::copy_n(reinterpret_cast<const char*>(&value),
                sizeof(T),
                reinterpret_cast<char*>(&v));
    return v;
}

High quality compilers will optimize the copy away. E.g., this program:

int main() {
    return caster(3.14f);
}

effectively optimizes to return 1078523331; on Intel processors.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • Good point! Is the behaviour still undefined if both types are the same size, which is the case here, when I only intend to use this for fundamental types, not compound types (which some more template work could enforce)? – bcumming Feb 20 '15 at 19:54
  • After doing some more reading, there is nothing in the standard about the size of the types. – bcumming Feb 20 '15 at 20:21
2

Between std::conditional_t and std::enable_if_t I believe that you can compress all your helper and int_type definitions into a self-sufficient caster function:

template <typename T>
auto caster(T value){return reinterpret_cast<std::conditional_t<sizeof(T) == sizeof(uint8_t),
                                                                uint8_t,
                                                                conditional_t<sizeof(T) == sizeof(uint16_t),
                                                                              uint16_t,
                                                                              conditional_t<sizeof(T) == sizeof(uint32_t),
                                                                                            uint32_t,
                                                                                            enable_if_t<sizeof(T) == sizeof(uint64_t),
                                                                                                        uint64_t>>>>&>(value);}

I've validated that this works on both gcc 4.9.2 and Visual Studio 2015, if you only have C++11 support though you can still get this into a self-sufficient caster function:

template <typename T>
typename std::conditional<sizeof(T) == sizeof(uint8_t),
                          uint8_t,
                          typename conditional<sizeof(T) == sizeof(uint16_t),
                                               uint16_t,
                                               typename conditional<sizeof(T) == sizeof(uint32_t),
                                                                    uint32_t,
                                                                    typename enable_if<sizeof(T) == sizeof(uint64_t),
                                                                                       uint64_t>::type>::type>::type>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}

This will pick the uint* that has the same sizeof as the type you pass to it and use that.

I have an explaination of std::enable_if over here that may be helpful to you.

Obviously this is just useful on types that are 8, 16, 32, or 64-bits in size, but if you feel like expanding it to handle other stuff, just add another conditional_t!


If you are only ever going to pass in 8, 16, 32, or 64-bit types you can get away with less protection in your template:

template <typename T>
auto caster(T value){return reinterpret_cast<std::tuple_element_t<size_t(log2(sizeof(T))), std::tuple<uint8_t,
                                                                                                      uint16_t,
                                                                                                      uint32_t,
                                                                                                      uint64_t>>&>(value);}

This works for C++14, the C++11 equivalent is:

template <typename T>
typename std::tuple_element<size_t(log2(sizeof(T))), std::tuple<uint8_t,
                                                                uint16_t,
                                                                uint32_t,
                                                                uint64_t>>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}

This is less forgiving than the conditional_t/enable_if_t template because of how I am indexing the std::tupple. size_t is an integral type so any type of any size less than 128-bits will cast to a valid std::tuple index. So for example a struct that was 3-bits in size would be cast to a uint16_t, while the desired result would probably have been for it to fail to compile.

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • That is a good point about std::enable_if... I had spent a couple of minutes wondering how to get rid of the helper template. – bcumming Feb 20 '15 at 19:57
  • @bcumming I've improved my code a bit further, but I didn't bother changing the `reinterpret_cast`. [Casey](http://stackoverflow.com/users/923854/casey) does have the right of it though that it is in violation of aliasing restrictions; a `copy_n` should be used instead: http://stackoverflow.com/questions/28697626/why-doesnt-reinterpret-cast-force-copy-n-for-casts-between-same-sized-types?lq=1 – Jonathan Mee Feb 25 '15 at 14:30
  • I wonder if putting all of the conditionals together makes things any clearer? My final solution tested whether alignof(T)==alignof(int?_t), and chose reinterpret_cast or copy_n according to the result. https://github.com/bcumming/cpp-etc/blob/master/punning/pun.cpp – bcumming Feb 27 '15 at 07:56
  • @bcumming As far as choosing I think you *must* use `copy_n`, because it is very likely that your compiler will inline this function which could easily cause aliasing errors in your code. As far as cleaning up the conditions, I've chosen to encapsulate everything in the function cause I didn't think there would be a lot of potential for reuse of something like a "[templatized-case statement](http://stackoverflow.com/q/28699281/2642059)". But you know your code base, if that's something you could reuse, it would clean up the `caster` a lot. – Jonathan Mee Feb 27 '15 at 11:28
  • @bcumming OK, this is the last edit, I found a cool way to index a `std::tuple` over [here](http://stackoverflow.com/q/28769788/2642059), which could be used to simplify `caster`. I'm done now, thanks for the great problem! – Jonathan Mee Mar 02 '15 at 12:38