C plus plus:Tutorials:Matrices

From GPWiki
Jump to: navigation, search

A general-purpose templated two-dimensional matrix in C++. Feel free to add to it.

#ifndef MATRIX2_HPP
#define MATRIX2_HPP
 
#include <cmath>
#include <iostream>
#include <vector>
#include <stdexcept>
#include <algorithm>
#include <functional>
 
template <class T>
class Matrix2
{
public:
    explicit Matrix2(int r = 0, int c = 0, T init_with = T())
     : rows_( r < 1 ? 4 : r), columns_( c < 1 ? 4 : c ),
       data_( rows_ * columns_, init_with ) {}
 
    template <typename U>
    explicit Matrix2(Matrix2<U> const &other)
     : rows_( other.rows() ), columns_( other.columns() ),
       data_( &*other, &*other+(rows_*columns_) ) {}
 
    T &operator()(int row, int column) { return data_[column+(columns_*row)]; }
    T const &operator()(int row, int column) const { return data_[column+(columns_*row)]; }
 
    int rows() const { return rows_; }
    int columns() const { return columns_; }
    operator T*() { return data_.empty() ? 0 : &data_[0]; }
    operator T const*() const { return data_.empty() ? 0 : &data_[0]; }
 
    Matrix2 &operator+=(const Matrix2& other) {
        if ( rows() != other.rows() || columns() != other.columns() ) {
            throw std::invalid_argument("To add matrices they must have the same size");
        }
 
        std::transform( data_.begin(), data_.end(),
                        other.data_.begin(),
                        data_.begin(),
                        std::plus<T>() );
        return *this;
    }
    Matrix2 &operator-=(const Matrix2& other) {
        if ( rows() != other.rows() || columns() != other.columns() ) {
            throw std::invalid_argument("To subtract matrices they must have the same size");
        }
 
        std::transform( data_.begin(), data_.end(),
                        other.data_.begin(),
                        data_.begin(),
                        std::minus<T>() );
        return *this;
    }
    template <class U>
    friend const Matrix2<U> operator*(const Matrix2<U>& one, const Matrix2<U>& two);
    Matrix2 &operator*=(const Matrix2& other) {
        return *this = *this * other;
    }
    Matrix2 &operator*=(T const &scale)
    {
        std::transform( data_.begin(), data_.end(),
                        data_.begin(),
                        std::bind2nd( std::multiplies<T>(), scale ) );
        return *this;
    }
    Matrix2 &operator/=(T const &scale)
    {
        std::transform( data_.begin(), data_.end(),
                        data_.begin(),
                        std::bind2nd( std::divides<T>(), scale ) );
        return *this;
    }
 
    void load_identity() {
        if ( rows() != columns() ) {
            throw std::invalid_argument("Identity matrices only exist for square matrices");
        }
 
        for (int i=0; i<rows(); i++) {
            for (int j=0; j<columns(); j++) {
                operator()( i, j ) = (i==j);
            }
        }
    }
 
    template <typename U>
    Matrix2 &operator=(Matrix2<U> const &other) {
        // note that the assign needs to be done first for exception safety.
        data_.assign( other.data_.begin(), other.data_.end() );
        rows_ = other.rows_;
        columns_ = other.columns_;
        return *this;
    }
 
private:
    // note: changing the order here will break both constructors
    int rows_;
    int columns_;
    std::vector<T> data_;
};
 
template <class T>
const Matrix2<T> operator+(Matrix2<T> one, Matrix2<T> const &two) {
    return one += two;
}
template <class T>
const Matrix2<T> operator-(Matrix2<T> one, Matrix2<T> const &two) {
    return one -= two;
}
 
template <class T>
const Matrix2<T> operator*(const Matrix2<T>& one, const Matrix2<T>& two) {
    if ( one.columns() != two.rows() ) {
        throw std::invalid_argument("Matrices can only be multiplied if the first has"
                                    " exactly as many columns as the second has rows");
    }
 
    Matrix2<T> result( one.rows(), two.columns() );
 
    for (int i=0; i<result.rows(); i++)
        for (int j=0; j<result.columns(); j++)
            for (int a=0; a<one.columns(); a++)
                result(i,j) += one(i,a) * two(a,j);
 
    return result;
}
 
template <class T>
const Matrix2<T> operator*(Matrix2<T> one, T const &factor) {
    return one *= factor;
}
template <class T>
const Matrix2<T> operator*(T const &factor, Matrix2<T> one) {
    return one *= factor;
}
template <class T>
const Matrix2<T> operator/(Matrix2<T> one, T const &factor) {
    return one /= factor;
}
 
template <typename T, typename U>
bool operator==(Matrix2<T> const &one, Matrix2<U> const &two) {
    return (one.rows() == two.rows()
        && one.columns() == two.columns()
        && std::equal( &*one, &*one + one.rows()*one.columns(),
                        &*two ));
}
template <typename T, typename U>
bool operator!=(Matrix2<T> const &one, Matrix2<U> const &two) {
    return !( one == two );
}
 
template<class T>
std::ostream& operator<<(std::ostream& os, const Matrix2<T>& mat) {
    for (int i=0; i< mat.rows(); i++) {
        os << "\n[";
        for (int j=0; j<mat.columns(); j++) {
            os << mat(i,j);
            if ( j+1 < mat.columns() )
                os << "\t";
        }
        os << "]" << std::endl;
    }
 
    return os;
}
 
typedef Matrix2<float> Matrixf;
typedef Matrix2<int> Matrixi;
typedef Matrix2<size_t> Matrixu;
 
#endif // MATRIX2_HPP