Thread: Operator overloading issue - C++

  1. #1
    Registered User
    Join Date
    Nov 2019
    Posts
    135

    Operator overloading issue - C++

    Hey everyone.

    I'm studying now C++ and we are taught that there are often methods which has to be defined in two versions: const and non-const.

    For example, if we want to implement some indexing operator for a Matrix class - we probably should provide the user with two versions of such method.

    For example:

    Code:
        float& operator()(int i, int j);
        float operator()(int i, int j) const;
    But, I'm confused about the necessity of that.
    Can someone explain carefully why is that really necessary?

    Thanks in advance!

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    Obviously you need a non-const version or else you would never be able to change values.
    A const version is also needed so that the object can be passed to functions that promise not to change it.
    Code:
    #include <iostream>
    #include <iomanip>
    #include <algorithm>
     
    class Matrix {
        int *m_vals;
        int m_rows, m_cols;
    public:
        Matrix(int rows, int cols)
            : m_vals(new int[rows * cols]), m_rows(rows), m_cols(cols) {
            std::fill(&m_vals[0], &m_vals[rows*cols], 0);
        }
        ~Matrix() { delete[] m_vals; }
        int rows() const { return m_rows; }
        int cols() const { return m_cols; }
        int& operator()(int row, int col)
            { return m_vals[row * m_cols + col]; }
        int operator()(int row, int col) const
            { return m_vals[row * m_cols + col]; }
    };
     
    void print(const Matrix& m) {
        for (int row = 0; row < m.rows(); ++row) {
            for (int col = 0; col < m.cols(); ++col)
                std::cout << std::setw(3) << m(row, col);
            std::cout << '\n';
        }
    }
     
    int main() {
        Matrix m(5, 8);
        m(1, 2) = 42;
        print(m);
    }
    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @john.c
    I didn't really catch the point.

    Suppose we have some method which is destined to do some modifications - so how can we define this kind of method a const one at all? It doesn't make sense.

    I am not sure I understand the point of your code in respect to our issue, but:
    You have defined one of the operators as non-const and it returns a reference, and the const one is returning a copy, I don't know what the reason is yet, but I'm approaching now to the print and main:
    Don't know what's the role of std::setw(3) but if I skip it - You have constructed a matrix (5, 8), then assigned to 1,2 cell the value of 42.
    Now you're passing this matrix as const the the print func and each iteration you call it with the const operator.

    What's the problem?

    Thank you!!

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by HelpMeC
    Suppose we have some method which is destined to do some modifications - so how can we define this kind of method a const one at all? It doesn't make sense.
    That's why you define the method to be non-const.

    But if you want the method to also be called in a const context, you have to define it as const.

    So, you have to define it as non-const, but you also have to define it as const! A conundrum! What to do? Just overload to have one non-const version and one const version. Problem solved.

    Quote Originally Posted by HelpMeC
    I am not sure I understand the point of your code in respect to our issue
    It's an example. It could be educational to comment out the const version and try to compile the code so you can see the compile error, then to uncomment the const version and comment out the non-const version so you can see the compile error.

    Quote Originally Posted by HelpMeC
    You have defined one of the operators as non-const and it returns a reference, and the const one is returning a copy, I don't know what the reason is yet,
    Returning a reference allows the item to be modified by the caller; returning a copy does not.
    Last edited by laserlight; 12-24-2019 at 07:39 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @laserlight -
    I have compiled the code through Online GDB C++ compiler.
    I have noticed that the print function requires a const version of the operator as the argument there is const. Where in the main function the assignment just requires that the operator is returning a reference to the element so we can change it.

    So, what I have done to fulfill all this requirements, yet we have one sufficient version:
    Code:
        int& operator()(int row, int col) const
            { return m_vals[row * m_cols + col]; }
    The code runs and compiles, as expected.

    I guess there is something wrong with this approach but not sure exactly what.

    Thank you very much.

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by HelpMeC
    I guess there is something wrong with this approach but not sure exactly what.
    The first problem is that the behaviour is undefined when the Matrix object really is const, rather than merely non-const in a const context. The second problem is that the behaviour is unexpected to the reader: you declared the member function to be const, and yet you permit a change to the observable state of the object through that member function.

    That is, you have chosen to lie to both the compiler and other programmers. Don't do that.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @laserlight -

    ahh, you're right of course.

    If I change the code to my suggested version:
    Code:
    #include <iostream>
    #include <iomanip>
    #include <algorithm>
      
    class Matrix {
        int *m_vals;
        int m_rows, m_cols;
    public:
        Matrix(int rows, int cols)
            : m_vals(new int[rows * cols]), m_rows(rows), m_cols(cols) {
            std::fill(&m_vals[0], &m_vals[rows*cols], 0);
        }
        ~Matrix() { delete[] m_vals; }
        int rows() const { return m_rows; }
        int cols() const { return m_cols; }
        int& operator()(int row, int col) const
            { return m_vals[row * m_cols + col]; }
    };
      
    void print(const Matrix& m) {
        for (int row = 0; row < m.rows(); ++row) {
            for (int col = 0; col < m.cols(); ++col)
                std::cout << std::setw(3) << m(row, col);
            std::cout << '\n';
        }
    }
      
    int main() {
        const Matrix m(5, 8);
        m(1, 2) = 42;
        print(m);
    }
    Then, I'm actually violating the const-correctness of the const Matrix m, enabling to bypass his const state by some method...

    Think I got it.
    Thank you!!!

  8. #8
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    BTW, how can I identify which method has to be defined in two version (const and non-const)?
    There is some rule or something like that which can indicate that some method has to have 2 versions?...

  9. #9
    Registered User
    Join Date
    May 2009
    Posts
    4,183
    I am a C programmer who has spent some time trying to learn C++.

    Tim S.

    Code:
    #include <iostream>
    #include <iomanip>
    #include <algorithm>
    
    class Matrix {
    private:
        int *m_vals;
        int m_rows, m_cols;
    public:
        Matrix(int rows, int cols)
            : m_vals(new int[rows * cols]), m_rows(rows), m_cols(cols) {
            std::fill(&m_vals[0], &m_vals[rows*cols], 0);
        }
        ~Matrix() { delete[] m_vals; }
        int rows() const { return m_rows; }
        int cols() const { return m_cols; }
        const int& operator()(int row, int col) const
            { return m_vals[row * m_cols + col]; }
        int& operator()(int row, int col)
            { return m_vals[row * m_cols + col]; }
    };
    
    void print(const Matrix& m) {
        for (int row = 0; row < m.rows(); ++row) {
            for (int col = 0; col < m.cols(); ++col)
                std::cout << std::setw(3) << m(row, col);
            std::cout << '\n';
        }
    }
    
    int main() {
        Matrix m(5, 8);
        m(1, 2) = 42;
        print(m);
    }
    "...a computer is a stupid machine with the ability to do incredibly smart things, while computer programmers are smart people with the ability to do incredibly stupid things. They are,in short, a perfect match.." Bill Bryson

  10. #10
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @stahta01 -
    Yea, it seems that your version also holds the const-correctness as laserlight and john have mentioned, but your solution is better (?) as you don't return a copy but a const reference (more efficient).

    Thanks,
    Maybe you can answer my last question:
    "BTW, how can I identify which method has to be defined in two version (const and non-const)?
    There is some rule or something like that which can indicate that some method has to have 2 versions?..."
    Last edited by HelpMeC; 12-25-2019 at 09:18 AM.

  11. #11
    Registered User
    Join Date
    May 2009
    Posts
    4,183
    I know of no rules; but, I have yet to learn c++ after more than a decade of trying for a few weeks at a time and then giving up for a year or so.

    But, my work pattern is to start out with no const and then add them to the parameters [that logically should be const] and fix the resulting errors by making const and non const methods.

    Edit:
    So, my first const would have been.
    Code:
    void print(const Matrix& m) {
    I also make all my getters to const if they return POD types like int.
    Edit2: POD means plain old data (non class types)

    NOTE: You main issue was if you make a method const and the method returns a reference you almost always need to make that reference a const.

    Tim S.
    Last edited by stahta01; 12-25-2019 at 10:01 AM.
    "...a computer is a stupid machine with the ability to do incredibly smart things, while computer programmers are smart people with the ability to do incredibly stupid things. They are,in short, a perfect match.." Bill Bryson

  12. #12
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @stahta01 -
    You know, I think your approach is wrong, doing that thing:
    Code:
        const int& operator()(int row, int col) const
            { return m_vals[row * m_cols + col]; }
    In case of:
    Code:
    // Constructor
    Matrix m1(5, 5);
    Matrix m2(5, 5);
    // The non const operator()
    m1(0, 0) = 1;
    m2(0, 0) = 2;
    // So far so good
    // Now, let's do the following one:
    m1(0, 0) = m2(0, 0); // I think here the compiler calls the const version of () invoked by m2. But now you have an element m1(0, 0) which is const reference - I think it's some of buggy

  13. #13
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    m1(0, 0) = m2(0, 0); // I think here the compiler calls the const version of () invoked by m2. But now you have an element m1(0, 0) which is const reference - I think it's some of buggy
    You may want to consider creating the copy constructor and the assignment operator overrides, m1(0, 0) = m2(0, 0); probably requires the assignment operator.

  14. #14
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @jimblumberg -
    It's an assignment between ints

  15. #15
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by HelpMeC
    BTW, how can I identify which method has to be defined in two version (const and non-const)?
    There is some rule or something like that which can indicate that some method has to have 2 versions?...
    It's just what your class requires. You'll know when you start thinking about how you intend objects of your class to be used. It's a fairly common thing when overloading operator[] or providing an at() member function for a collections class, or in this case overloading operator() as a kind of two parameter operator[].

    Quote Originally Posted by HelpMeC
    but your solution is better (?) as you don't return a copy but a const reference (more efficient).
    It's possibly worse because ints are cheap to copy. If the objects are expensive to copy then it would make sense to return a const reference.

    Quote Originally Posted by HelpMeC
    You know, I think your approach is wrong, doing that thing:
    No, both m1 and m2 are non-const, so the non-const overload will always be called. When in doubt, add print statements to instrument the overloads so you can see which one is called.

    Having said that, you should understand that what's happening here is copy assignment: the return value of m2(0, 0) will be copy assigned to the object referred to by the return value of m1(0, 0). So even if m2 is const, there's no issue.

    Quote Originally Posted by HelpMeC
    It's an assignment between ints
    Yes, although in real code, it is true that that Matrix class must have the copy constructor and copy assignment operator implemented or disabled.
    Last edited by laserlight; 12-25-2019 at 01:14 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Issue overloading stream Operator/Manipulator
    By Jaken Veina in forum C++ Programming
    Replies: 3
    Last Post: 06-08-2011, 12:48 PM
  2. Operator overloading compilation issue in GCC
    By redneon in forum C++ Programming
    Replies: 5
    Last Post: 03-13-2011, 07:53 PM
  3. Overloading Issue
    By CornedBee in forum C++ Programming
    Replies: 7
    Last Post: 09-23-2007, 06:03 AM
  4. Simple operator overloading issue
    By Desolation in forum C++ Programming
    Replies: 1
    Last Post: 05-09-2007, 08:56 PM
  5. Overloading << Issue
    By dld333 in forum C++ Programming
    Replies: 1
    Last Post: 10-27-2005, 02:18 AM

Tags for this Thread