部分专业化和SFINAE

  • 本文关键字:SFINAE 专业化 c++
  • 更新时间 :
  • 英文 :


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

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

我有两个问题:

1) 如果我没有错的话,我可以通过部分专业化或在矩阵方法上使用SFINAE来实现这一点(例如,当ColSize或RowSize为1时启用"length"方法)。上述方案的优缺点是什么?

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];
}

这实际上取决于要求是"通用矩阵不得具有长度方法"(然后应使用SFINAE或继承),还是"不能在通用矩阵上调用length"(然后length主体内部的static_assert适用)。第三种选择是什么都不做,并使length适用于一般矩阵,但仍有其他操作仅适用于向量。

对于"一般矩阵不能有长度方法"。为了节省空间,我将使用int和较短的符号名称。应该使用std::integral_constant而不是int_。由于语言限制,如果参数是非类型参数,则禁止专门处理更复杂的计算,因此需要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];
};

选择最合适的

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];
};

最新更新