6
\$\begingroup\$

I am refreshing my C++ programming skills, and I'm currently reading the book C++ 17 by Ivor Horton. The idea is to code some naive implementations of numerical algorithms (Root finding, solving a system of linear equations). To hone my skills, I decided to write a custom C++ Matrix class. This can be an interesting and useful exercise in itself.

My Matrix class supports fixed-size matrices whose dimensions are compile-time constants as well as dynamic (run-time) matrices. I overloaded the << and , operators, in a way that a matrix objects can be nicely initialized as:

MatrixXi m1 {3,3};
m1 <<1, 0, 0,
     0, 1, 0,
     0, 0, 1;

The class supports a subset of basic matrix algebra - addition, subtraction, multiplication and assignment through overloaded operators. I would very much appreciate your suggestions/feedback on this project.

I have couple of additional soft questions.

  1. Matrix takes 4 template parameters.

    template <typename scalarType, int rowsAtCompileTime = 0, int colsAtCompileTime = 0, typename containerType = std::array<T, r* c>>
    

    At some point, (due to build failures) I ended up doing partial template specialization, i.e. I have two versions of Matrix class - one with std::array<T> and another with std::vector<T,N> as the containerType. Should I try and generalize this again, to remove duplicate code?

  2. Would you call this well-designed/well-written code, or is it spaghetti code?

Matrix.h

#pragma once
#ifndef Matrix_H
#include <array>
#include <vector>
#include <stdexcept>
#include <type_traits>
#include <iterator>

template <typename T, int r = 0, int c = 0, typename cType = std::array<T, r* c>>
class Matrix;

using Matrix1d = Matrix<double, 1, 1, std::array<double, 1>>;
using Matrix2d = Matrix<double, 2, 2, std::array<double, 4>>;
using Matrix3d = Matrix<double, 3, 3, std::array<double, 9>>;
using Matrix4d = Matrix<double, 4, 4, std::array<double, 16>>;

using Matrix1i = Matrix<int, 1, 1, std::array<int, 1>>;
using Matrix2i = Matrix<int, 2, 2, std::array<int, 4>>;
using Matrix3i = Matrix<int, 3, 3, std::array<int, 9>>;
using Matrix4i = Matrix<int, 4, 4, std::array<int, 16>>;

using Matrix1f = Matrix<float, 1, 1, std::array<float, 1>>;
using Matrix2f = Matrix<float, 2, 2, std::array<float, 4>>;
using Matrix3f = Matrix<float, 3, 3, std::array<float, 9>>;
using Matrix4f = Matrix<float, 4, 4, std::array<float, 16>>;

using Vector1d = Matrix<double, 1, 1, std::vector<double>>;
using Vector2d = Matrix<double, 1, 2, std::vector<double>>;
using Vector3d = Matrix<double, 1, 3, std::vector<double>>;
using Vector4d = Matrix<double, 1, 4, std::vector<double>>;

using Vector1i = Matrix<int, 1, 1, std::vector<double>>;
using Vector2i = Matrix<int, 2, 2, std::vector<double>>;
using Vector3i = Matrix<int, 3, 3, std::vector<double>>;
using Vector4i = Matrix<int, 4, 4, std::vector<double>>;

using Vector1f = Matrix<float, 1, 1, std::vector<double>>;
using Vector2f = Matrix<float, 2, 2, std::vector<double>>;
using Vector3f = Matrix<float, 3, 3, std::vector<double>>;
using Vector4f = Matrix<float, 4, 4, std::vector<double>>;

using MatrixXi = Matrix<int, 0, 0, std::vector<int>>;
using MatrixXd = Matrix<double, 0, 0, std::vector<double>>;
using MatrixXf = Matrix<float, 0, 0, std::vector<float>>;

template <typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
class Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>
{
private:
    std::array<scalarType, rowsAtCompileTime* colsAtCompileTime> A;
    int _rows;
    int _cols;
    int _size;
    typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::iterator currentPosition;
public:
    Matrix();
    Matrix(int m, int n);
    Matrix(const Matrix& m);

