1

Suppose the following is the [] operator implementation for vector:

template<class T>
T& Vector<T>::operator[](unsigned int index)
{
    if(index >= my_capacity) {
        if(1 /*something to check that [] operator was used in RHS (means read)*/) cout << "Out of bounds read" << endl;
        else cout << "Out of bounds write" << endl; //means write operation
    }
    return arr[index];
}

And now inside main():

Vector<int> v(5, 10); //initializes 5 values, each = 10

Now, let's say I have 2 statements

int ans = v[10]; //First
v[10] = 1; //Second

I basically want to get the output as:

Out of bounds read
Out of bounds write

How should I decide the condition inside the 'if' for the same (that whether the call to [] operator is a read or a write one)? Can anyone please help me in this? Thanks a lot!

love_to_code
  • 77
  • 1
  • 6

3 Answers3

7

You can't.

The workaround is to return not a reference, but instead a proxy object that can detect whether it's read from or written to. But because C++ doesn't (yet) allow overloading operator ., such proxies can never be fully transparent.

My experience says that it's not worth it.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
3

The only way to do what you want is to have operator[] return a proxy object that implements separate conversion (read) and assignment (write) operators, eg:

template<class T>
struct VectorProxy
{
    Vector<T> &vec;
    unsigned int index;

    operator T&() {
        if (index >= vec.my_capacity) {
            throw std::runtime_error("Out of bounds read");
        return vec.arr[index];
    }

    VectorProxy& operator=(const T &val) {
        if (index >= vec.my_capacity) {
            throw std::runtime_error("Out of bounds write");
        vec.arr[index] = val;
        return *this;
    }
};

template<class T>
VectorProxy Vector<T>::operator[](unsigned int index)
{
    return VectorProxy{*this, index};
}

/* optionally:

template<class T>
struct VectorConstProxy
{
    const Vector<T> &vec;
    const unsigned int index;

    operator const T&() const {
        if (index >= vec.my_capacity) {
            throw std::runtime_error("Out of bounds read");
        return vec.arr[index];
    }
};

template<class T>
VectorConstProxy Vector<T>::operator[](unsigned int index) const
{
    return VectorConstProxy{*this, index};
}
*/
int main()
{
    ...
    Vector<int> v(5, 10);

    try {
        int ans = v[10];
    }
    catch (const std::exception &e) {
        cout << e.what() << endl;
    }

    try {
        v[10] = 1;
    }
    catch (const std::exception &e) {
        cout << e.what() << endl;
    }

    ...
}

Obviously, Vector(Const)Proxy will have to be declared as a friend of Vector if arr and my_capacity are not public.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
1

Another approach.

  1. Define couple of helper structs -- one to use for reading from and the other to write to.

  2. Overload operator[] with those types.

  3. Update the calls to use the appropriate helper objects.

struct ReadIndex { unsigned int index = 0;};
struct WriteIndex { unsigned int index = 0;};

template<class T>
T const& Vector<T>::operator[](ReadIndex r) const
{
    if(r.index >= my_capacity) {
        cout << "Out of bounds for read" << endl;
    }
    return arr[r.index];
}

template<class T>
T& Vector<T>::operator[](WriteIndex w)
{
    if(w.index >= my_capacity) {
        cout << "Out of bounds for write" << endl;
    }
    return arr[w.index];
}

Usage:

int ans = v[ReadIndex{10}];
v[WriteIndex{10}] = 1;
R Sahu
  • 204,454
  • 14
  • 159
  • 270