【问题标题】:Effective use of enable_if with C++ templates to avoid class specialization在 C++ 模板中有效使用 enable_if 以避免类特化
【发布时间】:2014-01-06 23:42:52
【问题描述】:

我无法编译我的代码。 clang、g++ 和 icpc 都会给出不同的错误信息,

在进入问题本身之前先了解一下背景:

我现在正在研究用于处理矩阵的模板类层次结构。数据类型(浮点型或双精度型)和“实施策略”有模板参数——目前这包括带有循环和英特尔 MKL 版本的常规 C++ 代码。以下是一个简短的摘要(请忽略此处缺少前向引用等 - 这与我的问题无关):

// Matrix.h

template <typename Type, typename IP>
class Matrix : public Matrix_Base<Type, IP>;

template <typename Matrix_Type>
class Matrix_Base{
    /* ... */

    // Matrix / Scalar addition
    template <typename T>
    Matrix_Base& operator+=(const T value_) { 
      return Implementation<IP>::Plus_Equal(
          static_cast<Matrix_Type&>(*this), value_);

    /* More operators and rest of code... */
    };

struct CPP;
struct MKL;

template <typename IP>
struct Implementation{
/* This struct contains static methods that do the actual operations */

我现在遇到的麻烦与实现类的实现有关(没有双关语)。我知道我可以使用实现模板类的特化来特化template &lt;&gt; struct Implementation&lt;MKL&gt;{/* ... */}; 但是,这将导致大量代码重复,因为有许多运算符(例如矩阵标量加法,减法,...)通用版和专用版都使用相同的代码。

所以,相反,我认为我可以摆脱模板专业化,只使用 enable_if 为那些在使用 MKL(或 CUDA 等)时具有不同实现的运算符提供不同的实现。

事实证明,这比我最初预期的更具挑战性。第一个 - operator += (T value_) 工作正常。我添加了一个检查只是为了确保参数是合理的(如果它是我的麻烦的根源,这可以消除,我怀疑)。

template <class Matrix_Type, typename Type, typename enable_if< 
    std::is_arithmetic<Type>::value  >::type* dummy = nullptr>
static Matrix_Type& Plus_Equal(Matrix_Type& matrix_, Type value_){
    uint64_t total_elements = matrix_.actual_dims.first * matrix_.actual_dims.second;
    //y := A + b

    #pragma parallel 
    for (uint64_t i = 0; i < total_elements; ++i)
        matrix_.Data[i] += value_; 

    return matrix_;
}

但是,我很难弄清楚如何处理operator *=(T value_)。这是因为 floatdouble 对 MKL 有不同的实现,但不是在一般情况下。

这是声明。请注意,第三个参数是一个虚拟参数,是我尝试强制函数重载,因为我不能使用部分模板函数特化:

template <class Matrix_Type, typename U, typename Type = 
    typename internal::Type_Traits< Matrix_Type>::type, typename  enable_if<
    std::is_arithmetic<Type>::value >::type* dummy = nullptr>

static Matrix_Type& Times_Equal(Matrix_Type& matrix_, U value_, Type dummy_ = 0.0);

一般情况的定义。 :

template<class IP>
template <class Matrix_Type, typename U, typename Type,  typename enable_if<
    std::is_arithmetic<Type>::value >::type* dummy>
Matrix_Type& Implementation<IP>::Times_Equal(Matrix_Type& matrix_, U value_, Type){

    uint64_t total_elements = matrix_.actual_dims.first * matrix_.actual_dims.second;

    //y := A - b
    #pragma parallel
    for (uint64_t i = 0; i < total_elements; ++i)
        matrix_.Data[i] *= value_;

    return matrix_;
}

当我尝试实现 MKL 的特化时,麻烦就开始了:

template<>
template <class Matrix_Type, typename U, typename Type, typename enable_if<
    std::is_arithmetic<Type>::value >::type* dummy>
Matrix_Type& Implementation<implementation::MKL>::Times_Equal(
    Matrix_Type& matrix_, 
    U value_,
    typename enable_if<std::is_same<Type,float>::value,Type>::type)
{

    float value = value_;

    MKL_INT total_elements = matrix_.actual_dims.first * matrix_.actual_dims.second;
    MKL_INT const_one = 1;

    //y := a * b
    sscal(&total_elements, &value, matrix_.Data, &const_one);
    return matrix_;
}

这给了我一个clang错误:

_错误:“Times_Equal”的外线定义与“实施”中的任何声明都不匹配_

在 g++ 中(稍微缩短)

_错误: 'Matrix_Type& Implementation::Times_Equal(...)' 的模板 ID `Times_Equal' 与任何模板声明都不匹配。

如果我将第三个参数更改为 Type,而不是启用 enable_if,则代码编译得非常好。但是当我这样做时,我看不到如何为 float 和 double 分别实现。

任何帮助将不胜感激。

【问题讨论】:

  • typename enable_if&lt; std::is_arithmetic&lt;Type&gt;::value &gt;::type* dummy = nullptr 也可以实现为typename = enable_if&lt; std::is_arithmetic&lt;Type&gt;::value &gt;::type,它有点短;)
  • “请注意,第三个参数是一个虚拟参数,是我尝试强制函数重载的尝试”对于(正常)重载解析,使用默认参数的参数将被忽略。 (它们可能仍会在函数模板 IIRC 的部分排序中发挥作用。)
  • @DyP,你的第二点很好;但是,我的理解是,如果 Type 不等于 'float',那么最终参数将导致替换失败,并且将排除实现。显然,我错了,因为它不会编译;但是,我真的不明白我在这里做错了什么。请注意,如果没有默认参数,我的一般案例实现会遇到麻烦。我想我总是可以将其他参数传递给实现,但我宁愿不这样做。而且我更多宁愿让编译器推导出模板参数。
  • 您遇到编译器错误,提示您定义的方法与类定义中的任何声明都不匹配。也许你应该声明你正在定义的重载?
  • 老实说,我会使用更少的enable_if 和更多的标签调度。我会做一个RootImplementation&lt;Derived&gt;,它使用CRTP 发送回你的Implmentation&lt;X&gt;,这是专门基于MKL 与标准的。 SFINAE,但通常是一个糟糕的设计决策。

标签: c++ templates c++11 template-specialization partial-specialization


【解决方案1】:

我认为使用 std::enable_if 实现这将是非常乏味的,因为一般情况下必须使用 enable_if 来实现,如果它不适合其中一种专业化,则将其打开。

具体解决您的代码,我认为编译器无法在您的 MKL 专业化中推断出 Type,因为它隐藏在 std::enable_if 中,因此永远不会调用此专业化。

除了使用 enable_if 你也许可以这样做:

#include<iostream>

struct CPP {};
struct MKL {};

namespace Implementation
{
   //
   // general Plus_Equal
   //
   template<class Type, class IP>
   struct Plus_Equal
   {
      template<class Matrix_Type>
      static Matrix_Type& apply(Matrix_Type& matrix_, Type value_)
      {
         std::cout << " Matrix Plus Equal General Implementation " << std::endl;
         // ... do general Plus_Equal ...
         return matrix_;
      }
   };

   //
   // specialized Plus_Equal for MKL with Type double
   //
   template<>
   struct Plus_Equal<double,MKL>
   {
      template<class Matrix_Type>
      static Matrix_Type& apply(Matrix_Type& matrix_, double value_)
      {
         std::cout << " Matrix Plus Equal MKL double Implementation " << std::endl;
         // ... do MKL/double specialized Plus_Equal ...
         return matrix_;
      }
   };
} // namespace Implementation

template <typename Type, typename IP, typename Matrix_Type>
class Matrix_Base
{  
   public:
   // ... matrix base implementation ...

   // Matrix / Scalar addition
   template <typename T>
   Matrix_Base& operator+=(const T value_) 
   { 
      return Implementation::Plus_Equal<Type,IP>::apply(static_cast<Matrix_Type&>(*this), value_);
   }

   // ...More operators and rest of code...
};

template <typename Type, typename IP>
class Matrix : public Matrix_Base<Type, IP, Matrix<Type,IP> >
{
   // ... Matrix implementation ...
};

int main()
{
   Matrix<float ,MKL> f_mkl_mat;
   Matrix<double,MKL> d_mkl_mat;

   f_mkl_mat+=2.0; // will use general plus equal
   d_mkl_mat+=2.0; // will use specialized MKL/double version

   return 0;
}

这里我使用了类专业化而不是 std::enable_if。我发现你和你的例子中的IPTypeMatrix_Type类型非常不一致,所以我希望我在这里正确使用它们。

关于 std::enable_if 上的 cmets。我会使用表格

template<... , typename std::enable_if< some bool >::type* = nullptr> void func(...);

结束

template<... , typename = std::enable_if< some bool >::type> void func(...);

因为它使您能够执行一些其他形式无法实现的函数重载。

希望你可以使用它:)

编辑 20/12-13:重新阅读我的帖子后,我发现我应该明确地执行 CRTP(Curiously Recurring Template Pattern),这是我在上面的代码中添加的。我将TypeIP 都传递给Matrix_Base。如果您觉得这很乏味,可以改为提供一个矩阵特征类,Matrix_Base 可以从中取出它们。

template<class A>
struct Matrix_Traits;

// Specialization for Matrix class
template<class Type, class IP>
struct Matrix_Traits<Matrix<Type,IP> >
{
   using type = Type;
   using ip   = IP;
};

那么Matrix_Base现在将只接受一个模板参数,即矩阵类本身,并从特征类中获取类型

template<class Matrix_Type>
class Matrix_Base
{
   // Matrix / Scalar addition
   template <typename T>
   Matrix_Base& operator+=(const T value_) 
   { 
      // We now get Type and IP from Matrix_Traits
      return Implementation::Plus_Equal<typename Matrix_Traits<Matrix_Type>::type
                                      , typename Matrix_Traits<Matrix_Type>::ip
                                      >::apply(static_cast<Matrix_Type&>(*this), value_);
   }
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-13
    • 1970-01-01
    • 1970-01-01
    • 2019-08-30
    相关资源
    最近更新 更多