0

Consider the following simple class.

#include <iostream>

using namespace std;

class test
{
public:
  int* myvar;
  int sz;

  test()
  {
    sz = 10;
    myvar = new int[10];
  }

  void dump()
  {
    for(int i = 0; i < sz; i++)
    {
      cout << myvar[i] << " ";
    }
    cout << endl;
  }

  int& operator()(int index)
  {
    if(index >= sz)
    {
      int* newvar = new int[index+1];

      for(int i = 0; i < sz; i++)
      {
        newvar[i] = myvar[i];
      }
      sz = index+1;
      delete myvar;
      myvar = newvar;
    }
    return myvar[index];
  }

  const int operator()(int index) const
  {
    if(index >= sz)
    {
      throw "index exceeds dimension";
    }
    else
    {
      return myvar[index];
    }
  }
};

It should behave like a dynamic array. I overloaded the () operator. My idea was, that for an assignment (lvalue), the upper version of the () will be called, and for a "read only" operation (rvalue) the lower version of () is used. The sample code should explain more clearly what I mean:

int main()
{
  test x;

  // will give 10 times zero
  x.dump();

  // assign some values
  x(1) = 7;
  x(9) = 99;

  // will give
  // 0 7 0 0 0 0 0 0 0 99
  x.dump();

  // should give 7
  cout << x(1) << endl;

  // should give 99
  cout << x(9) << endl;

  // this will increase the size of myvar to 15 elements and assign a value
  x(15) = 15;

  // this should give
  // 0 7 0 0 0 0 0 0 0 99 0 0 0 0 0 15
  x.dump();

  // this should throw an exception because x(20) got never assigned a value!
  // but instead of calling the lower version of operator() it also calls the
  // upper, resulting in x being expanded now to 21 elements.
  cout << x(20) << endl;

  // will give 21 elements, instead of 16.
  x.dump();

  return 0;
}

So I access the contents of myvar via the () operator. It should be possible to assign a value just to any element, but it shall not be possible to query the value of an element that has never been set before. I thought by using different versions of (), one of which being const should suffice, but apparently, the compiler is always using the upper version of my operator, and never the lower. How can I fix this problem?

I read about the proxy object, e.g here, but I think this implementation will not work in my case because I am using an array. So a) is it possible without the proxy, or if not b) how should the proxy look like in my case?

T. Pluess
  • 165
  • 1
  • 12
  • https://stackoverflow.com/questions/28026352/is-there-any-real-use-case-for-functions-reference-qualifiers – LogicStuff May 19 '19 at 10:42
  • 1
    Which overload of `operator()` is called only depends on what `x` is. In your case, `x` is the same for all calls to `operator()` so the same overload is always called, and the standard says that in this case it's the non-`const` qualified. You clearly need a proxy in this case. – Holt May 19 '19 at 10:50
  • sure, x is the very same object in both situations, but in the first case, an assignment is made to x. In this situation, the value of x may be changed so its perfectly fine if the non-const version of the operator is called. But in the latter case, x is only read and thus its value cannot change, therefore it would be intuitive that the const operator is called! – T. Pluess May 19 '19 at 11:17
  • 1
    @T.Pluess No assignment is made to `x`, assignment is made to the return value of `x(...)`, which is not `x`. – Holt May 19 '19 at 11:30
  • sure. So how do I use the proxy in this situation? – T. Pluess May 19 '19 at 11:59
  • 1
    @T.Pluess Something like that: https://godbolt.org/z/MY52OR – Holt May 19 '19 at 12:21
  • that worked for me. Do you want to post an answer, such that I can mark it as solution? – T. Pluess May 19 '19 at 15:07

1 Answers1

0

So this is the solution I finally came up with (sort of):

#include <iostream>

using namespace std;

template <class T> class myclass
{
private:
  unsigned numel;
  T* elem;

public:

  class proxy
  {
    private:

        T*& elem;
        unsigned& numel;
        const unsigned index;

        proxy(T*& elem, unsigned& numel, unsigned index) : elem(elem), numel(numel), index(index) { }

        // didn't really need those two
        // proxy(const proxy&) = default;
        // proxy(proxy&&) = default;

        friend class myclass;

    public:
        proxy& operator=(const T& value)
        {
          if(index >= numel)
          {
            cout << "assignment to an element outside the range!" << endl;
            cout << "old size: " << numel << endl;
            cout << "new size: " << index+1 << endl << endl;

            T* newelem = new T[index+1];
            for(unsigned i = 0; i <= index; i++)
            {
              if(i < this->numel)
              {
                newelem[i] = this->elem[i];
              }
              else
              {
                newelem[i] = 0;
              }
            }

            if(this->elem != nullptr)
            {
              delete this->elem;
            }
            this->elem = newelem;
            this->numel = index+1;
          }

          this->elem[index] = value;
          return *this;
        }

        proxy& operator=(const proxy &other)
        {
          *this = (const T&)other;
          return *this;
        }

        operator T&()
        {
          if(index >= numel)
          {
            cout << "cannot query the value of elements outside the range!" << endl;
            cout << "# of elements: " << numel << endl;
            cout << "index requested: " << index << endl << endl;

            throw out_of_range("");
          }
          return elem[index];
        }

        operator const T&() const
        {
          if(index >= numel)
          {
            throw out_of_range("");
          }
          return elem[index];
        }
    };

  myclass() : numel(0), elem(nullptr) {};

  myclass(unsigned count)
  {
    this->numel = count;
    this->elem = new T[count];
  }

  ~myclass()
  {
    if(this->elem != nullptr)
    {
      delete this->elem;
    }
  }


  friend ostream& operator<<(ostream& os, const myclass& mc)
  {
    os << endl;
    for(unsigned i = 0; i < mc.numel; i++)
    {
      os << mc.elem[i] << "  ";
      os << endl;
    }
    os << endl;
    return os;
  }

  proxy operator()(unsigned index)
  {
    return proxy(this->elem, this->numel, index);
  }

};


int main()
{
  myclass<double> my;

  my(1) = 77;
  my(0) = 200;
  my(8) = 12;

  cout << my;

  try
  {
    cout << my(0) << endl;
    cout << my(1) << endl;
    cout << my(8) << endl;
    cout << my(10) << endl;
  }
  catch(...)
  {
    cout << "error catched" << endl << endl;
  }

  my(10) = 10101;

  cout << my(10) << endl;

}

the output on the terminal looks like this:

assignment to an element outside the range!
old size: 0
new size: 2

assignment to an element outside the range!
old size: 2
new size: 9


200  
77  
0  
0  
0  
0  
0  
0  
12  

200
77
12
cannot query the value of elements outside the range!
# of elements: 9
index requested: 10

error catched

assignment to an element outside the range!
old size: 9
new size: 11

10101
T. Pluess
  • 165
  • 1
  • 12