0

I’m writing a matrix template class. All went well until I was overloading the multiplication operator. My class look like this:

template <typename TNum> class Matrix
{
private:
    // ...
    TNum* Data;
public:
    const TMatIdx NRows; // Type TMatIdx defined somewhere else.
    const TMatIdx NCols;
    const TMatIdx Size;

    // ...
    // Matrix * matrix
    template <typename T2>
    const Matrix<TNum> operator*(const Matrix<T2>& right) const;

    // Matrix * number
    template <typename T2>
    Matrix<TNum>& operator*=(const T2& scale);
};

// Matrix * number
template <typename TNum, typename T2>
Matrix<TNum> operator*(Matrix<TNum> lhs, const T2& rhs);
// Number * matrix
template <typename TNum, typename T2>
Matrix<TNum> operator*(const T2& lhs, Matrix<TNum> rhs);

I’m hoping to cover all possible multiplication combinations among matrices and numbers with the same * operator.

Then I wrote a small test program that multiplies two Matrix<double>s, my clang++ compiler complains about ambiguity:

test.cpp:46: error: ambiguous overload for 'operator*' in 'M * N'
matrix.h:225: note: candidates are: const QCD::Matrix<TNum> QCD::Matrix<TNum>::operator*(const QCD::Matrix<T2>&) const [with T2 = double, TNum = double]
matrix.h:118: note:                 QCD::Matrix<TNum> QCD::operator*(const T2&, QCD::Matrix<TNum>) [with TNum = double, T2 = QCD::Matrix<double>]
matrix.h:109: note:                 QCD::Matrix<TNum> QCD::operator*(QCD::Matrix<TNum>, const T2&) [with TNum = double, T2 = QCD::Matrix<double>]
test.cpp:52: error: ambiguous overload for 'operator*' in 'M * N'
matrix.h:225: note: candidates are: const QCD::Matrix<TNum> QCD::Matrix<TNum>::operator*(const QCD::Matrix<T2>&) const [with T2 = double, TNum = double]
matrix.h:118: note:                 QCD::Matrix<TNum> QCD::operator*(const T2&, QCD::Matrix<TNum>) [with TNum = double, T2 = QCD::Matrix<double>]
matrix.h:109: note:                 QCD::Matrix<TNum> QCD::operator*(QCD::Matrix<TNum>, const T2&) [with TNum = double, T2 = QCD::Matrix<double>]

Is it possible to overcome this ambiguity without having to explicitly write down all possible specializations for T2?

And FYI, here’s my implementation:

template<typename TNum> template <typename T2>
Matrix<TNum>& Matrix<TNum> ::
operator*=(const T2& rhs)
{
    for(TMatIdx i = 0; i < Size; i++)
        Data[i] *= rhs;
    return *this;
}

template<typename TNum> template <typename T2>
const Matrix<TNum> Matrix<TNum> ::
operator*(const Matrix<T2>& right) const
{
    Matrix<TNum> c(NRows, right.NCols);
    TNum sum_elems;
    for(TMatIdx i = 0; i < NRows; i++)
    {
        for(TMatIdx j = 0; j < right.NCols; j++)
        {
            sum_elems = TNum(0);
            for(TMatIdx k = 0; k < right.NRows; k++)
            {
                sum_elems += at(i, k) * right.at(k, j);
            }

            c.at(i, j) = sum_elems;
        }
    }
    return c;
}


template <typename TNum, typename T2>
Matrix<TNum> operator*(Matrix<TNum> lhs, const T2& rhs)
{
    lhs *= rhs;
    return lhs;
}

template <typename TNum, typename T2>
Matrix<TNum> operator*(const T2& lhs, Matrix<TNum> rhs)
{
    rhs *= lhs;
    return rhs;
}
MetroWind
  • 541
  • 4
  • 16

2 Answers2

3

I'm going to describe a solution with c++11, and then explain how to implement it in c++98.

In c++11 the header <type_traits> includes type functions and type predicates. This makes enforcing constraints more convenient.

std::is_same<T1, T2>::value is true if T1 is the same type as T2 and false otherwise.

typename std::enable_if< bool, T >::type is a well defined type T if bool is true and ill-defined otherwise.

When the compiler looks for candidate template functions and methods, it doesn't throw an error if an attempted specialization fails. It simply throws out that candidate. Which means the following code will remove the ambiguity:

template <typename TNum, typename T2>
typename std::enable_if< (!std::is_same<Matrix<TNum>, T2>::value),
Matrix<TNum> >::type operator*(const T2& lhs, Matrix<TNum> rhs);

