1

I am working on my own matrix class in Qt. I know there is a QGenericMatrix class template, but I need to set the size at runtime, which is not possible with this one. Apart from that I see this as a nice project to revive my linear algebra knowledge by implementing this.

However, I have already been able to define the * operator (multiplication) as follows:

MyMatrix.h

public:
   MyMatrix(int rows, int cols, double initValues=0.0);        // constructor to create NxM matrix with N=col, M=rows

   MyMatrix& operator*(double value);                           // multiply matrix with a double 
private:
    int rows;
    int cols;
    double **mat;
    void initMatrix(int rows=1, int cols=1);        // initialise the matrix, if no rows,cols are given it creates a "1x1 matrix"

MyMatrix.cpp

// constructor
MyMatrix::MyMatrix(int rows, int cols, double initValues)
{
    initMatrix(rows, cols);
    for (int i = 0; i < this->rows; ++i) {
        for (int j = 0; j < this->cols; ++j) {
            this->mat[i][j] = initValues;
        }
    }
}
// multiply each element in matrix by value
MyMatrix& MyMatrix::operator*(double value) {
    for (int i = 0; i < this->rows; ++i) {
        for (int j = 0; j < this->cols; ++j) {
            this->mat[i][j] = this->mat[i][j] * value;
        }
    }
    return *this;
}
// initialise all matrix cells
void MyMatrix::initMatrix(int rows, int cols)
{
    this->rows = rows;                          // assign argument to member variable rows
    this->cols = cols;                          // assign argument to member variable cols

    this->mat = new double*[this->rows];        // initialise mat with list of doubles ptrs with length rows
    for (int i = 0; i < this->rows; ++i) {      // iterate over each row-element
        this->mat[i] = new double[this->cols];  // initialise each rows-element with list of doubles with length cols
    }
}

main.cpp

    int rows = 2;
    int cols = 3;
    MyMatrix mat1(rows, cols, 1.0);             // creates matrix with all elements 1.0

    mat1 = mat1 * 3.0;

Note I extracted only the relevant parts, the class has grown already, so I guess posting the all three files completely would be more confusing.

So far so good. The above seems to do what it should.


Now, I want to be able to directly access each element in the matrix. Similar to how one can access elements in a QVector, like so:

Read an element:

   double temp = mat1[2][2]               // read the element in row=2, column=2

Write to an element:

   double temp = 17;
   mat1[2][2] = temp              // set value of element in row=2, column=2 to given double temp (here 17).

But I do not know how to define this [][] operator. I tried the following definition analog to the multiplication with a double, and because I need to give the row and column. I thought I try:

MyMatrix.h

   MyMatrix& operator[int c][int r](double value);    // write
   MyMatrix& operator[int c][int r]();                // read

The implementation to overwrite/read the element in row r and column c which I have in mind should look like this:

MyMatrix.cpp

// write to element
MyMatrix& MyMatrix::operator[int r][int c](double value) {
    this->mat[r][c] = value;
    return *this;
}
// read to element
double& MyMatrix::operator[int r][int c]() {
    return this->mat[r][c];
}

But that does not do the Trick.

Btw: Even before compiling QtCreator says:

/path/MyMatrixClass/mymatrix.cpp:60: error: expected ']'
/path/MyMatrixClass/mymatrix.cpp:60: to match this '['
/path/MyMatrixClass/mymatrix.cpp:60: error: expected '(' for function-style cast or type construction
/path/MyMatrixClass/mymatrix.cpp:61: error: use of undeclared identifier 'r'
/path/MyMatrixClass/mymatrix.cpp:61: error: use of undeclared identifier 'c'

I tried already searching a for these errors, but so far I could not find anything giving me a clue to what I want to achieve.

So, perhaps someone can give me a link on where to look for an example or some advice on how I can achieve what I want.


PS: Later I also want to be able to extract a certain row, or certain column, but I guess (=hope) that should be straight forward once I know how to handle the [][] operator the right way.

This is the first time I am really defining my own operators for a class. And I think I got the general idea from the * operator. (I have also + and - operators already working). However, until now I used Qt mostly for GUI building, simple data handling with QVectors, plotting spectra and alike. So, I guess I am just missing some basic Qt/c++ syntax.

