Search code examples
c++templatesoperator-overloadingambiguous

Two-parameter template ambiguous binary operator overload


So I have a matrix class (of type Derived) based on CRTP that takes two template parameters template<class T, class Derived>, and need to overload the binary operators +, - and * for a matrix multiplied by a scalar (of class T). For the * operator it will for instance be

template <class T, class Derived>
Derived operator*(const Derived &other, const T scalar)
{
    Derived result = other;
    for (size_t i = 0 ; i < other.get_rows(); i++)
    {
        for (size_t j = 0; j < other.get_cols(); j++)
        {
            result(i, j) *= scalar;
        }
    }
    return result;
}

template <class T, class Derived>
Derived operator*(const T scalar, const Derived &other) { return other * scalar; }

And then for example with a * A, where a is a double scalar and A a matrix object (dynamic), the compiler will not understand which operator to use, regardless of the class types, because of ambiguity in which template (T or Derived) to use.

Anyone have a solution here? Do I need to overload for specific template parameters such as double, int etc..?


Solution

  • The compiler can't possibly discern two overloads, that take arbitrary types for both parameters. In

    template <class T, class Derived>
    Derived operator*(const Derived &other, const T scalar)  {...}
    

    Derived and T are just template parameters that could be replaced by any type. The same goes for the other operator. Both declare a operator* that works for any two types.

    If the matrix is of type Derived (i.e. you have class Derived { ... }; somewhere) you probably meant to use this type instead of having a template parameter with the same name.

    template <class T>
    Derived operator*(const Derived &other, const T scalar) {...}
    
    template <class T, class Derived>
    Derived operator*(const T scalar, const Derived &other) {...} 
    

    Now you have two operators that take an instance of Derived as lhs and any other type as rhs or vice versa.

    In addition you could limit the type of T with std::enable_if or C++ 20 concepts & constraints using std::is_integral or std::is_floating_point templates to allow only multiplication for T's that are numbers.


    Alternatively, if you can't or don't want to use runtime polymorphism for different subclasses of matrices, you could keep your initial approach (with two template parameters) but limit the types that are allowed for Dervied the above mentioned methos, using std::is_same. SFINAE should pick the correct overload then.