Pergunta

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.

Foi útil?

Solução

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

Outras dicas

Simpler yet:

template<typename T, std::size_t Size>
class vec_base
{
  public: vec_base() {
      std::cout << "I am generic, my size is " << Size << std::endl;
  }
};

template<std::size_t Size>
class vec_base<typename std::enable_if<Size % 4 == 0, float>::type, Size>
{
  public: vec_base() {
      std::cout << "I am specialized for float, my size is " << Size << std::endl;
  }
};

template<std::size_t Size>
class vec_base<typename std::enable_if<Size % 2 == 0, double>::type, Size>
{
  public: vec_base() {
      std::cout << "I am specialized for double, my size is " << Size << std::endl;
  }
};

Encapsulate your specialness in a type trait

I would go for a is_special<T, N> type trait that gives std::false_type for all types that do not fit into your SSE solution, and std::true_type otherwise.

#include <cstddef>
#include <iostream>
#include <type_traits>

// test whether a vector of floating points fits into B bits SSE solution
template<typename T, std::size_t N, std::size_t B = 128>
struct is_special
: 
    std::integral_constant<bool, 
        std::is_floating_point<T>::value && 8 * sizeof(T) * N == B
    > 
{};

I generally don't like putting raw conditions in my template specializations that make use of such traits, as this can lead to less maintainable code (especially if you re-use the type trait anywhere else).

Specialize your template using enable_if

You can then do a std::enable_if on this type trait

template<typename, std::size_t, typename = void>
class vec_base
{
public:
    static void print() { std::cout << "vec_base<T, N>\n"; };    
};

template<std::size_t N>  
class vec_base<float, N, typename std::enable_if<is_special<float, N>::value>::type>
{
public:
    static void print() { std::cout << "vec_base<float, 4 * K>\n"; };    
};

template<std::size_t N> 
class vec_base<double, N, typename std::enable_if<is_special<double, N>::value>::type>
{
public:
    static void print() { std::cout << "vec_base<double, 2 * K>\n"; };    
};

Note that the generic template does not need to know about the kind of special conditions that you use for the specializations. This is a manifestion of the Open/Closed principle: your code is closed for modification, but open for extensions. For 128 bit long doubles, just add a new specialization.

Testing the code in practice

You can call this code like this:

int main()
{
    vec_base<int, 6>::print();    // vec_base<T, N>
    vec_base<float, 1>::print();  // vec_base<T, N>
    vec_base<float, 4>::print();  // vec_base<float, 4 * K>
    vec_base<double, 1>::print(); // vec_base<T, N>
    vec_base<double, 2>::print(); // vec_base<double, 2 * K>
}

Live Example that prints the commented lines above. It's important to test both the cases you want and the cases you don't want to call SSE code.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top