Navigation



9.8 — Overloading the subscript operator

When working with arrays, we typically use the subscript operator ([]) to index specific elements of an array:

anArray[0] = 7; // put the value 7 in the first element of the array

However, consider the following IntList class, which has a member variable that is an array:

class IntList
{
private:
    int m_anList[10];
};

int main()
{
    IntList cMyList;
    return 0;
}

Because the m_anList member variable is private, we can not access it directly from cMyList. This means we have no way to directly get or set values in the m_anList array. So how do we get or put elements into our list?

Without operator overloading, the typical method would be to create access functions:

class IntList
{
private:
    int m_anList[10];

public:
    void SetItem(int nIndex, int nData) { m_anList[nIndex] = nData; }
    int GetItem(int nIndex) { return m_anList[nIndex]; }
};

While this works, it’s not particularly user friendly. Consider the following example:

int main()
{
    IntList cMyList;
    cMyList.SetItem(2, 3);

    return 0;
}

Are we setting element 2 to the value 3, or element 3 to the value 2? Without seeing the definition of SetItem(), it’s simply not clear.

A better solution in this case is to overload the subscript operator ([]) to allow access to the elements of m_anList. The subscript operator is one of the operators that must be overloaded as a member function. In this case, our overloaded subscript will take one parameter, an integer value that is the index of the element to access, and it will return an integer.

class IntList
{
private:
    int m_anList[10];

public:
    int& operator[] (const int nIndex);
};

int& IntList::operator[] (const int nIndex)
{
    return m_anList[nIndex];
}

Now, whenever we use the subscript operator ([]) on an object of our class, the compiler will return the corresponding element from the m_anList member variable! This allows us to both get and set values of m_anList directly:

    IntList cMyList;
    cMyList[2] = 3; // set a value
    cout << cMyList[2]; // get a value

    return 0;

In this case, it’s much more obvious that cMyList[2] = 3 is setting element 2 to the value of 3!

Why operator[] returns a reference

Let’s take a closer look at how cMyList[2] = 3 evaluates. Because the subscript operator has a higher precedence than the assignment operator, cMyList[2] evaluates first. cMyList[2] calls operator[], which we’ve defined to return a reference to cMyList.m_anList[2]. Because operator[] is returning a reference, it returns the actual cMyList.m_anList[2] array element. Our partially evaluated expression becomes cMyList.m_anList[2] = 3, which is a straightforward integer assignment.

In the lesson a first look at variables, you learned that any value on the left hand side of an assignment statement must be an l-value (which is a variable that has an actual memory address). Because the result of operator[] can be used on the left hand side of an assignment (eg. cMyList[2] = 3), the return value of operator[] must be an l-value. As it turns out, references are always l-values, because you can only take a reference of variables that have memory addresses. So by returning a reference, the compiler is satisfied that we are returning an l-value.

Consider what would happen if operator[] returned an integer by value instead of by reference. cMyList[2] would call operator[], which would return the value of cMyList.m_anList[2]. For example, if m_anList[2] had the value of 6, operator[] would return the value 6. cMyList[2] = 3 would partially evaluate to 6 = 3, which makes no sense! If you try to do this, the C++ compiler will complain:

C:\VCProjects\Test.cpp(386) : error C2106: '=' : left operand must be l-value

Rule: Values returned by reference or pointer can be l-values or r-values. Values returned by value can only be r-values.

Conclusion

The subscript operator is typically overloaded to provide access to 1-dimensional array elements contained within a class. Because strings are typically implemented as arrays of characters, operator[] is often implemented in string classes to allow the user to access a single character of the string.

One other advantage of overloading the subscript operator is that we can make it safer than accessing arrays directly. Normally, when accessing arrays, the subscript operator does not check whether the index is valid. For example, the compiler will not complain about the following code:

int anArray[5];
anArray[7] = 3; // index 7 is out of bounds!

However, if we know the size of our array, we can make our overloaded subscript operator check to ensure the index is within bounds:

#include <cassert> // for assert()

class IntList
{
private:
    int m_anList[10];

public:
    int& operator[] (const int nIndex);
};

int& IntList::operator[] (const int nIndex)
{
    assert(nIndex >= 0 && nIndex < 10);

    return m_anList[nIndex];
}

In the above example, we have used the assert() function (included in the cassert header) to make sure our index is valid. If the expression inside the assert evaluates to false (which means the user passed in an invalid index), the program will terminate with an error message, which is much better than the alternative (corrupting memory). This is probably the most common method of doing error checking of this sort.

9.9 — Overloading the parenthesis operator
Index
9.7 — Overloading the increment and decrement operators

