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!
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.
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 */ };
typename U = void
can be avoidedThe 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 */ };
typename T
and std::same
are not necessaryFrom 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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With