Search code examples
c++operator-overloadingvariable-assignmentsubscription

Assignment and retrieval using subscript and equals operator overloads


I've been having a hell of a time trying to solve this. What I am trying to do is use operator overloading so that my objects behave more like a multi dimensional array. I've found solutions to several of the smaller problems involved in making this happen but whenever I try to put it all together there is one issue or another, either lvalue assignment error or invalid initialization from rvalue or just straight up seg fault. I would appreciate any advice TY.

#include <iostream>
#include <vector>
#include <string> 

class Matrix {
  std::string **m;
 public:
  Matrix(int x, int y) {
    m = new std::string*[x];
    for (int i = 0; i < x; i++)
      m[x] = new std::string[y];
  }
  class Proxy {
    std::string *mm;
    int lastIndex = 0;
  public:
    Proxy(std::string *s) : mm(s) {}
      std::string &operator[](int index) {
      lastIndex = index;
      return mm[index];
    }
    std::string &operator=(std::string s) {
      mm[lastIndex] = s;
      return mm[lastIndex];
    }
  };
  Proxy operator[](int index) {
    return Proxy(m[index]);
  }
};

int main()
{
  Matrix *m = new Matrix(5, 5);
  m[2][2] = std::string("It Works");
  std::cout << m[2][2] << std::endl;
  return 0;

Solution

  • In main(), m is a pointer to a Matrix object, so you need to dereference the pointer in order to access the Matrix object so you can invoke your Matrix::operator[] on it, eg:

    int main()
    {
      Matrix *m = new Matrix(5, 5);
      (*m)[2][2] = "It Works";
      std::cout << (*m)[2][2] << std::endl;
      delete m;
      return 0;
    }
    

    Online Demo

    Otherwise, the pointer is not really needed in your example to begin with, eg:

    int main()
    {
      Matrix m(5, 5);
      m[2][2] = "It Works";
      std::cout << m[2][2] << std::endl;
      return 0;
    }
    

    Online Demo

    Either way, your Proxy does not need to implement operator= at all, eg:

    class Proxy {
      std::string *mm;
    public:
      Proxy(std::string *s) : mm(s) {}
    
      std::string& operator[](int index) {
        return mm[index];
      }
    };
    

    A statement like m[2][2] = "..."; will not invoke your Proxy::operator=, it will invoke only Proxy::operator[]. A statement like m[2] = "..."; would be needed to invoke Proxy::operator=, which doesn't make sense to do in a multi-dimensional scenario.


    Also, your Matrix constructor has a bug - writing to m[x] is going out of bounds of the m[] array, so the array is not actually filled at all, and you are corrupting surrounding memory, and leaking memory. You need to write to m[i] instead:

    //m[x] = new std::string[y];
    m[i] = new std::string[y];
    

    After fixing that, Matrix is still leaking memory, as it does not implement a destructor to free the std::strings. You must delete[] anything you new[] (same with delete and new).

    And then, you should finish off implementing support for the Rule of 3/5/0, by implementing a copy constructor and a copy assignment operator (your example code does not need them, but production code should always have them), eg:

    #include <iostream>
    #include <string> 
    #include <utility>
    
    class Matrix {
        std::string **m;
        int m_x, m_y;
    public:
        Matrix(int x = 0, int y = 0) : m_x(x), m_y(y) {
            m = new std::string*[x];
            for (int i = 0; i < x; ++i)
                m[i] = new std::string[y];
        }
    
        Matrix(const Matrix &src) : m_x(src.m_x), m_y(src.m_y) {
            m = new std::string*[m_x];
            for (int i = 0; i < m_x; ++i) {
                m[i] = new std::string[m_y];
                for (int j = 0; j < m_y; ++j) {
                    m[i][j] = src.m[i][j];
                }
            }
        }
    
        ~Matrix() {
            for (int i = 0; i < m_x; ++i)
                delete[] m[i];
            delete[] m;
        }
    
        Matrix& operator=(const Matrix &rhs) {
            if (&rhs != this) {
                Matrix temp(rhs);
                std::swap(m, temp.m);
                std::swap(m_x, temp.m_x);
                std::swap(m_y, temp.m_y);
            }
            return *this;
        }
        
        class Proxy {
            std::string *mm;
        public:
            Proxy(std::string *s) : mm(s) {}
    
            std::string& operator[](int index) {
                return mm[index];
            }
        };
    
        Proxy operator[](int index) {
            return Proxy(m[index]);
        }
    };
    
    int main()
    {
        Matrix m(5, 5);
     
        m[2][2] = "It Works";
        std::cout << m[2][2] << std::endl;
     
        Matrix m2(m);
        std::cout << m2[2][2] << std::endl;
     
        Matrix m3;
        m3 = m2;
        std::cout << m3[2][2] << std::endl;
    
        return 0;
    }
    

    Online Demo

    However, rather than using new[] manually, consider using std::vector instead (which you are already aware of, since you have #include <vector> in your code). This way, the Rule of 3/5/0 can be handled entirely by the compiler for you. std::vector and std::string are both fully compliant with the Rule, and so any compiler-generated destructor, copy constructor, and copy-assignment operator in Matrix will suffice, eg:

    #include <iostream>
    #include <vector>
    #include <string> 
    
    class Matrix {
        std::vector<std::vector<std::string>> m;
    public:
        Matrix(int x = 0, int y = 0) {
            m.resize(x);
            for (int i = 0; i < x; ++i)
                m[i].resize(y);
        }
    
        class Proxy {
            std::vector<std::string> &mm;
        public:
            Proxy(std::vector<std::string> &s) : mm(s) {}
    
            std::string& operator[](int index) {
                return mm[index];
            }
        };
    
        Proxy operator[](int index) {
            return Proxy(m[index]);
        }
    };
    

    Online Demo