23 comments to 9.8 — Overloading the subscript operator

  • sergk

    Backwards compatibility with C sometimes could be regretting.
    Subscript operator is such case. In C is meant to be used with C “arrays”, and in turn those are just `pointer+offset*sizeof` syntactic sugar.
    Long story short, handy `operator[]` is useless when you deal with pointer to an object. Of course there is possibility to use dereference operator, or just call `operator[]` explicitly, but this is no way better than using getters/setters.

    Such semantical inconsistency could lead to some evil pitfalls. Consider:

    class Node
    {
    private:
    Node* m_list;
    public:
    ...
    Node* operator[] (const int index) { return m_list[index]; }
    };

    and when usage:

    Node cMyNode;
    Node* pMyNode = cMyNode[2]; // ok - operator[]
    Node* pNoNode = pMyNode[2]; // gotcha!

  • Good point, Sergk. If you use a subscript on a pointer, the compiler will assume that you are trying to access a member of an array that the pointer is pointing to.

    If you want to use an overloaded subscript operator on the variable that a pointer points to, you have to dereference the pointer first. However, because the array index operator has a higher precedence than the subscript operator, you have to use parenthesis to make sure it resolves correctly:

    Node *pNode = new Node();
    cout << (*pNode)[2];

    (dereference first, then use overloaded subscript operator to get second element)

  • [...] 9.8 — Overloading the subscript operator Category Indexes [...]

  • jarves

    what about overloading the subscript operator to… say sort and int of arrays:

    example:

    class Whatever
    {
    private:
    int * m_value;
    public:
    int &operator[](std::size_t index);
    };

    • I am not sure what you are saying, Jarves. You want to overload the subscript operator to do what?

      • jarves

        oh sorries :S basically what i’m trying to do here is take a dynamic array of ints (int * m_value) and use the overloaded subscript operator to sort the array from smallest integer to highest integer… and it’s kickin my ass.

        • I would highly recommend using a function named Sort instead of overloading the subscript operator to do array sorting. It’s generally not a good idea to overload operators to do things they aren’t normally used for, simply because it’s confusing and non-intuitive.

  • [...] Prev/Next Posts « 9.6 — Overloading operators using member functions | Home | 9.8 — Overloading the subscript operator » Monday, October 15th, 2007 at 8:19 [...]

  • davidv

    The “Why operator[] returns a reference” part was brilliant. You made a pretty difficult idea (at least to me) to seem obvious.

  • subs script operator overloading in c++ with header file include simple and sweet program dijiye

  • [...] also be able to get cout to work too. I directed you to Learn C++, which has a specific section on overloading the subscript operator that should shed some light on it. __________________ On [...]

  • Mojito

    Hi, and thanks for this post.

    I have another question. I want to do sub-indexing too, something like: MyNewType[a][b].

    anybody? :)

  • rob

    Hi Alex,

    In a case where we have 2 lists, can we still overload the [] operator?
    How does the overload function know which list to use when returning the reference?

    —rob

    
    class IntList
    {
    private:
        int m_anList_A[10];
        int m_anList_B[10];
    
    public:
        int& operator[] (const int nIndex);
    };
    
    int& IntList::operator[] (const int nIndex)
    {
        return ???
    }
    
    • Jeffrey Bosboom

      Your operator[] function will do whatever you write in its body — it’s just a regular member function with a funny name. So you can overload operator[] to return an element from the first list or the second list. You can also do something more complicated like:

      int& IntList::operator[](const int index)
      {
          if (index < 10)
              return m_anList_A[index];
          else if (index < 20)
              return m_anList_B[index-10];
          else
              throw std::out_of_range("Index out of range");
      }
      

      This will return an element from the first list if the index is 0-9, return an element from the second list if the index is 10-19, or throw an exception otherwise.

  • [...] 9.8 Overloading the subscript operator [...]

  • Nathan_OR

    Great explanation and yes thank you for the “why operator[] returns a reference” portion, it makes it very clear why it must be so when using array[n] as an l-value. However I am still not clear why operator[] returns a reference when used as an r-value! It seems that would be equivalent to:


    int x = 1;
    int y = 2;
    int array[3] = { 0, 0, 0 };

    array[0] = x; // okay, per your explanation, we don't mean to write 0 = 1, rather, we mean [element at index 0] = 1
    // but then what about:
    x = array[0]; // why does x end up holding the r-value of "0", instead of the address of the integer array element at index 0?

    Perhaps I need more coffee, but the reasoning here is escaping me… is it simply a matter of compiler convention that a reference used as an r-value will always be evaluated for its r-value rather than the l-value?

  • [...] occupies one bit. There are other considerations with this specialisation too. For example, the subscript operators do not return a reference to a value within the vector, they return a copy of a bool that [...]

  • venkatvb

    why can’t we overload array subscript operator as friend. why it gives error when I overload [],->,= operator as friend?

  • [...] the lesson on overloading the subscript operator, you learned that we could overload operator[] to provide direct access to a private [...]

You must be logged in to post a comment.