Question

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;
}
Was it helpful?

Solution

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.

OTHER TIPS

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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top