#include <cassert>
#include <vector>
#include <iostream>
class int_matrix_t final // final matrix is not intended to be inherited from
{
public:
// use std::size_t for sizes and indexes (this is what the standard library does too)
inline int_matrix_t(std::size_t rows, std::size_t cols) :
m_rows{rows},
m_cols{cols},
m_data(rows*cols,0) // allocate memory for the matrix and set all values to 0
{
// one of the responsibilities of classes
// is to make sure that the invariants are always satisfied
// double check this for debug builds.
assert(m_rows > 0);
assert(m_cols > 0);
}
// A constructor that takes a 2D array and initializes the matrix with it
// a template is used for ease of use see construction of matrices in main
template<typename std::size_t rows_v, typename std::size_t cols_v>
int_matrix_t(const int (&data)[rows_v][cols_v]) : m_rows{rows_v}, m_cols{cols_v}, m_data(rows_v*cols_v)
{
// copy data from the 2D array to the matrix
for(std::size_t row{0ul}; row < rows_v; ++row)
{
for(std::size_t col{0ul}; col < cols_v; ++col)
{
at(row,col) = data[row][col];
}
}
}
// copy/move constructors will be generated by the compiler
// which is fine because members are all copyable and movable
~int_matrix_t() = default;
// pass right hand side matrix as const reference to avoid copying
// and to make sure it is not modified by the multiplication operator
inline int_matrix_t operator*(const int_matrix_t& rhs) const
{
assert( m_rows == rhs.m_rows);
assert( m_cols == rhs.m_rows);
int_matrix_t result{m_rows, m_cols};
for(std::size_t row{0ul}; row < m_rows; ++row)
{
for(std::size_t col{0ul}; col < rhs.m_cols; ++col)
{
int sum{};
for(std::size_t k{0ul}; k < m_cols; ++k)
{
sum += at(row,k) * rhs.at(k,col);
}
result.m_data[row*rhs.m_cols + col] = sum;
}
}
return result;
}
// since the memory is contiguous we need to provide a way to access the elements
// by row and column
inline int& at(std::size_t row, std::size_t col)
{
return m_data[row*m_cols + col];
}
inline int at(std::size_t row, std::size_t col) const
{
return m_data[row*m_cols + col];
}
std::size_t rows() const noexcept
{
return m_rows;
}
std::size_t columns() const noexcept
{
return m_cols;
}
// check if two matrices are equal
bool operator==(const int_matrix_t& rhs) const
{
if (m_rows != rhs.m_rows) return false;
if (m_cols != rhs.m_cols) return false;
return m_data == rhs.m_data;
}
private:
std::size_t m_rows{}; // always initialized to 0
std::size_t m_cols{}; // always initialized to 0
// do not use std::vector<std::vector<int>> as it results in data not being contiguous in memory
// which is not cache friendly and will slow down your calculations
std::vector<int> m_data{};
};
std::ostream& operator<<(std::ostream& os, const int_matrix_t& matrix)
{
os << "[\n";
for(std::size_t row{0ul}; row < matrix.rows(); ++row)
{
os << "[";
bool comma{false};
for(std::size_t col{0ul}; col < matrix.columns(); ++col)
{
if (comma) os << ",";
os << matrix.at(row,col);
comma = true;
}
os << "]\n";
}
os << "]\n";
return os;
}
int main()
{
int_matrix_t a
{{
{-1,-1,4},
{0,3,-3},
{2,-1,-2}
}};
int_matrix_t b
{{
{3,2,3},
{2,2,1},
{2,1,1}
}};
int_matrix_t expected
{{
{3,0,0},
{0,3,0},
{0,0,3}
}};
int_matrix_t c = a * b;
// this is where operator== is used
assert(c == expected);
std::cout << c;
return 0;
}