- 18.1 Introduction
- 18.2 Initialization
- 18.3 Copying
- 18.4 Essential operations
- 18.5 Access to vector elements
- 18.6 Arrays
- 18.7 Examples: palindrome
- Drill
- Review
- Terms
- Exercises
- Postscript
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) }
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.