Basically I have written a matrix class for addition, multiplication and scalar multiplication.
I need your review of the class implementation below in terms of efficiency, memory consumption and new C++11/14/17 features that can be used.
Here goes the code:
Matrix.hpp
// Created by prajwal.sapare on 2/18/2020.
//
#ifndef UNTITLED2_MATRIX_HPP
#define UNTITLED2_MATRIX_HPP
#include <vector>
#include <exception>
#include <iostream>
#include <algorithm>
#include "helper.h"
template<class T, uint m, uint n>
class Matrix;
template <class T, uint n>
using rowVector = Matrix<T, 1, n>;
template <class T, uint m>
using colVector = Matrix<T, m, 1>;
template<class T, uint m, uint n>
class Matrix {
public:
explicit Matrix();
explicit Matrix(const std::vector<T> matrixValue);
int getRows()const;
int getColoumns() const;
T& operator()(const uint row, const uint col);
Matrix<T,m,n> operator=(std::vector<T> matrixIntializationValue);
template<uint N>
Matrix<T,m,N> operator*(Matrix<T,n,N>& other);
template<uint a, uint b>
bool operator==(Matrix<T,a,b>& other);
Matrix<T,m,n> operator+(Matrix<T,m,n>& other);
Matrix<T,m,n> operator*(T scalar);
template<class T, uint m, uint n> friend std::ostream& operator<<(std::ostream& os, const Matrix<T,m,n>& matrix);
private:
uint rows;
uint cols;
std::vector<std::vector<T>> data;
};
template<class T, uint m, uint n>
Matrix<T,m,n>::Matrix(): rows(m), cols(n)
{
if(rows == 0 || cols == 0)
throw std::invalid_argument( "received zero as argument" );
data.resize(rows);
for (auto& colData : data)
colData.resize(cols);
}
template<class T, uint m, uint n>
Matrix<T,m,n>::Matrix(const std::vector<T> matrixValue): rows(m), cols(n)
{
if(rows == 0 || cols == 0)
throw std::invalid_argument( "received zero as argument" );
if(matrixValue.empty())
throw std::invalid_argument( "Empty vector" );
if(rows * cols != matrixValue.size())
throw std::runtime_error( "Total number of matrix values does not match with rows and coloumns provided" );
data.resize(rows);
for (auto& colData : data)
colData.resize(cols);
for (auto i = 0; i<rows; i++)
data[i] = {matrixValue.begin() + (i * cols), matrixValue.begin() + ((i+1) * cols)};
}
template<class T, uint m, uint n>
int Matrix<T,m,n>::getRows() const
{
return this->rows;
}
template<class T, uint m, uint n>
int Matrix<T,m,n>::getColoumns() const
{
return this->cols;
}
template<class T, uint m, uint n>
T& Matrix<T,m,n>::operator()(const uint row, const uint col)
{
return this->data[row][col];
}
template<class T, uint m, uint n>
Matrix<T,m,n> Matrix<T,m,n>::operator+(Matrix& other)
{
if ((this->rows == other.getRows()) && (this->cols == other.getColoumns()))
{
Matrix<T,m,n> resultantMatrix;
for(auto i = 0; i< this->rows; i++)
{
for(auto j = 0; j < this->cols; j++)
{
auto& valueFirst = this->data[i][j];
auto& valueSecond = other(i, j);
if((additionOverflow(valueFirst, valueSecond)) || (additionUnderflow(valueFirst, valueSecond)))
throw std::out_of_range("Resultant value of matrix is out of range");
else
resultantMatrix(i, j) = valueFirst + valueSecond;
}
}
return resultantMatrix;
}
else
throw std::runtime_error("Matrices cannot be added, sizes do not match");
}
template<class T, uint m, uint n>
template<uint N>
Matrix<T,m,N> Matrix<T,m,n>::operator*(Matrix<T,n,N>& other)
{
if ((this->cols == other.getRows()))
{
Matrix<T,m,N> resultantMatrix;
T temp = 0;
for (auto i = 0; i < this->rows; i++) {
for (auto j = 0; j < other.getColoumns(); j++) {
temp = 0.0;
for (auto k = 0; k < this->cols; k++) {
if(other(k, j) != 0)
{
if(multiplicationOverflow(this->data[i][k], other(k, j)) || multiplicationUnderflow(this->data[i][k], other(k, j)))
throw std::out_of_range("Resultant value of matrix is out of range");
}
auto tempMul = this->data[i][k] * other(k, j);
if((additionOverflow(temp, tempMul)) || (additionUnderflow(temp, tempMul)))
throw std::out_of_range("Resultant value of matrix is out of range");
temp = temp + tempMul;
}
resultantMatrix(i, j) = temp;
}
}
return resultantMatrix;
}
else
throw std::runtime_error("Matrices cannot be multiplied, invalid sizes");
}
template<class T, uint m, uint n>
Matrix<T,m,n> Matrix<T,m,n>::operator*(T scalar){
Matrix<T,m,n> resultantMatrix;
for (auto i = 0; i < this->rows; i++)
{
for (auto j = 0; j < this->cols; j++)
{
auto valueFirst = this->data[i][j];
if (scalar == 0)
resultantMatrix(i,j) = 0;
else if((multiplicationOverflow(valueFirst, scalar)) || (multiplicationUnderflow(valueFirst, scalar)))
throw std::out_of_range("Resultant value of matrix is out of range");
else
resultantMatrix(i,j) = this->data[i][j] * scalar;
}
}
return resultantMatrix;
}
template<class T, uint m, uint n>
Matrix<T,m,n> Matrix<T,m,n>::operator=(std::vector<T> matrixIntializationValue)
{
if (!matrixIntializationValue.empty())
{
if(rows * cols != matrixIntializationValue.size())
throw std::invalid_argument( "Total number of matrix values does not match with rows and coloumns provided" );
for (auto i = 0; i<rows; i++)
data[i] = {matrixIntializationValue.begin() + (i * cols), matrixIntializationValue.begin() + ((i+1) * cols)};
return *this;
}
else
throw std::runtime_error("Matrices cannot be multiplied, invalid sizes");
}
template<class T, uint m, uint n>
std::ostream& operator<<(std::ostream &os, const Matrix<T,m,n> &matrix)
{
if(!matrix.data.empty())
{
for (const auto& rowVal : matrix.data)
{
for(const auto& colVal : rowVal)
os << colVal << " ";
os << std::endl;
}
}
return os;
}
template<class T, uint m, uint n>
template<uint a, uint b>
bool Matrix<T,m,n>::operator==(Matrix<T,a,b>& other)
{
if( (m!=a) || (n!=b))
return false;
for(auto i =0; i< m ; i++)
{
for(auto j=0;j <n;j++)
{
if(this->data[i][j]!= other(i,j))
return false;
}
}
return true;
}
#endif //UNTITLED2_MATRIX_HPP
Helper.h
//
// Created by prajwal.sapare on 2/22/2020.
//
#ifndef UNTITLED2_HELPER_H
#define UNTITLED2_HELPER_H
using uint = unsigned int;
#include <limits>
template <typename T>
constexpr bool additionOverflow(const T& x, const T& y) {
return (y >= 0) && (x > std::numeric_limits<T>::max() - y);
}
template <typename T>
constexpr bool additionUnderflow(const T& x, const T& y) {
return (y < 0) && (x < std::numeric_limits<T>::min() - y);
}
template <typename T>
constexpr bool multiplicationOverflow(const T& x, const T& y) {
return ((y >= 0) && (x >= 0) && (x > std::numeric_limits<T>::max() / y))
|| ((y < 0) && (x < 0) && (x < std::numeric_limits<T>::max() / y));
}
template <typename T>
constexpr bool multiplicationUnderflow(const T& x, const T& y) {
return ((y >= 0) && (x < 0) && (x < std::numeric_limits<T>::min() / y))
|| ((y < 0) && (x >= 0) && (x > std::numeric_limits<T>::min() / y));
}
#endif //UNTITLED2_HELPER_H