【问题标题】:Avoiding code duplication in template specializations避免模​​板特化中的代码重复
【发布时间】:2017-09-12 14:47:30
【问题描述】:

我有一个矩阵类

template<typename Type, size_t Rows, size_t Columns> class Matrix
{
public:
    Type Entries[Rows][Columns];

    // Operators, etc.
};

然后我需要对这个类进行特化(对于方阵等),这意味着我需要为每个特化重写整个类的实现。然后我开始考虑避免代码重复的方法。

想到的第一个想法是在基础模板中定义专门化的函数,但删除它们,这样它们就不能在基础模板中使用,并在类之外定义专门化。

// Inside class definition
auto GetDeterminant() const -> T = delete;

// Outside class definition
template<typename Type, size_t Size> Matrix<T, Size, Size>::GetDeterminant() const -> T
{
    // Implementation
}

但后来,在遇到一些错误后,我了解到部分专业化的功能模板是不允许的。所以我尝试了这些方法

方法一:

template<typename Derived, typename Type, size_t Rows, size_t Columns> class MatrixBase
{
public:
    T Entries[Row][Columns];

    // Operators, etc.
}

template<typename Type, size_t Rows, size_t Columns> class Matrix : public MatrixBase<Matrix<Type, Rows, Columns>, Rows, Columns>
{
    // General MxN matrix stuff
}

template<typename Type, size_t Size> class Matrix<T, Size, Size> : public MatrixBase<Matrix<T, Size, Size>, Size, Size>
{
public:
    // Specific MxM matrix stuff
}

在这里,我实现了 CRTP,就像在另一个 question 上看到的那样,但是我觉得抽象有点不需要,尽管可能有更好的方法。所以我想出了另一种方法。

方法二:

template<typename T, size_t Rows, size_t Columns> class Matrix
{
public:
    T Entries[Rows][Columns]

    auto GetDeterminant() const -> T
    {
        static_assert(Rows == Columns);

        // Some computations
    }
}

这似乎很好,如果有人试图在非方阵上使用它,他们会得到一个编译时错误。

问题:

有没有办法只使用模板来解决这个问题?还是我应该坚持使用其中一种方法?

【问题讨论】:

  • 我喜欢static_assert 方法。

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


【解决方案1】:

我提出一种方法 2 的变体,使用 SFINAE 来启用或禁用 GetDeterminant()

template <typename Type, size_t Rows, size_t Columns>
class Matrix
 {
   public:
      Type Entries[Rows][Columns];

      template <size_t R = Rows>
      typename std::enable_if<R == Columns, Type>::type GetDeterminant () const
       {
         // some computations
         return {};
       }
 };

如果您想避免GetDeterminant() 可能被启用显式模板R 值的风险,如下所示

Matrix<int,  3, 3>  m0;
Matrix<long, 3, 2>  m1;

auto d0 = m0.GetDeterminant(); // compile
//auto d1 = m1.GetDeterminant(); // compilation error
auto d1 = m1.GetDeterminant<2>(); // compile!!!

您可以维护static_assert(类似于static_assert(R == Rows, "!");)或者您可以改进std::enable_if 测试,如下所示

  template <size_t R = Rows>
  typename std::enable_if<(R == Columns) && (R == Rows), Type>::type
      GetDeterminant () const
   {
     // some computations
     return {};
   }

【讨论】:

    【解决方案2】:

    首先,引用 aschelper 的评论:

    我喜欢static_assert 方法。

    我也是。它清晰、简洁,可以进一步给出有意义和解释性的错误信息。


    helper struct(在实现命名空间中)

    namespace matrix_impl {
      template<std::size_t Rows, std::size_t Columns>
      struct determinate_calculation {
        int operator()(int const (& entries)[Rows][Columns]) const = delete;
      };
      // or just don't define the class template
    
      template<std::size_t Size>
      struct determinate_calculation<Size, Size> {
        int operator()(int const (& entries)[Size][Size]) const {
          return entries[0][0]; // Woah, calculation wrong as hell ;)
        }
      };
    }
    
    template<std::size_t Rows, std::size_t Columns>
    struct Matrix {
      int entries[Rows][Columns];
    
      int get_determinate() const {
        return matrix_impl::determinate_calculation<Rows, Columns>{}(entries);
      }
    };
    
    • (-1) 需要包含已删除函数的类模板定义或类模板声明。这只是样板文件。
    • (-1) 需要一个包含某些函数中实际代码的类模板特化。不仅仅是编写一个函数。
    • (-1/+1) 需要将Matrix 类的必需成员作为参数传递给函数。很好,因为它使依赖关系明确。不好,因为有很多成员,这可能会变得很麻烦(而且“嘿,让我们使用一个类来传递参数”只是添加到样板文件中)。

    std::enable_if

    max66 was faster,没什么好补充的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-23
      • 2014-03-07
      相关资源
      最近更新 更多