简体   繁体   中英

Left-hand operator overloading is ambiguous

I created a Matrix template class to work with matrixes of various types (int, float, 2D-points, etc.)

template<typename Type>
class Matrix {
...
};

I added some operators like +, -, *, /. These functions also need to be templated, because I want to multiply a Matrix type by float, or by a Matrix.

Here is my multiplication implementation:

template<typename T>
Matrix operator*(const T &other) const {
    Matrix temp(*this);
    temp *= other;
    return temp;
}

Matrix &operator*=(const Matrix &other) {
    auto temp = Matrix(rows, other.columns);
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < other.columns; j++) {
            temp.matrix[i][j] = Type();
            for (int k = 0; k < other.rows; k++)
                temp.matrix[i][j] += matrix[i][k] * other.matrix[k][j];
        }
    AllocMatrixData(temp.rows, temp.columns);
    matrix = temp.matrix;
    return *this;
}

template<typename T>
Matrix &operator*=(const T value) {
    for (ProxyVector<Type> &line : matrix) <- ProxyVector is just a wrapper
        line *= value;
    return *this;
}

I want my Matrix object to be on the left side, so I can do this:

Matrix * 5

However I can't do this:

5 * Matrix

So I added a function that can take a type T as the first argument and Matrix as second.

template<typename T>
friend Matrix<Type> operator*(const T Value, const Matrix<Type> &other)
{
    return other * Value;
}

But now Matrix*Matrix multiplication is ambiguous. I think I understand why - I have two functions that take T and Matrix and in this case T can be the Matrix type too. So how do I fix this?

When multiplying 2 matrices, both the member operator and operator* are applicable and have equal precedence regarding the decision of the overload to choose.

Use concepts (or SFINAE prior to C++20) to determine, if the parameter is a matrix or a scalar. This allows you to define overloads that only take part in overload resolution for the proper parameter types.

I recommend being consistent with the location of the implementation of the operator btw: If you implement a symetric operator where one of the overloads need to be defined at namespace scope, define both overloads at namespace scope; this allows you to keep the logic of both versions close together making it easier to maintain the logic.

In the following case we simply define everything that's not a matrix a scalar.

template<class T>
struct Matrix
{
};

template<class T>
struct IsMatrixHelper : std::false_type {};

template<class T>
struct IsMatrixHelper<Matrix<T>> : std::true_type {};

template<class T>
concept Scalar = !IsMatrixHelper<T>::value;

template<class T, Scalar U>
auto operator*(Matrix<T> const& m, U scalar)
{
    return Matrix<decltype(std::declval<T>()* std::declval<U>())>{};
}

template<Scalar T, class U>
auto operator*(T scalar, Matrix<U> const& m)
{
    return Matrix<decltype(std::declval<T>()* std::declval<U>())>{};
}

template<class T, class U>
auto operator*(Matrix<T> const& m1, Matrix<U> const& m2)
{
    return Matrix<decltype(std::declval<T>()* std::declval<U>())>{};
}

static_assert(std::is_same_v<decltype(std::declval<Matrix<int>>() * std::declval<Matrix<long long>>()), Matrix<long long>>);
static_assert(std::is_same_v<decltype(std::declval<Matrix<long long>>() * std::declval<int>()), Matrix<long long>>);
static_assert(std::is_same_v<decltype(std::declval<long long>() * std::declval<Matrix<int>>()), Matrix<long long>>);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM