2

I wanted to write some template functions to handle type punning in a defined way, so I came up with these two functions. The first takes an object and converts it with punning to another. It ensures that both types are POD and of equal size. The second was intended to simply take any pointer (as if void*) but still check to make sure that the pointer was to a POD type. The problem I ran into is if I pass a non-const pointer then the first function will get used instead. What would be the best way of handling this?

template <class TO, class FROM>
FORCE_INLINE TO punning_cast(const FROM &input)
{
    static_assert(std::is_pod<TO>::value, "TO must be POD");
    static_assert(std::is_pod<FROM>::value, "FROM must be POD");
    static_assert(sizeof(TO) == sizeof(FROM), "TO and FROM must be the same size");

    TO out;
    std::memcpy(&out, &input, sizeof(TO));
    return out;
}

template <class TO, class FROM>
FORCE_INLINE TO punning_cast(const FROM *input)
{
    static_assert(std::is_pod<TO>::value, "TO must be POD");
    static_assert(std::is_pod<FROM>::value, "FROM must be POD");

    TO out;
    std::memcpy(&out, input, sizeof(TO));
    return out;
}
Chris_F
  • 4,991
  • 5
  • 33
  • 63

2 Answers2

3

Function templates mix weirdly with overloading.

A possible solution (not the unique) would be to use enable_if for the declaration of each function enalbing the second for pointer types and vise versa for the first

#include <type_traits>

template <class TO, class FROM>
FORCE_INLINE typename enable_if<!is_pointer<FROM>::value, TO>::type
    punning_cast(const FROM &input) { ... }

template <class TO, class FROM>
FORCE_INLINE typename enable_if<is_pointer<FROM>::value, TO>::type
    punning_cast(const FROM input) { ... }

So an example of achieving the dissambiguation (between reference and pointer) would be this one

Community
  • 1
  • 1
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • I'm not sure that "Function templates don't overload" is entirely accurate, but +1 for the rest. – Mooing Duck May 19 '14 at 20:07
  • @MooingDuck Too much of an oversimplification ? I was going to go into [function template overload resolution](http://stackoverflow.com/a/22411782/2567683) but then I'd have to go out of the question's scope (you reckon I should remove it to prevent misinterpretations ? ) – Nikos Athanasiou May 19 '14 at 20:15
  • I think I'd just change it to something like "Function templates mix weird with overloading, see [link](http://www.gotw.ca/gotw/049.htm)." – Mooing Duck May 19 '14 at 20:22
  • @MooingDuck Good thinking, will do that. Thnx (language lawyers would probably shout about "instantiations of the function template being the ones that participate in the overload process" but this phrasing avoids that also ;) – Nikos Athanasiou May 19 '14 at 20:23
  • This looks interesting, however I have ran into another issue when using this. If I pass an array I get an error about an ambiguous overload. – Chris_F May 19 '14 at 21:25
  • @Chris_F An array is not a pointer. When passed to a function template it's exact type can be deduced, preventing its decay. You can check [here](http://stackoverflow.com/a/23012406/2567683) for explicit ways to decay an array into a pointer (I added kloffy 's method for completeness, the rest of the solutions are prior to c++14) – Nikos Athanasiou May 20 '14 at 04:38
  • Should I not just add `&& !std::is_array::value` to the first version to force the pointer versions to catch it instead? – Chris_F May 20 '14 at 04:45
  • @Chris_F It's a matter of taste. Note that for this to work, the second version should take its argument by value and not by ref/ptr (the way I write it) or else it'll also be disabled and the program won't compile. – Nikos Athanasiou May 20 '14 at 06:11
3

Disclaimer: I like Nikos Athanasiou's answer better for C++11 and onward.

One solution to this (common) problem is to wrap the functions in structs and add a helper function that selects one of them:

template <class TO, class FROM>
struct punning_cast_impl
{
    static FORCE_INLINE TO cast(const FROM &input)
    {
        static_assert(std::is_pod<TO>::value, "TO must be POD");
        static_assert(std::is_pod<FROM>::value, "FROM must be POD");
        static_assert(sizeof(TO) == sizeof(FROM), "TO and FROM must be the same size");

        TO out;
        std::memcpy(&out, &input, sizeof(TO));
        return out;
    }
};

template <class TO, class FROM>
struct punning_cast_impl<TO, FROM*>
{
    static FORCE_INLINE TO cast(const FROM *input)
    {
        static_assert(std::is_pod<TO>::value, "TO must be POD");
        static_assert(std::is_pod<FROM>::value, "FROM must be POD");

        TO out;
        std::memcpy(&out, input, sizeof(TO));
        return out;
    }
};

template<class TO, class FROM>
TO FORCE_INLINE punning_cast(const FROM& input)
{
    return punning_cast_impl<TO, FROM>::cast(input);
}

int main()
{
    double d1 = 50.0;
    int64_t i1 = punning_cast<int64_t>(d1); // calls version #1

    double d2 = 100.0;
    int64_t i2 = punning_cast<int64_t>(&d2); // calls version #2
}
Community
  • 1
  • 1
dlf
  • 9,045
  • 4
  • 32
  • 58
  • +1 you beat me to it. I would add a `const` for the formal argument to the `punninc_cast_impl::cast` function. Otherwise would not be usable for actual argument a pointer to `const`. – Cheers and hth. - Alf May 19 '14 at 19:41
  • @Cheersandhth.-Alf Oops; lost that somehow in the copying. Thanks. – dlf May 19 '14 at 19:46
  • +1 For the sportsmanship. Though lengthier, this answer scales nicely and a choice can only be made depending on the problem at hand. Nicely done. – Nikos Athanasiou May 19 '14 at 20:07