Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can a C++ template be specialized for all 32-bit POD types?

I've developed a simple template function for swapping the byte order of a single field:

template <typename T> inline void SwapEndian(T& ptr) {
    char *bytes = reinterpret_cast<char*>(&ptr);
    int a = sizeof(T) / 2;
    while (a--) {
        char tmp = bytes[a];
        int b = sizeof(T) - 1 - a;
        bytes[a] = bytes[b];
        bytes[b] = tmp;
    }
}

I'll often use it where T = int or float. Both of these types are represented by 4 bytes on the target platforms, and can be processed by the same specialization of the template.

Because this function sometimes is responsible for processing large buffers of raw data, I've created an optimized specialization:

template<> inline void SwapEndian(float& ptr) {
    #if defined(__GNUC__)
        *reinterpret_cast<unsigned*>(&ptr) = __builtin_bswap32(*reinterpret_cast<unsigned*>(&ptr));

    #elif defined(_MSC_VER)
        *reinterpret_cast<unsigned*>(&ptr) = __byteswap_ulong(*reinterpret_cast<unsigned*>(&ptr));

    #endif
}

This specialization also works with 32-bit integers, signed or unsigned, so I have a big smelly pile of duplicates with only the type name different.

How do I route all instantiations of 4 byte POD types through this one template? (PS. I'm open to solving this in a different way, but in that case I'd like to know definitively whether or not it's possible to build these kind of meta-specialized templates.)


EDIT: Thanks everyone, after reading the answers and realizing that arithmetic is a better restriction than pod, I was inspired to write something. All the answers were useful but I could only accept one, so I accepted the one that appears to be structurally the same.

template<bool, bool> struct SwapEndian_ { template<typename T> static inline void _(T&); };
template<> template<typename T> inline void SwapEndian_<true, true>::_(T& ptr) {
    // ... stuff here ...
}
// ... more stuff here ...
template<typename T> inline void SwapEndian(T& ptr) {
    static_assert(is_arithmetic<T>::value, "Endian swap not supported for non-arithmetic types.");
    SwapEndian_<sizeof(T) & (8 | 4), sizeof(T) & (8 | 2)>::template _<T>(ptr);
}
like image 839
MickLH Avatar asked Dec 15 '22 16:12

MickLH


2 Answers

When in doubt, tag dispatch.

This implementation has 2 traits -- is_pod and get_sizeof_t. The base override dispatches to SwapEndians with those two traits tagged. There is also a is_pod override, and an override (which I'd advise =deleteing) for non-pod types.

Extension to new traits and types is relatively easy.

template<size_t n>
using sizeof_t = std::integral_constant<size_t, n>;
template<class T>
using get_sizeof_t = sizeof_t<sizeof(T)>;

template <class T>
void SwapEndian(T& t, std::true_type /*is pod*/, sizeof_t<4>) {
  std::cout << "4 bytes!\n";
  // code to handle 32 bit pods
}
template <class T, size_t n>
void SwapEndian(T& t, std::true_type /*is pod*/, sizeof_t<n>) {
  std::cout << "pod\n";
  // code to handle generic case
}
template <class T, size_t n>
void SwapEndian(T& t, std::false_type /*is pod*/, sizeof_t<n>) {
  std::cout << "not pod\n";
  // probably want to =delete this overload actually 
}
template<class T>
void SwapEndian(T& t) {
    SwapEndian(t, std::is_pod<T>{}, get_sizeof_t<T>{});
}

I am not sure if this is a good idea, but the above should do it.

Uses some C++14 features. Assumes CHAR_BIT is 8.

You should only rarely specialize template functions. Instead overload. Tag dispatching gives you the power of overload resolution to dispatch what code to run at compile time.

live example

like image 169
Yakk - Adam Nevraumont Avatar answered Dec 25 '22 22:12

Yakk - Adam Nevraumont


I'm using a separate SwapEndian and SwapEndianImpl so that we can use template deduction and partial specialization.

template<bool> struct SwapEndianImpl
{
    template<typename t> static inline void Func(t& n);
};
template<> template<typename t> void SwapEndianImpl<false>::Func(t& n)
{
    std::cout << "not 32bit pod" << std::endl;
}
template<> template<typename t> void SwapEndianImpl<true>::Func(t& n)
{
    std::cout << "32bit pod" << std::endl;
}

template<typename t> inline void SwapEndian(t& n)
{
    SwapEndianImpl<std::is_pod<t>::value && sizeof(t) == (32 / CHAR_BIT)>::template Func<t>(n);
}

I believe that this is a better way to go than SFINAE if you specialize to more than two conditions.

like image 43
Michael Gazonda Avatar answered Dec 25 '22 23:12

Michael Gazonda