tags:

views:

112

answers:

4

I would like to provide a templated function, that varies its implementation (->specialization) according to the sizeof the template type.

Something similar to this (omitted typecasts), but without the if/elseif:

template<class T>
T byteswap(T & swapIt)
{
    if(sizeof(T) == 2)
    {
        return _byteswap_ushort (swapIt);
    }
    else if(sizeof(T) == 4)
    {
        return _byteswap_ulong(swapIt);
    }
    else if(sizeof(T) == 8)
    {
        return _byteswap_uint64(swapIt);
    }
            throw std::exception();
}

I know there are many roads to reach my goal, but since I try to learn about SFINAE and type traits I'm particularly interested in solutions using those techniques to decide at compile time which specialization to choose and which calls are not admitted.

Perhaps implementing a class trait is_4ByteLong and using boost::enable_if...

I have to admit, I'm stuck right now, so I thank you for any help or advice

+10  A: 

You don't need SFINAE or type traits. Vanilla template specialization is enough. Of course it must be specialized on structs as C++(98) doesn't support function template partial specialization.

template <typename T, size_t n>
struct ByteswapImpl
/*
{
  T operator()(T& swapIt) const { throw std::exception(); }
}
*/    // remove the comments if you need run-time error instead of compile-time error.
;

template <typename T>
struct ByteswapImpl<T, 2> {
  T operator()(T& swapIt) const { return _byteswap_ushort (swapIt); }
};

// ...

template <typename T>
T byteswap(T& swapIt) { return ByteswapImpl<T, sizeof(T)>()(swapIt); }
KennyTM
Nice answer but I would not make a default implementation of `ByteswapImpl`. That way, you'll just get a compile error if the needed specialization does not exist.
Job
And the function can be static so that no object has to be created (similar to `boost::numeric_cast` and `boost::numeric_conververt`).
Philipp
I was under the impression that only function template *partial* specialization isn't supported... Am I wrong?
Job
@Job: you're right, but since `T` isn't precised here we are effectively speaking about partial specialization.
Matthieu M.
@Matthieu: Oh yes you're right. Stupid I didn't see that :-)
Job
@Job: Thanks, added back the "partial".
KennyTM
+3  A: 

Simply make an auxiliary class that takes the size as a template argument:

#include <cstddef>
#include <iostream>


template<std::size_t Size>
struct ByteSwapper { };

template<>
struct ByteSwapper<2> {
  static unsigned short swap(unsigned short a) {
    return 2 * a;
  }
};

template<typename T>
T byteswap(const T& a) {
  return ByteSwapper<sizeof(T)>::swap(a);
}


int main() {
  unsigned short s = 5;
  std::cout << byteswap(s) << std::endl;
  unsigned int i = 7;
  // std::cout << byteswap(i) << std::endl; // error
}
Philipp
+2  A: 

Just for the sake of demonstrating enable_if in action, since you spoke about it:

template <class T>
typename boost::enable_if_c< sizeof(T) == 2, T >::type
swapIt(T& rhs) { return _byteswap_short(rhs); }

template <class T>
typename boost::enable_if_c< sizeof(T) == 4, T >::type
swapIt(T& rhs) { return _byteswap_long(rhs); }

etc...

And of course, instead of throwing, there is just no implementation if the type doesn't meet any of the requirement and thus you have a compile time error.

Two notes:

  • Use of typename and ::type are mandatory
  • I used enable_if_c because my expression evaluates to a boolean value directly, whereas enable_if requires a type containing a ::value member which is a boolean.
Matthieu M.
+1  A: 

I can propose the following method: Its benefit is that you don't have to throw an exception if the operand isn't of the valid size. It just won't link. So that you have the error checking at build time.

template<int size>
void byteswapInPlace(void* p);

template<> void byteswapInPlace<1>(void* p) { /* do nothing */ }

template<> void byteswapInPlace<2>(void* p)
{
    _byteswap_ushort((ushort*) p);
}

template<> void byteswapInPlace<4>(void* p)
{
    _byteswap_ulong((ulong*) p);
}

template<> void byteswapInPlace<8>(void* p)
{
    _byteswap_uint64((uint64*) p);
}


template<class T>
T byteswap(T & swapIt)
{
    byteswapInPlace<sizeof(T)>(&swapIt);
    return swapIt;
}
valdo
You could instead put some sort of static assertion (e.g `BOOST_STATIC_ASSERT`) in the base case. No need to wait until linking to get an error.
UncleBens
Yes, you're right, this is what I'd do: default imlpementation which generates a compiler error.
valdo