PatZim
  • 11
  • 5
  • [mcve] with emphasis on **minimal** please. – SergeyA Aug 16 '19 at 18:56
  • [][] is not an operator. See here: https://softwareengineering.stackexchange.com/questions/337802/can-i-overload-operator – drescherjm Aug 16 '19 at 18:58
  • 1
    Why not use `std::vector` instead of `double *`? Your class, given what you posted, violates the rule of 3. – PaulMcKenzie Aug 16 '19 at 19:01
  • 2
    There is no `operator[][]`. The closest thing is an `operator[]` that returns a type that also has an `operator[]`. – François Andrieux Aug 16 '19 at 19:02
  • [A worthwhile model for a simple matrix class.](https://stackoverflow.com/a/2076668/4581301). It's simple, [Rule of Five](https://en.cppreference.com/w/cpp/language/rule_of_three) compliant and cache friendly. – user4581301 Aug 16 '19 at 19:02
  • [Please read this FAQ](https://isocpp.org/wiki/faq/operator-overloading#matrix-array-of-array) on using `operator[]`. – PaulMcKenzie Aug 16 '19 at 19:04
  • Extracting one of rows or columns is usually easy. Extracting the other requires you to think in [strides](https://en.wikipedia.org/wiki/Stride_of_an_array) or copy. – user4581301 Aug 16 '19 at 19:07
  • 1
    @drescherjm Thanks for the link. I am still not fully understanding everything in there, but I get that I cannot define a [][] operator, but instead I need to overload a [] operator. The first returns a row, and the second [] uses that row to find the element in that row. And now it seems to make sense to use a QVector instead of a list of doubles, because then the first [] operator could return a QVector element. (Why I didn't think of that in the first place, it would give me all the power from the QVector type.) I shall try to change my code using the infos in that link you posted. – PatZim Aug 16 '19 at 19:08
  • 1
    I like the code for this answer (2nd code using proxy): https://stackoverflow.com/a/2216055/487892 – drescherjm Aug 16 '19 at 19:12
  • @PatZim If you have an `n x m` matrix, accessing it with `[]` should return a reference to a `1 x m` matrix. This could be used to implement an n-dimensional matrix, which accessed with `[]` will give you an n-1 matrix, which accessed with `[]` will again give you an n-2 matrix, until you get to the scalar. – Mirko Aug 16 '19 at 19:35
  • This is a short way to get a 2D Matrix where elements can be accessed by `[i][j]` https://stackoverflow.com/questions/36123452/statically-declared-2-d-array-c-as-data-member-of-a-class/36123944#36123944 Only `[]` is overloaded and it returns a pointer to a row which is accessed automatically with the second `[]`. – doug Aug 16 '19 at 23:16

3 Answers3

1

You can have the matrix use the operator() with as many parameters as you like. Squared brackets won't work.

double const& operator()(size_t rowIndex, size_t colIndex) const;
double& operator()(size_t rowIndex, size_t colIndex);

Then, you can use your matrix class like this

Matrix M(n, n);
for (size_t i = 0; i < n; ++i) {
    M(i, i) = 1.0;
}
return M;
AlexG
  • 1,091
  • 7
  • 15
  • Indeed, this is actually a common technique [according to cppreference](https://en.cppreference.com/w/cpp/language/operators#Array_subscript_operator). See [Eigen](https://eigen.tuxfamily.org/dox/group__TutorialMatrixClass.html#title4) for an example. – Mike Aug 16 '19 at 20:14
0

There is no [][] operator. If you use the [] on two-diminsional array, you will get another pointer. Finally, you apply [] operator a second time on pointer returned from the first [] operator. Can use this little hack below. But this wont solve your problems on the long run:

#include <iostream>
#include <vector>
class Matrix
{
    int rows, cols;
    double**data;
public: 
    Matrix(int rows, int cols, double init)
        :rows(rows),cols(cols),data(new double*[rows])
    {
        for (int i = 0; i < this->rows; ++i) {
            data[i] = new double[cols];
            for (int j = 0; j < this->cols; ++j) {
                this->data[i][j] = init;
            }
        }
    }

    const double *operator[](int row)const
    {
        return data[row];
    }

    const double& operator[](const std::pair<int,int>& key)const
    {
        return data[key.first][key.second];
    }

    double& operator[](const std::pair<int, int>& key)
    {
        // validate the key
        return data[key.first][key.second];
    }

    double *operator[](int row)
    {
        return data[row];
    }
};

int main()
{
    Matrix m(3, 4, 0);

    m[2][1] = 3;

    std::cout << m[std::make_pair(2, 1)];
    // C++17 also allows:
    // std::cout << m[{2, 1}];
    std::cin.get();

}

Outputs: 3

I added a std::pair approach, m[{2, 1}] looks like syntax suggar. I cannot recommend the pointer solution. You dont have to reinvent the wheel neither. Take a look at newmath 11: http://www.robertnz.net/nm11.htm

StephanH
  • 463
  • 4
  • 12
  • There are caveats with this approach because it allows a lot of misbehaviour. For example `delete m[4];` – user4581301 Aug 16 '19 at 19:10
  • Of course, you are right. Reading is fine but the non-const operator is a serious threat. He may should use another function for writing instead. – StephanH Aug 16 '19 at 19:16
  • @StephanH Thanks, that also helps a bit to better understand what I need to do. I will try to change everything to use QVectors, such that the matrix becomes like QVector> Not sure yet how to do this exactly, but this is part of the learning now. btw: I know there are several math classes for matrices, but 1. I like Qt, 2. I want to refresh my linear algebra and 3. I want to learn how it can be done. (knowing how to define a class with operator like this can always be handy...) – PatZim Aug 16 '19 at 19:36
  • You are welcome. I would recommend you to use std::vector> instead. I really like Qt but i prefer to use the standard stl libraries doing such 'simple' tasks. A one dimensional array/vector would be fine too. if you save the number of rows and columns (as you do), you can always translate to indices (row,col) into the corresponding index of your one dimensional array/vector. You can learn a lot about overloading operator by creating such a problem. If you aim to use it in a more complex context, you should youe newmat11 instead. – StephanH Aug 16 '19 at 19:52
0

So, since the QVector class is already quite powerful, and I assume it shall later more easy to deal with vector operations on my matrix class, I did now the following

My matrix class now the following content:

MyMatrix.h

Public:
    MyMatrix(int rows, int cols, double initValues=0.0);   // constructor 
    QVector<double> operator[](int idx);

Private:
    QVector<QVector<double>> *mat;

My implementation of the operator is then as follows:

MyMatrix.cpp

// constructor
MyMatrix::MyMatrix(int rows, int cols, double initValues)
{
    QVector<double> tmpRow(cols, initValues);
    this->mat = new QVector<QVector<double>>(rows, tmpRow);
}

// operator definition
QVector<double> MyMatrix::operator[](int idx) {
    QVector<double> temp = this->mat[0][idx];
    return temp;
}

I dont really understand though why the [0] is needed. Because mat is a QVector< QVector < double >>, so the first should give a QVector< double >, but for some reason it behaves not as I would expect, i.e. this->mat[idx] should return the QVector< double >.

Anyway, with the above I can now use it like this:

main.cpp

    qDebug() << mat1[0];           // prints the QVector in row=0    
    qDebug() << mat1[1];           // prints the QVector in row=1
    qDebug() << mat1[1][1];        // prints element in row=1 and column=1
    qDebug() << mat1[1][2];        // prints element in row=1 and column=2  

This then prints:

QVector(1, 1, 1)
QVector(1, 1, 1)
1
1

So that is not yet really finished, but I think I am now heading in the right direction.

Btw, @PaulMcKenzie shared a link saying that () is better than []. I implemented the operator for () and []. Both worked, just with the exception, that the syntax is with () slightly different; the usage is then:

main.cpp

    qDebug() << mat1(0);           // prints the QVector in row=0    
    qDebug() << mat1(1)[1];        // prints element in row=1 and column=1

Which looks odd. So I think in my case the [] looks better, and I can directly use the QVector as normal with the [].

I will look into this in more detail tomorrow to see if I can now achieve what I want. Either way, so far thank for all the input. Any comment is greatly appreciated.

PatZim
  • 11
  • 5