3

I'm writing a Matrix type, and I would like the following code to define a 2,2 integer matrix, put its first element at 3, and throw an exception upon reaching the third line:

Matrix<int> a(2,2);
a[0][0] = 3;
int b = a[10][28];

For this, I need to overload "operator[][]". Let me explain further:

  • My matrix is defined as a template with parameter type T and stores its elements in a 2-dimensional array defined as T** content.
  • My point in overloading "operator[][]" is to avoid an access violation and throw an exception that can be handled afterwards (an access violation is not an exception).
  • Overloading operator[] once would allow me to use a[0] and a[0] would return a T* type.
  • To overload the "operator[][]" of Matrix, I actually need to overload the operator[] of my aforementioned T* return type.
  • Since T* is an array, its operator[] is already defined and working fine, so there's no point in changing it.
  • Even if I did, I could not do anything relevant to my point because it is outside of the Matrix class.

How can I work this out?
I could make the first operator[] overload return a new MatrixRow object which I can work further with, but is it not a pretty heavy solution?

  • 4
    You already answered your own question... and a MatrixRow object is only as heavy as you make it, if it contains a copy of the whole row it will be expensive, but if it just points into the bigger matrix, it is as cheap to make as T*. Probably the reality is somewhere in the middle, you need a T* and a size_t to check the MatrixRow::operator[]() argument against. Another option is defining Matrix::operator()(int row, int col). – Ben Voigt Dec 9 '16 at 1:21
  • 1
    Cross-site duplicate of stackoverflow.com/q/6969881/103167 (also check the list of linked questions on that page) – Ben Voigt Dec 9 '16 at 1:22
  • @BenVoigt Ok I will dig into that thanks for the precisions! But it's curious, the question you mentioned didn't show up when I looked for it. – qreon Dec 9 '16 at 2:19
  • Another example: stackoverflow.com/a/2216055/179910 – Jerry Coffin Dec 9 '16 at 4:34
  • 7
    The MatrixRow object is an example of a proxy object in C++. For example, std::vector<bool>::operator[] returns a temporary proxy. Provided that the compiler is able to completely inline the creation, use, and destruction of the proxy object, it is possible that the compiler can transform the code so that the equivalent behavior is accomplished without actually having to create any object that consumes memory. Be sure to use appropriate optimization flags and verify the compiled disassembly. – rwong Dec 9 '16 at 5:08
6

There is no operator[][] in C++. When using multidimensional indices, operator[] is applied in sequence to the result of the previous one.

Example: a[i][j] applies operator[](i) to a. It then applies operator[](j) to the result.

Now there are at least 4 ways to solve your problem:

  • overload operator[]() with an object that combines both coordinates, for example by packing them into a pair with operator[] (pair<size_t, size_t>). This is nice in theory, but makes indexing look clumsy.
  • overload operator[] to return a reference to an object that represents the row (e.g. MatrixRow), and with itself has an appropriate operator[]. This is a very clean approach. However, it requires the Matrix to be an aggregate of MatrixRow (because you need to be able to return a reference to it). Unfortunately, this is now always possible (e.g. some sparse matrix structures use compression schemes that do not allow a directly addressable row object).
    Update: A variant of this approach could be to return a proxy object for the row instead of a reference. This is a little bit trickier because the proxy has to emulate the behavior of a row reference (see discussion in the comments) and it still needs a class to represent a full matrix row for this purpose. However, the additional complexity comes with the advantage of decoupling the internal representation of the matrix from the representation of a full row. (Thanks Angew for this suggestion)
  • expose the inner details of your implementation, by returning directly the pointer to the array. While this is very comfortable, it's really not a clean design, because you create a dependency of the using code to the inner details of your Matrix.
  • overload operator() , which allows for multiple parameters. The main issue here is to et accustomed to the use of () instead of []. If you go for this, you'd better not define an operator[] for the Matrix, just to avoid some subtle errors that might arise when accidentally combining [] with the use of the coma operator.

One question though: why do you still use arrays, when you could more safely use vectors instead ?

Example of the last approach:

template <class T>
class Matrix {
    vector<vector<T>> m; 
public: 
    Matrix (int r, int c) : m(r, vector<int>(c, T())) { }
    T& operator() (int r, int c) { return m[r][c]; }
    ...
};

Matrix a(2,2); 
a(1,0) = 5;
int e = a(1,0);

For more examples (using 3 of the 4 discussed approaches), see online demo.

  • I went with the second way, I used a MatrixRow object and it's working fine now. As for why I use arrays instead of vectors, the short answer is that I enjoy doing everything from scratch, and encountering problems like this one makes me able to solve them faster afterwards. The long answer is, I want to make my code as light as possible, even if it means only having the bare functionality, which is okay since it is going to be used by me only. :) – qreon Dec 12 '16 at 3:25
  • WRT the akwardness of matrix[make_pair(r,c)]: I expect that matrix[{r,c}] works in C++17. – Sjoerd Dec 12 '16 at 14:21
  • @Sjoerd shouldn't this already work in c++14 ? But playing with [{ }] as in the online demo shows that it'll be easy to forget a pair. Option 2 or 4 seem therefore better suited imho – Christophe Dec 13 '16 at 6:56
  • @Christophe Yes, could be C++14 already. And you're right that forgetting the {} in a[{1,2}] would cause a[1,2] to be interpreted as a[2]. However, when there is no overload for operator[](int) that should result in a compiler error, but it will be a bit cryptic. – Sjoerd Dec 13 '16 at 9:01
  • 1
    vector<vector<T>> m; DON'T do that. PLEASE. It not only creates unnecessary vectors (waste of space, time for allocation and has poor locality) but also allows to have a non-rectangular array which is unneeded. Just use std::unique_ptr<T[]> and make a custom address computing function. – Sopel Dec 15 '16 at 20:30

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.