Only declarations are considered when making this decision. The above logic is semantically reasonably, but an eyesore to read. So c++11 supports template aliases, and constexpr functions.

template<bool B, typename T = void>
using Enable_if = typename std::enable_if<B, T>::type;

template<typename T1, typename T2>
constexpr bool Is_same(){
  return std::is_same<T1, T2>::value;
}

The above then becomes:

template <typename TNum, typename T2>
Enable_if<( !Is_same<Matrix<TNum>, T2>() ),
Matrix<TNum> > operator*(const T2& lhs, Matrix<TNum> rhs);

concepts will provide tools to make this more convenient.

Now if you don't have c++11, you don't get the eye-candy. But, Boost provides the same functions. Suppose you don't have either, implementing them isn't terrible.

Compile time functions depend on multiple language rules, which makes them difficult to understand. We'll consider enable_if first. We want typename enable_if<true, T>::type to be well defined but, typename enable_if<false, T>::type to be nonsense. We use specialization:

template<bool B, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> {};

notice false handles exactly half of relevant cases. It's worthwhile to chew on the above.

In order to implement is_same, we need a notion of true or false at compile time. We can guarantee this with static const variables. We want is_same to have a compile time true value, whenever its template arguments are the same. The specialization system rules handle this directly.

template<typename, typename>
struct is_same{
  static const bool value = false;
};

template<typename T>
struct is_same<T, T>{
  static const bool value = true;
};

This should do what you want. Note you could abstract one step further and make another pair of structs.

struct false_type {
  static const bool value = false;
};

struct true_type {
  static const bool value = true;
};

then is_same becomes:

template<typename, typename>
struct is_same : false_type {};

template<typename T>
struct is_same<T, T> : true_type {};

which makes it look more like a function.

I prefer this to the category solution, because it's easier to separate the metaprogram into a header file. You can then reuse the logic elsewhere. Still, if you aren't using c++11 or even boost, making necessary compile time functions can be a headache.

If using complex( or any simple redesign ) satisfies your current and future requirements - prefer that. Otherwise I think this solution is reasonably future proof.

Polymer
  • 1,043
  • 11
  • 13
  • Nice answer and explanation! I tried it an it worked flawlessly (at least for my tiny test program :). Thanks a lot! – MetroWind Oct 24 '13 at 18:16
0

This may be help.

#include<utility>

template<class Type>
struct test{
private:
    struct _matrix{};
    struct _scalar{};
    template<class T>
    struct category{
        typedef _scalar type;
    };
    template<class T>
    struct category<test<T>>{
        typedef _matrix type;
    };

    template<class T>
    void do_foo(T, _matrix){}

    template<class T>
    void do_foo(T, _scalar){}

public:
    //c++11
    template<class T>
    void operator*(T&& a){
        do_foo(std::forward<T>(a), category<T>::type());
    }
    //Older Compiler
    //template<class T>
    //void operator*(const T& a){
    //  do_foo(a, category<T>::type());
    //}

};

int main(){
    typedef test<int> int_matrix;
    int_matrix obj;
    obj*int_matrix();
    obj*obj;
    obj*1;
    obj*1.;

    return 0;
}
cqdjyy01234
  • 1,180
  • 10
  • 20
  • Could you provide some hints on how this code works? Also I may not have access to a c++11 compiler. I’ll need to run this code on some super computers that have weird old compilers… – MetroWind Oct 17 '13 at 00:43
  • The features utilized are function overload (do_foo) and class template specialization. The std::forward is not essential. So the trick will be effective on older compilers. The reason that your original code doesn't work is that there is not preferred match for Matrix. Indeed, I think it seems impossible to have a preferred match with two function templates in your case and the only solution I can think of is function overload. PS: I think you can just define the operator for class matrix and T. There is no need for all types because of the automatic type conversion. – cqdjyy01234 Oct 17 '13 at 02:46
  • I think the scalar multiplication should not be template because of the automatic type conversion. – cqdjyy01234 Oct 17 '13 at 03:00
  • The reason I’m having a separated type for scalar is that I’ll have matrices of matrices later, and I don’t want the program to convert a number to a diagonal matrix and then do the multiplication. The finished program will be extremely memory- and cpu-hungry, so I’m trying to save every bits I can. Though I’m considering, if nothing works out I might just set T2 to std::complex and be done with it. – MetroWind Oct 18 '13 at 19:24
  • @Darksair So now your problem is? – cqdjyy01234 Oct 20 '13 at 06:24
  • The problem still stands. using std::complex is the last choice because it may need to call the constructor of std::complex, which I assume takes finite time. But thanks a lot for your help! – MetroWind Oct 24 '13 at 18:14