Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ - Overloading [] operators based on the side of assignment

I'm trying to write a dynamic array template in c++

I'm currently overloading the [] operators and I'd like to implement a different behavior based on which side of assignment they are used on.

#include <iostream>
...

template <class T>
T dynamic_array<T>::operator[](int idx) {
    return this->array[idx];
}

template <class T>
T& dynamic_array<T>::operator[](int idx) {
    return this->array[idx];
}

using namespace std;
int main() {
    dynamic_array<int>* temp = new dynamic_array<int>();

    // Uses the T& type since we are explicitly 
    // trying to modify the stored object
    (*temp)[0] = 1;

    // Uses the T type since nothing in the array 
    // should be modified outside the array
    int& b = (*temp)[0]; 

    // For instance...
    b = 4;
    cout<<(*temp)[0]; // Should still be 1
    return 0;
}

I get compiler errors when trying to overload like this for obvious reasons.

Is there a proper way to do this?

My search so far has not been successful. Anything I've seen with overloaded [] operators seems to accept that the user can modify the stored item outside of the object.

I've implemented methods to use (instance(int i), update(int i, T obj)) but it would be nice to be able to use this class like a regular array.

like image 833
JackWink Avatar asked Sep 14 '11 02:09

JackWink


2 Answers

You cannot overload only on return type.

The standard way to provide constant and non-constant accessor overloads is to differentiate by the constness of this:

T       & get()       { return x; }
const T & get() const { return x; }  // or T get() const;

For the constant version, you can return either a const-reference or by value, depending on what T is - const-reference is probably more universally useful.

(In place of get() you would write operator[](std::size_t i), of course. I just wanted to keep it short.)


I don't think this achieves 100% what you had in mind, but that's because you have an error in your reasoning: int b = foo() will never be a reference to anything, even if foo() returns a (const or non-const) reference, because b is declared to be of type int, not int&. Practically, you would actually call the non-const version when you say int b = (*temp)[0]; but that isn't actually a problem. (To get the constant version, you'd have to say int b = static_cast<const dynamic_array<int> &>(*temp)[0]; or (*static_cast<const dynamic_array<int> *>(temp))[0] - but why bother.)

like image 93
Kerrek SB Avatar answered Oct 20 '22 05:10

Kerrek SB


Scott Meyers talked about this in one of the Effective C++ books. Basically the trick was to return a temporary const- or non-const proxy object from the index operators (operator[]() and operator[]() const), then overload the assignment and implicit conversion operators for that proxy class. Something like this:

template <class T>
class Array
{
  public:
    struct proxy {
      T& element;

      proxy(T& el) : element(el) {}

      operator const T& () const {
        return element; // For use on RHS of assignment
      }

      proxy& operator=(const T& rhs) {
        // For use on LHS of assignment
        // Add your logic here
      }
    };

    const proxy operator[](int i) const {
      return proxy(a[i]);
    }

    proxy operator[](int i) {
      return proxy(a[i]);
    }

  private:
     T* a;
};

I may have some of the details wrong but the idea is to defer the decision of what side of the assignment the element is on until an actual attempt is made to assign to it. That is, you don't know what will be done at the time of the operator[] call, but you certainly do when you attempt to assign to the subsequent element reference.

like image 32
Drew Hall Avatar answered Oct 20 '22 06:10

Drew Hall