    int rows() const;
    int cols() const;
    int size() const;

    //Overloaded operators
    scalarType operator()(const int i, const int j) const;      //Subscript operator
    scalarType& operator()(const int i, const int j);           //Subscript operator const arrays
    Matrix operator+(const Matrix& m) const;                    
    Matrix operator-(const Matrix& m) const;
    Matrix& operator<<(const scalarType x);
    Matrix& operator,(const scalarType x);
    Matrix& operator=(const Matrix& right_hand_side);
};

template <typename scalarType>
class Matrix<typename scalarType, 0, 0, std::vector<scalarType>>
{
private:
    std::vector<scalarType> A;
    int _rows;
    int _cols;
    int _size;
    typename std::vector<scalarType>::iterator currentPosition;
public:
    Matrix();
    Matrix(int m, int n);
    Matrix(const Matrix& m);

    int rows() const;
    int cols() const;
    int size() const;

    //Overloaded operators
    scalarType operator()(const int i, const int j) const;
    scalarType& operator()(const int i, const int j);
    Matrix operator+(const Matrix& m) const;
    Matrix operator-(const Matrix& m) const;
    Matrix& operator<<(const scalarType x);
    Matrix& operator,(const scalarType x);
    Matrix& operator=(const Matrix& right_hand_side);
};

// Non-member operator functions
template<typename scalarType, int m, int n, int p, int q>
Matrix<scalarType, m, q, std::vector<scalarType>> operator*(const Matrix<scalarType, m, n, std::vector<scalarType>>& A, const Matrix<scalarType, p, q, std::vector<scalarType>>& B);

template<typename scalarType, int m, int n, int p, int q>
Matrix<scalarType, m, q, std::array<scalarType, m* q>> operator*(const Matrix<scalarType, m, n, std::array<scalarType, m* n>>& A, const Matrix<scalarType, p, q, std::array<scalarType, p* q>>& B);


//Default constructor
template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::Matrix() :_rows{ rowsAtCompileTime }, _cols{ colsAtCompileTime }
{
    currentPosition = A.begin();
}

//Default constructor
template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::Matrix() :_rows{ 0 }, _cols{ 0 }
{
    currentPosition = A.begin();
}

//Fixed-Size matrices
template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::Matrix(int m, int n)
{
    //Do nothing.
}

//Run-time matrices
template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::Matrix(int m, int n) : _rows{ m }, _cols{ n }
{
    int numOfElements{ m * n };
    for (int i{}; i < numOfElements; ++i)
    {
        A.push_back(0);
    }

    currentPosition = A.begin();
}

