1

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?

JimmyNJ
  • 1,134
  • 1
  • 8
  • 23
  • 1
    For the functions that need to take a `float` don't use a template argument `T` just use a `float`, so `friend Matrix operator*(float Value, const Matrix &other)` – john Dec 24 '22 at 14:31
  • 1
    Does this answer your question? [What are the basic rules and idioms for operator overloading?](https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading) – Richard Critten Dec 24 '22 at 14:41

1 Answers1

1

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>>);
fabian
  • 80,457
  • 12
  • 86
  • 114
  • In this case I should declare every type Matrix can contain? – dimanchique Dec 24 '22 at 14:57
  • @dimanchique Why would you need to do this? I mean sure, you could introduce a concept for types that could be template parameter types for `Matrix`, but in the code above the result type is simply deduced and an operation fails compilation, if the multiplication operator is not defined for the relevant types. If you implement the code for `+` accordingly, this would allow for `+` operations of `Matrix` and `std::string`,but it would fail for `*` operations on operands of the same type.Without an artificial restriction,the code may be useable in scenarios you've not yet thought of – fabian Dec 24 '22 at 15:06