编译器在乘以子类型时选择了错误的运算符*

Wrong operator* chosen by compiler when multiplying subtypes

我在自己的库中编写了一个通用矩阵 class,其中包含 +、-、* 等不错的运算符。特别是,它有(函数体不重要所以你可以忽略它们但我稍后仍然会引用它所以有它很好):

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

然后我写了 Matrix4x4Matrix 的一个子类型,专门用于旋转和平移等 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 选择不涉及类型转换的最佳重载。

我不确定如何解决这个问题。我考虑了几个解决方案,none 其中看起来不错:

这就是我现在的位置。还有其他想法吗?也许我首先做错了什么? 我确定我会从中学到一些东西,所以非常欢迎任何帮助 (:

编辑:

MatrixMatrix4x4的定义如所问:

template<typename T, int Y, int X>
class Matrix
{
private:
    T data[Y][X];
    ...
};

class Matrix4x4 : public Matrix<float,4,4>
{
    ...
};

按如下方式使用 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 运算符,必​​须在 class 中内联实现。您可以调用另一个函数并执行该函数。

您也可以随意使用 sfinae 或标签调度,但上面的内容非常简洁明了。请注意,标量只允许在 lhs 上使用,因为 Matrix * Scalar 是......古怪。 Scalar * Matrix 比较常规。

正如 Yakk 所建议的那样,使用 decltype(Y*U) 作为 return 类型允许删除类型不能相乘的重载,从而迫使编译器使用正确的版本。据我了解,这是对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;
}

但是有些运算符没有 return 可以用这些术语声明的类型,例如 operator+=。最多它必须 return 与左手类型 *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;
    }
}