Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial template specialization for type

I have a class vec_base defined like so:

template<typename T, std::size_t Size>
class vec_base;

and I would like to specialize it so that

vec_base<float, /* any multiple of 4 */>

and

vec_base<double, /* any multiple of 2 */>

can have specific members independently as apposed to, say

vec_base<int, 6>

which would have generic members that I have already defined

I'm having a tough time implementing this because of the lenient size allowed (any multiple of 4 or 2) if it were specifically 2 or 4 I know I could perform full specialization, but that isn't the case :/

How would I go about this? Any help at all is appreciated, I always love learning new language techniques!


EDIT

okay so I have this so far:

template<std::size_t Size>
struct is_div_by_4{static const bool value = (Size % 4 == 0);};
// have to define because of template requirements
// about not being dependent on other parameters

template<typename T, std::size_t Size, bool is_special>
class vec_base;

template<typename T, std::size_t Size>
class vec_base<T, Size, false>{
    // generic implementation
};

teplate<std::size_t Size>
class vec_base<float, Size, is_div_by_4<Size>::value>{
    // Special implementation (for sse, it's no secret)
};

but I haven't compiled it yet and I know it won't work, so please don't point that out; it's just what I have so far incase you thought I was just deferring my own work to SO.

like image 209
RamblingMad Avatar asked Apr 27 '14 05:04

RamblingMad


1 Answers

Simple solution

The most simple technique would be using std::enable_if and std::same similar to what you did:

template<typename T, std::size_t Size, typename U = void>
class vec_base { /* implement me generically */ };

template<typename T, std::size_t Size>
class vec_base<T, Size, typename std::enable_if<std::is_same<T, float>::value && Size % 4 == 0>::type>
{ /* implement me with 10% more awesome-sauce */ };

template<typename T, std::size_t Size>
class vec_base<T, Size, typename std::enable_if<std::is_same<T, double>::value && Size % 2 == 0>::type>
{ /* implement me with pepper instead */ };

Why the typename U = void can be avoided

The idea behind std::enable if is something called the SFINAE principle, which basically states that whenever instantiating a template does not work, the compiler will not error out, but instead just remove that one definition from all overload sets and similar name resolutions.

The implementation behind std::enable_if specializes the class template, so that std::enable_if<false> does not contain a member type at all. Therefore using that type member will cause an error that (due to SFINAE) removes this specialization from consideration.

Since your template already contains a type parameter, you could instead use that type parameter, since the std::enable_if<true>::type is actually the same as its second parameter, a type parameter that only defaults to void, but can of course be set.

Therefore, you can remove the last template parameter in the generic implementation completely and instead specialize like so:

template<typename T, std::size_t Size>
class vec_base<typename std::enable_if<std::is_same<T, float>::value && Size % 4 == 0, float>::type, Size>
{ /* implement me with 10% more awesome-sauce */ };

Why the typename T and std::same are not necessary

From this you can also see that you could remove the typename T of your specializations and drop the usage of std::is_same. T must always be a specific type after all...

template<std::size_t Size>
class vec_base<typename std::enable_if<Size % 4 == 0, float>::type, Size>
{
    friend vec_base operator+(vec_base const& lhs, vec_base const& rhs)
    { /* do some additions */
        return lhs;
    }
};

Adding more operators outside of the class is fairly simple:

// works for all vec_base variants
template<typename T, std::size_t Size>
vec_base<T, Size> operator-(vec_base<T, Size> const& lhs, vec_base<T, Size> const& rhs)
{ /* do some subtractions */
    return lhs;
}

// works only for the specialization float, divisible by 4
template<std::size_t Size>
typename std::enable_if<Size % 4 == 0, vec_base<float, Size>>::type
operator-(vec_base<float, Size> const& lhs, vec_base<float, Size> const& rhs)
{ /* do some salty computations */
    return lhs;
}

This actually works because the second version is strictly more restricted than the first version (every argument group that works with the special function also works for the generic one - but the generic one has some for which the special one will not work).

Fixing your attempt

Although you seem rather down about your attempt, here is how to adapt it to work as well (note that this solution is far more convoluted than the one shown above):

template<typename T, std::size_t Size, int mode =
    (std::is_same<T, float>::value && Size % 4 == 0) ? 1
    : (std::is_same<T, double>::value && Size % 2 == 0) ? 2
    : 0>
struct vec_base;

template<typename T, std::size_t Size>
struct vec_base<T, Size, 0>
{ static void hello() { ::std::cout << "hello all\n"; } };

template<std::size_t Size>
struct vec_base<float, Size, 1>
{ static void hello() { ::std::cout << "hello 4 floats\n"; } };

template<std::size_t Size>
struct vec_base<double, Size, 2>
{ static void hello() { ::std::cout << "hello 2 doubles\n"; } };

You would call it like so:

vec_base<float, 2>::hello(); // hello all
vec_base<float, 4>::hello(); // hello 4 floats
like image 107
gha.st Avatar answered Sep 18 '22 04:09

gha.st