3

I am using Boost-Operatators to construct a matrix class. (A toy project). However, I run into issues when I want to mix matrices of different element types.

Basically I have a template class Matrix<T>, where T is the element type of that matrix. I'm using Boost-Operators to define operators between instances of Matrix<T> (e.g. element-wise add), between Matrix<T> and T (e.g. scalar multiplication), and if possible also between Matrix<T> and Matrix<U> (e.g. real matrix plus complex matrix).

The boost operators support one, or two template arguments. One if you want operators between two objects of the same type, and two if you want mixed operators.

template<typename T>
class Matrix : boost::addable<Matrix<T>> // Add another matrix of same type.
               boost::multiplyable2<Matrix<T>,T> // Scalar multiplication with a `T`.

However, I cannot give Matrix<U> as a second argument, because then my class would have two template arguments and the type would depend on which matrices I can operate with.

template<typename T, typename U>
class Matrix : boost::addable2<Matrix<T,U>,Matrix<U,?>> // Now I have two template arguments.
                                                        // That's certainly not what I want!

I also tried implementing my own version of boost::addable, but this didn't work either. The compiler complains about an uncomplete type.

template<class Derived>                                       
class Addable {                                               
    template<class Other>                                     
    friend Derived operator+(Derived lhs, const Other &rhs) { 
        return lhs += rhs;                                    
    }                                                         

    template<class Other>                                     
    friend Derived operator+(const Other &lhs, Derived rhs) { 
        return rhs += lhs;                                    
    }                                                         
};                                                            

Another approach was to define a cast constructor from Matrix<U> to Matrix<T>. However, now I have the issue, that those are two different types, and I don't get access to the private members. So, I either need to make more stuff public than I want to, or find a different way of doing this.

How would you implement such a thing?

The full Code

#include <cassert>
#include <utility>
#include <complex>
#include <vector>
#include <algorithm>
#include <iostream>

#include <boost/operators.hpp>


typedef double Real;
typedef std::complex<Real> Complex;


template<typename T>
class Matrix : boost::addable<Matrix<T>>
{
public:
    Matrix() = default;
    template<typename U>
    Matrix(const Matrix<U> &other)
        : m_(other.m()), n_(other.n()),
          data_(other.data_.begin(), other.data_.end()) { }
    Matrix(size_t m, size_t n) : m_(m), n_(n), data_(m*n) { }
    Matrix(size_t m, size_t n, const T &initial)
        : m_(m), n_(n), data_(m*n, initial) { }

    size_t m() const { return m_; }
    size_t n() const { return n_; }
    size_t size() const {
        assert(m_*n_ == data_.size());
        return data_.size();
    }

    const T &operator()(size_t i, size_t j) const { return data_[i*m_ + j]; }
    T &operator()(size_t i, size_t j) { return data_[i*m_ + j]; }

    void fill(const T &value) {
        std::fill(data_.begin(), data_.end(), value);
    }

    Matrix &operator+=(const Matrix &other) {
        assert(dim_match(other));
        for (int i = 0; i < size(); ++i) {
            data_[i] += other.data_[i];
        }
        return *this;
    }

    friend std::ostream &operator<<(std::ostream &o, const Matrix &m) {
        if (m.size() == 0) {
            o << "()" << std::endl;
            return o;
        }
        for (int i = 0; i < m.m(); ++i) {
            o << "( ";
            for (int j = 0; j < m.n() - 1; ++j) {
                o << m(i,j) << ", ";
            }
            o << m(i, m.n() - 1) << " )" << std::endl;
        }
        return o;
    }

private:
    bool dim_match(const Matrix &other) {
        return n_ == other.n_ && m_ == other.m_;
    }

private:
    int m_, n_;
    typedef std::vector<T> Store;
    Store data_;
};


int main() {
    Matrix<Real> A(2,3, 1.);
    Matrix<Complex> B(2,3, Complex(0,1));
    auto C = Matrix<Complex>(A) + B;
    std::cout << A << std::endl;
    std::cout << B << std::endl;
    std::cout << C << std::endl;
}
Lemming
  • 4,085
  • 3
  • 23
  • 36

1 Answers1

2

This is how I'd do it: use a friend template function (see Operator overloading: The Decision between Member and Non-member):

template<typename T>
class Matrix
{
public:
    template<typename> friend class Matrix;

And then later

template <typename T1, typename T2>
Matrix<typename std::common_type<T1, T2>::type> 
    operator+(Matrix<T1> const& a, Matrix<T2> const& b)
{
    Matrix<typename std::common_type<T1, T2>::type> result(a);
    return (result += b);
}

Note the use of common_type to arrive at a sensible resulting type (you might want to introduce your own trait there to cater for your specific requirements)

See it Live On Coliru

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Fixed the sample so that it now actually /adds/ the arrays :) – sehe Feb 09 '14 at 19:30
  • Thanks for your answer. It seems you made the `data_` member `public` to fix the problem. While this would work, I think it's not a good idea. How the matrix stores it's elements should be implementation specific and not part of the interface... – Lemming Feb 09 '14 at 21:53
  • I think I figured it out now. You can make the other matrix a friend `template class Matrix;`. Then the conversion will work even for a `private` `data_` member. The `operator+` doesn't even need to be friend anymore in that case. In any case the `std::common_type` was key! Thanks! – Lemming Feb 09 '14 at 22:18
  • @Lemming Erm. Yeah. I dunno how I forgot to fix that. I was focusing on the return type before anything else - and "parked" the friend thing for a moment. Anyways, fixed my answer to work with private members :) – sehe Feb 09 '14 at 22:55