部分专业化和SFINAE

时间:2017-02-11 12:30:25

标签: c++

假设我有以下Matrix模板类,并且需要将向量表示为1 x RowSize或ColSize x 1矩阵(这样我可以重用许多与向量兼容的矩阵运算符:乘以2个矩阵,乘以矩阵通过标量等):

template <class T, size_t ColumnSize, size_t RowSize>
struct Matrix {
    T [ColumnSize][RowSize];
}

我有两个问题:

1)如果我没有弄错,我可以通过部分特化或在Matrix方法上使用SFINAE来实现这一点(例如,当ColSize或RowSize为1时启用&#39; length&#39;方法)。上述选项的优缺点是什么?

2)如果我选择使用部分特化,有没有办法为行和列向量定义一个特化,而不是:

template <class T, size_t ColumnSize>
struct Matrix<T, ColumnSize, 1> {
    T length() const;

    T [ColumnSize][RowSize];
}

template <class T, size_t RowSize>
struct Matrix<T, 1, RowSize> {
    T length() const;

    T [ColumnSize][RowSize];
}

2 个答案:

答案 0 :(得分:1)

这实际上取决于要求是“一般矩阵必须没有长度方法”(然后应该使用SFINAE或继承),或者“length不能在一般矩阵上调用”(那么length正文中的static_assert适用)。第三种选择是不做任何事情并使length适用于通用矩阵,但是仍有其他操作仅适用于向量。

对于“一般矩阵必须没有长度方法”。为了节省空间,我将使用int和更短的符号名称。您应该使用int_而不是std::integral_constant。由于语言限制,如果参数是非类型参数,则禁止使用更复杂的计算,因此需要int_包装器。因此,我们将paramer作为一个类型,并将值包装到其中。以下不使用SFINAE,而是继承。使用向量混合基类的d(),您可以在混合类中随时访问向量的数据。

template<int> struct int_;

template<typename D, typename S>
struct V { };

template<typename T, int A, int B>
struct M : V<M<T, A, B>, int_<A * B>> {
   T data[A][B];
};

template<typename T, int A, int B>
struct V<M<T, A, B>, int_<A + B - 1>> { 
   int length() const { return A * B; }

   M<T, A, B> *d() { return static_cast<M<T, A, B>*>(this); }
   const M<T, A, B> *d() const { return static_cast<const M<T, A, B>*>(this); }
};

现在是

int main() { 
   M<float, 1, 3> m1; m1.length();
   M<float, 3, 1> m2; m2.length();
   // M<float, 3, 2> m3; m3.length(); error
}

对于“length不能在一般矩阵上调用”,你可以使用“static_assert”

template<typename T, int A, int B>
struct M {
   int length() const {
      static_assert(A == 1 || B == 1, "must not be called on a matrix!");
      return A * B;
   }

   T data[A][B];
};

选择最合适的

答案 1 :(得分:0)

SFINAE只能根据自己的参数禁用模板声明。使用封闭类的参数禁用非模板成员函数(例如length)有点不自然。该技术看起来像这样:

template <class T, size_t RowSize, size_t ColumnSize>
struct Matrix {
    // SFINAE turns a non-template into a template.
    // Introduce a fake dependency so enable_if resolves upon function call.
    template< typename size_t_ = size_t >
    static constexpr
    // Now write the actual condition within the return type.
    std::enable_if_t< RowSize == 1 || ColumnSize == 1
    , size_t_ > length() const;
        { return RowSize * ColumnSize; }

    T [ColumnSize][RowSize];
}

如果你能忍受这种丑陋,那么你就能得到你想要的东西:所需类型的功能,当不满足条件时它会完全消失。不需要其他支持。

另一方面,部分特化会影响整个类定义。由于在每个部分专业化中复制整个类的设计通常很差,因此继承被用作Johannes所描述的。

为了给他的答案添加一个替代方案,SFINAE可以在部分专业化中使用,以避免聪明的代数和int_问题。

// Add "typename = void" for idiomatic class SFINAE.
template<size_t RowSize, size_t ColumnSize, typename = void>
struct maybe_vector_interface { }; // Trivial specialization for non-vectors

// Partial specialization for vectors:
template<size_t RowSize, size_t ColumnSize>
struct maybe_vector_interface< RowSize, ColumnSize,
    std::enable_if_t< RowSize == 1 || ColumnSize == 1 > > { 
    static constexpr int length() const
        { return RowSize * ColumnSize; }
};

template<typename T, size_t RowSize, size_t ColumnSize>
struct Matrix
    : maybe_vector_interface<RowSize, ColumnSize> {
    T data[RowSize][ColumnSize];
};