23

I'm having trouble with telling the difference between get and set for the operator[]. I need to tell the difference between these function calls.

cout << data[5];
data[5] = 1;

I googled it, and the answers I found still didn't help. People suggested making the signatures for the methods different by adding const. I did that, and they still both called the same method.

There are the signatures I had used:

const T& operator[](unsigned int index) const;
T& operator[](unsigned int index);

What am I doing wrong?

Taztingo
  • 1,915
  • 2
  • 27
  • 42
  • 4
    You might want to look up *proxy objects*, which are often used to distinguish reads from writes. – templatetypedef Oct 07 '13 at 05:46
  • 1
    See Item 30 "Proxy Classes" from [More Effective C++ by Scott Meyers](http://www.amazon.com/More-Effective-Improve-Programs-ebook/dp/B004VSMDNY/ref=la_B004BBEYYW_1_2?s=books&ie=UTF8&qid=1381129998&sr=1-2) for an extensive discussion on this subject, including surprising pitfalls (related to user-defined conversions) that proxy classes can give you. That material is [mandatory reading](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – TemplateRex Oct 07 '13 at 07:12

4 Answers4

27

The solution is to use a "proxy" object that will delay the actual operation:

#include <vector>
#include <iostream>

template<typename T>
struct MyArray {
    std::vector<T> data;
    MyArray(int size) : data(size) {}

    struct Deref {
        MyArray& a;
        int index;
        Deref(MyArray& a, int index) : a(a), index(index) {}

        operator T() {
            std::cout << "reading\n"; return a.data[index];
        }

        T& operator=(const T& other) {
            std::cout << "writing\n"; return a.data[index] = other;
        }
   };

   Deref operator[](int index) {
       return Deref(*this, index);
   }
};

int main(int argc, const char *argv[]) {
    MyArray<int> foo(3);
    foo[1] = 42;
    std::cout << "Value is " << foo[1] << "\n";
    return 0;
}

Simple const-ness cannot be used because you may need to read from a non-const instance, that is the reason for which you must delay the operation: the assignment happens "after" the access and the compiler doesn't tell you if the access will be later used as a target for assignment or not.

The idea is therefore that on access you just store away the index that has been requested and wait to know if a reading or a writing operation is happening. By providing an implicit conversion operator from the proxy to T you know when a reading operation occurs, by providing an assignment operator to the proxy from T you know when writing occurs.

6502
  • 112,025
  • 15
  • 165
  • 265
4

The const version means that if the object on which it is called is const, you are allowed to call that version of the [] operator, and only that version.

But if the object is not const, then both versions of the [] operator can be called, but the compiler will select the non-const version. In other words, for a non-const object, the non-const version of the operator can act as either the "setter" or "getter". This is why the same version is called in both cases in your example, because your data object is not const.

You would have to do something like this:

const Data& data_cref = data;
cout << data_cref[5];  // calls the const version of operator[]

Data& data_ref = data;
data_ref[5] = 1;       // calls the non-const version of operator[]
Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
1

To be compatible with the ordinary meaning of subscript, the subscript operator usually returns a reference to the element that is fetched. By returning a reference, subscript can be used on either side of an assignment.

Consequently, it is also usually a good idea to define both const and nonconst versions of this operator. When applied to a object, subscript should return a reference to const so that it is assign to the returned object.

----C++ Primer, Fifth Edition

Terran
  • 649
  • 10
  • 24
0

const version of your operator[] will be called for const data object:

const Data data;
cout << data[5];
cpp
  • 3,743
  • 3
  • 24
  • 38