假设我有以下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];
}
答案 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];
};