0

Is it possible to const-overload the function call operator operator() so that:

  • when the caller is on the left-hand side of an expression, the non-constant version is always called
  • when the caller is on the right-hand side of the expression, the constant version is always called

?

I'm still learning C++ and spent hours yesterday scouring the Internet to figure out this one. ChatGPT and some other answers here suggested that this would be possible using a trick involving a helper or proxy class, but that seems to entail constructing such an object every time my operator() is called. I'm wondering if this is possible in a less costly manner. I know that i can simply use a constant getter to solve this problem, but I'm curious to learn more about operator overloading.

Here's a toy program (hopefully) to clarify:

#include <iostream>
#include <vector>

// Implements a matrix. There are two arrays, a 'previous' and a 'current' one,
// to read the previous state from and to write the updated state into. Reading
// should always be from the 'previous' array and writing should always be to
// the 'current' array. The pointers to these arrays can be swapped.
class Matrix {
private:
    // matrix dimensions
    const int m_width;
    const int m_height;
    // arrays
    std::vector<double> m_arr1;
    std::vector<double> m_arr2;
    // 'current' and 'previous' pointers to arrays
    std::vector<double>* m_curr;
    std::vector<double>* m_prev;
public:
    Matrix(const int width, const int height)
        :
        m_width{width}, m_height{height},
        m_arr1(width*height, 0.0), m_arr2(width*height, 0.0),
        m_curr{&m_arr1}, m_prev{&m_arr2}
    {}
    // non-const version which I would always want to be called from the left-hand side
    double& operator()(const int i, const int j) {
        std::cout << "write to (" << i << ", " << j << ")" << std::endl;
        return (*m_curr)[m_width*j + i];
    }
    // const version which I would always want to be called from the right-hand side
    const double& operator()(const int i, const int j) const {
        std::cout << "read from (" << i << ", " << j << ")" << std::endl;
        return (*m_prev)[m_width*j + i];
    }
    void swap() {
        std::cout << "swap" << std::endl;
        std::vector<double>* temp{m_curr};
        m_curr = m_prev;
        m_prev = temp;
    }
};

int main()
{
    Matrix A{2, 2};
    A(1, 0) = -1;           // writes to 'current' array ('m_arr1')
    A.swap();               // arrays are swapped
    A(1, 0) = 3 + A(1, 0);  // I wish this would write to 'current' array ('m_arr2') from 'previous' array ('m_arr1')
    return 0;
}

The problem here is that since my 'Matrix' instances generally arent const, the constant version doesn't get called. Is there a way to get the constant version called from the right-hand side without constructing helper or proxy objects? Thank you!

  • This cannot be done in C++, C++ does not work this way, on a fundamental level. – Sam Varshavchik Mar 21 '23 at 11:11
  • No, not without some heavy proxy magic. More importantly, I pity any user that would have to work with that. – Quimby Mar 21 '23 at 11:15
  • Instead of having two `operator()`, just have two differently named member functions that have that behavior. You could use helper lambdas to do the getter-by-value, and mutator-by-reference, to make it easy to at the callsite. – Eljay Mar 21 '23 at 11:30
  • If you insist to use the `operator()`, you could involve an additional parameter to tag whether you want to access the new or old version. E.g. use an `enum { New, Old };` and the assignment could look like `A(1, 0, New) = 3 + A(1, 0, Old);`. – Scheff's Cat Mar 21 '23 at 11:32
  • I wonder a bit about all this back-up stuff. Are you aware that the expression at RHS is always evaluated before the expression on LHS of an assignment? So, the read will always be done before the write. (Otherwise, simple things like `i = i + 1;` wouldn't work properly.) I just recalled [Order of evaluation](https://en.cppreference.com/w/cpp/language/eval_order) to be sure: _20) In every simple assignment expression E1 = E2 and every compound assignment expression E1 @= E2, every value computation and side effect of E2 is sequenced before every value computation and side effect of E1_ – Scheff's Cat Mar 21 '23 at 11:33
  • Thanks for the comments! The jury is pretty unanimous on this one so got it. @Scheff'sCat, I'll just have two differently named functions. And I was under the impression that the RHS would be evaluated first. – Petri Hirvonen Mar 21 '23 at 17:11

0 Answers0