template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::Matrix(const Matrix& m) : A{ m.A }, _rows{ m.rows() }, _cols{ m.cols() }, currentPosition{ m.currentPosition }
{
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::Matrix(const Matrix& m) : A{ m.A }, _rows{ m.rows() }, _cols{ m.cols() }, currentPosition{ m.currentPosition }
{
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline int Matrix<scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::rows() const
{
    return _rows;
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline int Matrix<scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::cols() const
{
    return _cols;
}

template<typename scalarType>
inline int Matrix<scalarType, 0, 0, std::vector<scalarType>>::rows() const
{
    return _rows;
}

template<typename scalarType>
inline int Matrix<scalarType, 0, 0, std::vector<scalarType>>::cols() const
{
    return _cols;
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline int Matrix<scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::size() const
{
    return A.size();
}

template<typename scalarType>
inline int Matrix<scalarType, 0, 0, std::vector<scalarType>>::size() const
{
    return A.size();
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline scalarType Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::operator()(const int i, const int j) const
{
    typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::const_iterator it{ A.begin() };
    it = it + (i * _cols) + j;
    if (it < A.end())
        return *it;
    else
        throw std::out_of_range("\nError accessing an element beyond matrix bounds");
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline scalarType& Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::operator()(const int i, const int j)
{
    typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::iterator it{ A.begin() };
    it = it + (i * _cols) + j;
    if (it < A.end())
        return *it;
    else
        throw std::out_of_range("\nError accessing an element beyond matrix bounds");
}

template<typename scalarType>
inline scalarType Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::operator()(const int i, const int j) const
{
    typename std::vector<scalarType>::const_iterator it{ A.begin() };
    it = it + (i * _cols) + j;
    if (it < A.end())
        return *it;
    else
        throw std::out_of_range("\nError accessing an element beyond matrix bounds");
}

template<typename scalarType>
inline scalarType& Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::operator()(const int i, const int j)
{
    typename std::vector<scalarType>::iterator it{ A.begin() };
    it = it + (i * _cols) + j;
    if (it < A.end())
        return *it;
    else
        throw std::out_of_range("\nError accessing an element beyond matrix bounds");
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>> Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::operator+(const Matrix& m) const
{
    Matrix<scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>> result{};

    if (this->rows() == m.rows() && this->cols() == m.cols())
    {
        typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::const_iterator it1{ A.begin() };
        typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::const_iterator it2{ m.A.begin() };
        typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::iterator resultIter{ result.A.begin() };
        while (it1 < A.end() && it2 < m.A.end())
        {
            *resultIter = *it1 + *it2;
            ++it1; ++it2; ++resultIter;
        }
    }
    else
    {
        throw std::logic_error("Matrices have different dimensions; therefore cannot be added!");
    }


    return result;
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>> Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::operator-(const Matrix& m) const
{
    Matrix<scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>> result{};

    if (this->rows() == m.rows() && this->cols() == m.cols())
    {
        typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::const_iterator it1{ A.begin() };
        typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::const_iterator it2{ m.A.begin() };
        typename std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>::iterator resultIter{ result.A.begin() };
        while (it1 < A.end() && it2 < m.A.end())
        {
            *resultIter = *it1 - *it2;
            ++it1; ++it2; ++resultIter;
        }
    }
    else
    {
        throw std::logic_error("Matrices have different dimensions; therefore cannot be added!");
    }


    return result;
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>& Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::operator<<(const scalarType x)
{
    if (currentPosition < A.end())
    {
        *currentPosition = x;
        ++currentPosition;
    }
    else
    {
        throw std::logic_error("Error: Attempting to set values beyond matrix bounds!");
    }
    return *this;
}

template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>>& Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::operator<<(const scalarType x)
{
    if (currentPosition < A.end())
    {
        *currentPosition = x;
        ++currentPosition;
    }
    else
    {
        throw std::logic_error("Error: Attempting to set values beyond matrix bounds!");
    }
    return *this;
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>& Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::operator,(const scalarType x)
{
    if (currentPosition < A.end())
    {
        *currentPosition = x;
        ++currentPosition;
    }
    else
    {
        throw std::logic_error("Error: Attempting to set values beyond matrix bounds!");
    }
    return *this;
}

template<typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
inline Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>& Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>::operator=(const Matrix& right_hand_side)
{
    if (this->rows() != right_hand_side.rows() || this->cols() != right_hand_side.cols())
        throw std::logic_error("Assignment failed, matrices have different dimensions");

    if (this == &right_hand_side)
        return *this;

    this->A = right_hand_side.A;
    this->_rows = right_hand_side._rows;
    this->_cols = right_hand_side._cols;
    this->currentPosition = right_hand_side.currentPosition;
    return *this;
}

template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>>& Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::operator=(const Matrix& right_hand_side)
{
    if (this->rows() != right_hand_side.rows() || this->cols() != right_hand_side.cols())
        throw std::logic_error("Assignment failed, matrices have different dimensions");

    if (this == &right_hand_side)
        return *this;

    this->A = right_hand_side.A;
    this->_rows = right_hand_side._rows;
    this->_cols = right_hand_side._cols;
    this->currentPosition = right_hand_side.currentPosition;
    return *this;
}

template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>>& Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::operator,(const scalarType x)
{
    if (currentPosition < A.end())
    {
        *currentPosition = x;
        ++currentPosition;
    }
    else
    {
        throw std::logic_error("Error: Attempting to set values beyond matrix bounds!");
    }
    return *this;
}

template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>> Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::operator+(const Matrix& m) const
{
    if (this->rows() == m.rows() && this->cols() == m.cols())
    {
        Matrix<scalarType, 0, 0, std::vector<scalarType>> result{m.rows(),m.cols()};
        typename std::vector<scalarType>::const_iterator it1{ A.begin() };
        typename std::vector<scalarType>::const_iterator it2{ m.A.begin() };
        typename std::vector<scalarType>::iterator resultIter{ result.A.begin() };
        while (it1 < A.end() && it2 < m.A.end())
        {
            *resultIter = *it1 + *it2;
            ++it1; ++it2; ++resultIter;
        }
        return result;
    }
    else
    {
        throw std::logic_error("Matrices have different dimensions; therefore cannot be added!");
    }
}

template<typename scalarType>
inline Matrix<typename scalarType, 0, 0, std::vector<scalarType>> Matrix<typename scalarType, 0, 0, std::vector<scalarType>>::operator-(const Matrix& m) const
{
    if (this->rows() == m.rows() && this->cols() == m.cols())
    {
        Matrix<scalarType, 0, 0, std::vector<scalarType>> result{ m.rows(),m.cols() };
        typename std::vector<scalarType>::const_iterator it1{ A.begin() };
        typename std::vector<scalarType>::const_iterator it2{ m.A.begin() };
        typename std::vector<scalarType>::iterator resultIter{ result.A.begin() };
        while (it1 < A.end() && it2 < m.A.end())
        {
            *resultIter = *it1 - *it2;
            ++it1; ++it2; ++resultIter;
        }
        return result;
    }
    else
    {
        throw std::logic_error("Matrices have different dimensions; therefore cannot be added!");
    }
}

template<typename scalarType, int m, int n, int p, int q>
inline Matrix<scalarType, m, q, std::vector<scalarType>> operator*(const Matrix<scalarType, m, n, std::vector<scalarType>>& A, const Matrix<scalarType, p, q, std::vector<scalarType>>& B)
{
    if (n != p)
        throw std::logic_error("Error multiplying the matrices; the number of cols(A) must equal the number of rows(B)!");

    Matrix<scalarType, 0, 0, std::vector<scalarType>> result{ m,q };

    for (int i{}; i < m; ++i)
    {
        for (int k{}; k < p; ++k)
        {
            scalarType sum{};
            for (int j{}; j < n; ++j)
            {
                sum += A(i, j) * B(j, k);
            }
            result(i, k) = sum;
        }
    }

    return result;
}

template<typename scalarType, int m, int n, int p, int q>
Matrix<scalarType, m, q, std::array<scalarType, m* q>> operator*(const Matrix<scalarType, m, n, std::array<scalarType, m* n>>& A, const Matrix<scalarType, p, q, std::array<scalarType, p* q>>& B)
{
    if (n != p)
        throw std::logic_error("Error multiplying the matrices; the number of cols(A) must equal the number of rows(B)!");

    Matrix<scalarType, m, q, std::array<scalarType, m* q>> result;

    for (int i{}; i < m; ++i)
    {
        for (int k{}; k < p; ++k)
        {
            scalarType sum{};
            for (int j{}; j < n; ++j)
            {
                sum += A(i, j) * B(j, k);
            }
            result(i, k) = sum;
        }
    }

    return result;
}
#endif // !Matrix_H

TestMatrix.cpp

// MatrixImpl.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include "Matrix.h"
#include <iomanip>

int main()
{
    MatrixXi m1(3,3);
    m1 <<1, 0, 0,
        0, 1, 0,
        0, 0, 1;

    MatrixXi m2{ 3,3 };

    //m2 = m1;

    m2 << 2, 0, 0, 
        0, 1, 0, 
        0, 0, 3;

    MatrixXi m3 = m1 + m2;

    for (int i{}; i < 3; ++i)
    {
        for (int j{}; j < 3; ++j)
        {
            std::cout << std::setw(5) << m3(i, j);
        }
        std::cout << std::endl;
    }
    return 0;
}

Update:

Source code

Unit Tests

Documentation

\$\endgroup\$
0
5
\$\begingroup\$

We have a stray typename keyword, that's considered incorrect by GCC 11:

template <typename scalarType, int rowsAtCompileTime, int colsAtCompileTime>
class Matrix<typename scalarType, rowsAtCompileTime, colsAtCompileTime, std::array<scalarType, rowsAtCompileTime* colsAtCompileTime>>
//           🔺🔺🔺🔺🔺 here

There are a few more instances of the same problem. Fixing these enables compilation.


My main concern with the code is its ambition. We're actually providing two different types, but shoehorning them into the same template. It might be better to provide separate types for compile-time constant size matrices and run-time-sized ones.

I would expect to see a std::initializer_list constructor, and I think that's more valuable than the << operator. Fixed-size matrices perhaps ought to be constructible from an appropriate std::array, too (open question - do you want to make a copy or just refer to the data?).


Please use std::size_t for dimensions rather than int.


What's the _size member for? It's never assigned or used.


There's no need to write inline when defining template functions.


Missing functionality:

  • I'd expect to see +=, -=, unary + and -; perhaps also scalar multiplication. Probably implement + and - in terms of += and -=.
  • Dot-product and cross-product should be provided.
  • A transpose function could be useful.
  • A rectangular view of a matrix is also often useful.
  • For square matrices, we might want to compute the determinant.

Some of the extra functionality might be simpler if we can move to C++20 and use the <concepts> header to constrain templates.


This isn't the best way to populate a vector:

template<typename scalarType>
inline Matrix<scalarType, 0, 0, std::vector<scalarType>>::Matrix(int m, int n) : _rows{ m }, _cols{ n }
{
    int numOfElements{ m * n };
    for (int i{}; i < numOfElements; ++i)
    {
        A.push_back(0);
    }

    currentPosition = A.begin();
}

We can make it more efficient using A.reserve(numOfElements), but really we should just construct it with the right size (and no longer depend on being able to convert 0 to T):

template<typename scalarType>
inline Matrix<scalarType, 0, 0, std::vector<scalarType>>::Matrix(std::size_t m, std::size_t n)
    : A(m * n),
      _rows{m}, _cols{n},
      currentPosition{A.begin()}
{
}

Something that will make the code much more useful is the ability to promote a matrix, e.g. from Matrix<int> to Matrix<long> (of the same dimensions). At present, the binary operations require two matrices of the same type, but they become a lot more powerful when we can put two different matrices together and get a result of their common type.

It's also a good idea to provide (explicit) narrowing conversions too.


The tests at present exercise only a small subset of the functionality. Consider learning to use one of the available test frameworks to get a more comprehensive set of self-checking unit tests. That's a whole subject in itself, but a very valuable skill to acquire.

When expanding the tests, remember to include some tests where T is an aggregate type such as std::complex.

\$\endgroup\$
3
  • \$\begingroup\$ std::span is an example of a class having such a dichotomy. Arguably, all allocator-aware containers would also apply, ymmv. ... \$\endgroup\$ Nov 10 at 12:56
  • \$\begingroup\$ @TobySpeight, I implemented row(A, i) and col(A, j) operations that return by value. But, I also would like these methods, such that they can be on the left-hand side of an expression. Do you think it's possible to achieve this with the current implementation? \$\endgroup\$
    – Quasar
    Nov 11 at 12:55
  • \$\begingroup\$ Sounds like a good self-review. Yes, it does make sense to make assignable versions of those functions - that would fall out naturally if you implement the rectangular views (since rows and cols are just special cases of such views). \$\endgroup\$ Nov 11 at 13:40
1
\$\begingroup\$

I overloaded the << and , operators, in a way that a matrix objects can be nicely initialized...

How's that any nicer than just using an initializer list or variadic parameter list in the constructor?


Your trivial member functions like size would be much simpler if coded inline in the class.


Your test code only tests one operation?
And, your need for nested loops in the test to print out the result shows that you need a element iterator supplied as part of the class.


Yes, you should abstract out the choice of container by using a base class that only holds the representation and any necessary functions for manipulating the container; then a single implementation of all the interesting math functions and simple accessors can be written in universal code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.