简体   繁体   English

类型的部分模板专业化

[英]Partial template specialization for type

I have a class vec_base defined like so: 我有一个像这样定义的类vec_base

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 :/ 我很难实现这个,因为允许的宽松大小(4或2的任意倍数),如果它特别是2或4我知道我可以执行完全专业化,但事实并非如此:/

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. 这就是我到目前为止的想法,因为你认为我只是把自己的工作推迟到了。

Simple solution 简单解决方案

The most simple technique would be using std::enable_if and std::same similar to what you did: 最简单的技术是使用std::enable_ifstd::same类似于你所做的:

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 为什么可以避免使用typename U = void

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. std::enable if背后的想法称为SFINAE原则,它基本上表明无论何时实例化模板都不起作用,编译器都不会出错,而只是从所有重载集和类似的名称解析中删除一个定义。

The implementation behind std::enable_if specializes the class template, so that std::enable_if<false> does not contain a member type at all. std::enable_if背后的实现专门化了类模板,因此std::enable_if<false>根本不包含成员type Therefore using that type member will cause an error that (due to SFINAE) removes this specialization from consideration. 因此,使用该类型成员将导致(由于SFINAE)从考虑中删除此特化的错误。

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. 由于您的模板已经包含一个类型参数,您可以改为使用该类型参数,因为std::enable_if<true>::type实际上与它的第二个参数相同,一个类型参数只默认为void ,但是当然要设定。

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 为什么typename Tstd::same不是必需的

From this you can also see that you could remove the typename T of your specializations and drop the usage of std::is_same . 从这里你还可以看到你可以删除你的特化的typename T并删除std::is_same的用法。 T must always be a specific type after all... 毕竟T必须始终是特定的类型......

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

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. 我会选择一个is_special<T, N>类型特征,它为所有不适合您的SSE解决方案的类型提供std::false_type ,否则为std::true_type

#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 使用enable_if专门化您的模板

You can then do a std::enable_if on this type trait 然后,您可以在此类型特征上执行std::enable_if

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. 对于128位长的双打,只需添加一个新的专业化。

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. 测试您想要的案例和您不想调用SSE代码的案例非常重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM