Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

18.5 Access to vector elements

So far (§17.6), we have used set() and get() member functions to access elements. Such uses are verbose and ugly. We want our usual subscript notation: v[i]. The way to get that is to define a member function called operator[]. Here is our first (naive) try:

class vector {
      int sz;                   // the size
      double* elem;             // a pointer to the elements
public:
      // . . .
          double operator[](int n) { return elem[n]; }       // return element
};

That looks good and especially it looks simple, but unfortunately it is too simple. Letting the subscript operator (operator[]()) return a value enables reading but not writing of elements:

vector v(10);
double x = v[2];                // fine
v[3] = x;                      // error: v[3] is not an lvalue

Here, v[i] is interpreted as a call v.operator[](i), and that call returns the value of v’s element number i. For this overly naive vector, v[3] is a floating-point value, not a floating-point variable.

Our next try is to let operator[] return a pointer to the appropriate element:

class vector {
      int sz;                     // the size
      double* elem;                // a pointer to the elements
public:
     // . . .
     double* operator[](int n) { return &elem[n]; }       // return pointer
};

Given that definition, we can write

vector v(10);
for (int i=0; i<v.size(); ++i) {            // works, but still too ugly
       *v[i] = i;
       cout << *v[i];
}

Here, v[i] is interpreted as a call v.operator[](i), and that call returns a pointer to v’s element number i. The problem is that we have to write * to dereference that pointer to get to the element. That’s almost as bad as having to write set() and get(). Returning a reference from the subscript operator solves this problem:

class vector {
      // . . .
      double& operator[ ](int n) { return elem[n]; }        // return reference
};

Now we can write

vector v(10);
for (int i=0; i<v.size(); ++i) {         // works!
      v[i] = i;                         // v[i] returns a reference element i
      cout << v[i];
}

We have achieved the conventional notation: v[i] is interpreted as a call ­v.operator[](i), and that returns a reference to v’s element number i.

18.5.1 Overloading on const

The operator[]() defined so far has a problem: it cannot be invoked for a const vector. For example:

void f(const vector& cv)
{
       double d = cv[1];           // error, but should be fine
       cv[1] = 2.0;                // error (as it should be)
}
round-b.jpg

The reason is that our vector::operator[]() could potentially change a vector. It doesn’t, but the compiler doesn’t know that because we “forgot” to tell it. The solution is to provide a version that is a const member function (see §9.7.4). That’s easily done:

class vector {
     // . . .
     double& operator[](int n);           // for non-const vectors
     double operator[](int n) const;      // for const vectors
};

We obviously couldn’t return a double& from the const version, so we returned a double value. We could equally well have returned a const double&, but since a double is a small object there would be no point in returning a reference (§8.5.6), so we decided to pass it back by value. We can now write

void ff(const vector& cv, vector& v)
{
      double d = cv[1];                             // fine (uses the const [])
      cv[1] = 2.0;                                  // error (uses the const [])
      double d = v[1];                              // fine (uses the non-const [])
      v[1] = 2.0;                                   // fine (uses the non-const [])
}

Since vectors are often passed by const reference, this const version of operator[]() is an essential addition.

  • + Share This
  • 🔖 Save To Your Account