乘以子类型时编译器选择了错误的运算符*
Posted
技术标签:
【中文标题】乘以子类型时编译器选择了错误的运算符*【英文标题】:Wrong operator* chosen by compiler when multiplying subtypes 【发布时间】:2015-10-26 02:01:30 【问题描述】:我在自己的库中编写了一个通用的 Matrix 类,其中包含 +、-、* 等不错的运算符。特别是它有(函数体并不重要,你可以忽略它们,但我稍后还是会引用它,所以有它很好):
template<typename T, int X, int Y>
Matrix<T,Y,X> operator*(const Matrix<T,Y,X> & left, const Matrix<T,Y,X> & up)
Matrix<T,Y,X> result = Matrix<T,Y,X>::Zero;
for (unsigned int j = 0; j<Y; j++)
for (unsigned int i = 0; i<X; i++)
for (unsigned int k = 0; k<X; k++)
result[j][k] += left[j][i] * up[i][k];
return result;
template<typename T, int Y, int X, typename U>
Matrix<T,Y,X> operator*(const Matrix<T,Y,X> & left, const U & right)
// Expected to handle build-in types
Matrix<T, Y, X> result = Matrix<T, Y, X>::Zero;
for (int j = 0; j < Y; ++j)
for (int i = 0; i < X; ++i)
result[j][i] += left[j][i] * right;
return result;
然后我写了Matrix4x4
,Matrix
的一个子类型,专门用于旋转和平移等 3D 转换,因此它具有用于此目的的成员函数。当然,Matrix4x4
是个坏名字,我保证我会解决这个问题。
在使用Matrix4x4
的代码中的某个点我使用operator*
:
// std::vector<Matrix4x4> mstackvertices(Matrix4x4::Identity);
mstackvertices.push_back(mstackvertices.back() * m_camera.m_projectionmatrix);
这里的m_camera.m_projectionmatrix
也是Matrix4x4
。
这应该调用第一个 operator*
,但属于第二个,因为 gcc 在第二个重载中给我一个错误,在以下行:
result[j][i] += left[j][i] * right;
错误信息:
Matrix.hpp|169|error: no match for ‘operator*’ (operand types are ‘const float’ and ‘const swegl::Matrix4x4’)|
Matrix.hpp|169|note: candidates are:|
...
我的猜测是 Matrix4x4
不完全是 Matrix
,而只是一个子类型,一些规则适用于使 gcc 选择不涉及类型转换的最佳重载。
我不确定如何解决这个问题。我已经考虑了几种解决方案,但似乎都不是很好:
删除将接收内置类型的运算符,从而强制编译器选择唯一剩余的重载。这可行,但迫使我从看似完美的库中删除一个功能。 使用组合而不是继承,并根据Matrix
重载所有相关的Matrix4x4
运算符。
在Matrix4x4
中重新实现operator*
。会有重复的代码,或者,如果我能设法通过强制转换调用正确的 Matrix::operator*
重载,那仍然会很麻烦。
创建Matrix4x4::operator Matrix<float,4,4>()
。它似乎不起作用,但我也可能在那里做错了什么。无论如何,我知道这会创建一个不受欢迎的对象副本。
这就是我现在的位置。还有什么想法吗?也许我首先做错了什么? 我相信我会从中学到一些东西,所以非常欢迎任何帮助(:
编辑:
Matrix
和 Matrix4x4
的定义如下:
template<typename T, int Y, int X>
class Matrix
private:
T data[Y][X];
...
;
class Matrix4x4 : public Matrix<float,4,4>
...
;
【问题讨论】:
我们是否应该猜测模板参数是什么,或者Matrix
和Matrix4x4
的定义是什么样的?
认为这无关紧要。无论如何,善意地询问会有所帮助。
AxB 乘以 BxC 而不是 AxB。
typename U
在第二个函数中被推导出为Matrix4x4 const&
,这比Matrix<float,4,4> const&
更好的匹配,你必须限制可以与 SFINAE 一起使用的第二个函数的类型.另请注意,函数返回 Matrix<T,Y,X>
而不是派生类,这可能不是您想要的。
是的,@Yakk。为了便于阅读,我选择简化代码。
【参考方案1】:
使用 Koenig 运算符,如下所示:
template<class T, int X, int Y>
class Matrix
// ...
public:
// calculates the return value of `T*U`:
template<class U>
using R=decltype(std::declval<T const&>()*std::declval<U const&>());
// maybe addin a `decay_t` to the above, if required. (But who returns
// a reference from binary `*`?)
// Multiplication by a matrix on the RHS
// The RHS dimension of this matrix, and the LHS dimension
// of the RHS matrix, must match. Accepts matrices with a
// different underlying T.
template<class U, int Z>
Matrix<R<U>,X,Z> operator*(Matrix<U,Y,Z>const& rhs)const;
// you can implement this operator here, or you can do it below
// in the same header file.
// This is the Koenig operator. It is a friend operator that
// is *not* a template, where the left hand side is a scalar of
// type T, and the right hand side is our own type.
friend Matrix<R<T>,X,Y> operator*(
T const& lhs, Matrix const& rhs
)
// implement here
;
成员矩阵乘法比非成员更好地处理歧义。友元运算符就是我所说的 Koenig 运算符,必须在类中内联实现。您可以调用另一个函数并实现该函数。
你也可以乱用 sfinae 或标签调度,但上面的内容是干净和简单的。请注意,标量只允许在 lhs 上使用,因为 Matrix * Scalar
是......古怪。 Scalar * Matrix
更传统。
【讨论】:
有趣!decltype
实际上删除了错误的 operator*
: template<typename T, int X, int Y, typename U, int Z> Matrix<decltype(std::declval<T const&>()*std::declval<U const&>()),Y,X> operator*(const Matrix<T,Y,X> & left, const Matrix<U,X,Z> & up)
。但是随后operator+=
陷入了同样的问题,不可能将 decltype 放入返回类型中,因为它必须返回this
的类型。另外,我不确定您的 Koenig 运营商如何提供帮助。 operator* 不需要它,+= 也无济于事。还是这样?
@gab declryoe ssfinae 不需要上述工作。它应该与 `+=`` 一起使用。【参考方案2】:
正如 Yakk 建议的那样,使用decltype(Y*U)
作为返回类型允许删除类型不能相乘的重载,从而强制编译器使用正确的版本。据我了解,这是对 SFINAE 的使用:
template<typename T, int X, int Y, typename U, int Z>
Matrix<decltype(std::declval<T const&>()*std::declval<U const&>()),Y,X>
operator*(const Matrix<T,Y,X> & left, const Matrix<U,X,Z> & up)
Matrix<T,Y,Z> result = Matrix<T, Y, Z>::Zero;
for (unsigned int j = 0; j<Y; j++)
for (unsigned int i = 0; i<X; i++)
for (unsigned int k = 0; k<Z; k++)
result[j][k] += left[j][i] * up[i][k];
return result;
template<typename T, int Y, int X, typename U>
Matrix<decltype(std::declval<T const&>()*std::declval<U const&>()),Y,X>
operator*(const Matrix<T,Y,X> & left, const U & right)
Matrix<T, Y, X> result = Matrix<T, Y, X>::Zero;
for (int j = 0; j < Y; ++j)
for (int i = 0; i < X; ++i)
result[j][i] += left[j][i] * right;
return result;
但是有些运算符没有可以用这些术语声明的返回类型,例如operator+=
。充其量它必须返回与左侧类型相同的类型,*this
。对于这些情况,enable_if
可以按如下方式使用:
template<typename T, typename U, typename = decltype(std::declval<T const&>()+std::declval<U const&>())>
struct can_add
static const bool value = true;
;
template<typename T, typename U>
struct can_add<T,U>
static const bool value = false;
;
template<typename T, int Y, int X>
class Matrix
private:
T data[Y][X];
public:
// ...
template<typename U, typename = std::enable_if<freon::can_add<T,U>::value>>
void operator+=(const Matrix<U, Y, X> & other)
for (int j = 0; j < Y; ++j)
for (int i = 0; i < X; ++i)
data[j][i] += other[j][i];
template<typename U, typename = std::enable_if<freon::can_add<T,U>::value>>
void operator+=(const U & other)
for (int j = 0; j < Y; ++j)
for (int i = 0; i < X; ++i)
data[j][i] += other;
【讨论】:
以上是关于乘以子类型时编译器选择了错误的运算符*的主要内容,如果未能解决你的问题,请参考以下文章
c语言学习笔记|编译过程|数据类型|scanf|运算符|选择与循环|指针的传递