9.8 — Overloading the subscript operator

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

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

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

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

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

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.

You could also just return the entire list and use operator[] to access the element:

While this also works, it’s syntactically odd:

Overloading operator[]

However, a better solution in this case is to overload the subscript operator ([]) to allow access to the elements of m_list. The subscript operator is one of the operators that must be overloaded as a member function. An overloaded operator[] function will always take one parameter: the subscript that the user places between the hard braces. In our IntList case, we expect the user to pass in an integer index, and we’ll return an integer value back as a result.

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

This is both easy syntactically and from a comprehension standpoint. When list[2] evaluates, the compiler first checks to see if there’s an overloaded operator[] function. If so, it passes the value inside the hard braces (in this case, 2) as an argument to the function.

Note that although you can provide a default value for the function parameter, actually using operator[] without a subscript inside is not considered a valid syntax, so there’s no point.

Why operator[] returns a reference

Let’s take a closer look at how list[2] = 3 evaluates. Because the subscript operator has a higher precedence than the assignment operator, list[2] evaluates first. list[2] calls operator[], which we’ve defined to return a reference to list.m_list[2]. Because operator[] is returning a reference, it returns the actual list.m_list[2] array element. Our partially evaluated expression becomes list.m_list[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 (e.g. list[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. list[2] would call operator[], which would return the value of list.m_list[2]. For example, if m_list[2] had the value of 6, operator[] would return the value 6. list[2] = 3 would partially evaluate to 6 = 3, which makes no sense! If you try to do this, the C++ compiler will complain:

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

Dealing with const objects

In the above IntList example, operator[] is non-const, and we can use it as an l-value to change the state of non-const objects. However, what if our IntList object was const? In this case, we wouldn’t be able to call the non-const version of operator[] because that would allow us to potentially change the state of a const object.

The good news is that we can define a non-const and a const version of operator[] separately. The non-const version will be used with non-const objects, and the const version with const-objects.

If we comment out the line clist[2] = 3, the above program compiles and executes as expected.

Error checking

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:

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

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.

Pointers to objects and overloaded operator[] don’t mix

If you try to call operator[] on a pointer to an object, C++ will assume you’re trying to index an array of objects of that type.

Consider the following example:

Because we can’t assign an integer to an IntList, this won’t compile. However, if assigning an integer was valid, this would compile and run, with undefined results.

Rule: Make sure you’re not trying to call an overloaded operator[] on a pointer to an object.

The proper syntax would be to dereference the pointer first (making sure to use parenthesis since operator[] has higher precedence than operator*), then call operator[]:

This is ugly and error prone. Better yet, don’t set pointers to your objects if you don’t have to.

The function parameter does not need to be an integer

As mentioned above, C++ passes what the user types between the hard braces as an argument to the overloaded function. In most cases, this will be an integer value. However, this is not required -- and in fact, you can define that your overloaded operator[] take a value of any type you desire. You could define your overloaded operator[] to take a double, a std::string, or whatever else you like.

As a ridiculous example, just so you can see that it works:

As you would expect, this prints:

Hello, world!

Overloading operator[] to take a std::string parameter can be useful when writing certain kinds of classes, such as those that use words as indices.


The subscript operator is typically overloaded to provide direct access to individual elements from an array (or other similar structure) contained within a class. Because strings are often implemented as arrays of characters, operator[] is often implemented in string classes to allow the user to access a single character of the string.

Quiz time

1) A map is a class that stores elements as a key-value pair. The key must be unique, and is used to access the associated pair. In this quiz, we’re going to write an application that lets us assign grades to students by name, using a simple map class. The student’s name will be the key, and the grade (as a char) will be the value.

1a) First, write a struct named StudentGrade that contains the student’s name (as a std::string) and grade (as a char).

Show Solution

1b) Add a class named GradeMap that contains a std::vector of StudentGrade named m_map. Add a default constructor that does nothing.

Show Solution

1c) Write an overloaded operator[] for this class. This function should take a std::string parameter, and return a reference to a char. In the body of the function, first iterate through the vector to see if the student’s name already exists (you can use a for-each loop for this). If the student exists, return a reference to the grade and you’re done. Otherwise, use the std::vector::push_back() function to add a StudentGrade for this new student. When you do this, std::vector will add a copy of your StudentGrade to itself (resizing if needed). Finally, we need to return a reference to the grade for the student we just added to the std::vector. We can access the student we just added using the std::vector::back() function.

The following program should run:

Show Solution

2) Extra credit #1: The GradeMap class and sample program we wrote is inefficient for many reasons. Describe one way that the GradeMap class could be improved.

Show Solution

3) Extra credit #2: Why doesn’t this program work as expected?

Show Solution

9.9 -- Overloading the parenthesis operator
9.7 -- Overloading the increment and decrement operators

92 comments to 9.8 — Overloading the subscript operator

  • Will

    Hi Alex,
    I hava a question about the user defined dictionary.
    In Python, we have __setitem__ and __getitem__ to differentiate between setter and getter. They can have different behaviours.
    For example:
    When user try to read from the dictionary, if the key is not in the dictionary, we raise an exception.
    When user try to assign an value to the dictionary, if the key is not in the dictionary, we create a new entry.

    But how can we do that in C++?

    Using the code above, if we try to assign a value to a new key, the code will do exactly what we want.
    However, if we try to read an invalid key, the map object will simply add a new entry and return a default value, which is kind of wired. How can we solve that?
    Thanks in advance.

    • Alex

      The problem here is that operator[] doesn’t know what we intend to do with the result it returns, so it can’t really make any assumptions about whether it should or should not create a new name.

      Probably the best way to resolve this would be to use explicit getGrade(name) and setGrade(name, grade) functions instead of overloading operator[]. The get function would return the grade if it found the name, otherwise it would return an appropriate error response (error value, exception, etc…). The set function would create a new name if one didn’t exist, and set the value.

  • Anonymous

    Because operator[] is returning a reference, it returns the actual list.m_list[2] array element. Our partially evaluated expression becomes list.m_list[2] = 3, which is a straightforward integer assignment.

    Isn’t this illegal in main to initialize a private variable directly?

    • Alex

      No. You can’t access m_list directly (because of the private access control) but you can access it indirectly through a reference or pointer.

      Access controls are enforced at compile time, whereas references and pointers evaluate at runtime. This means you can use them to “subvert” access controls (intentionally or otherwise).

  • zerocool

    For const version of [] operator overloading what’s the point in returning a reference as in your example.?

  • Kattencrack Kledge

    In this line of code:

    Since the function is constant, why should we still get a reference of the return value and not just the value, since int& just lets the return type also be a l_value, which a const variable will never be able to be?

    • Alex

      We don’t get a reference of the return value -- we get a const reference of the return value. Const variables can be lvalues, they just can’t be modified.


    Hi Alex,

    Thanks for the great tutorial… In the quiz, can you please explain why are we doing this?

    for (auto &ref : m_map)

    What will this part of the code do?

    Thanks and Regards

    • Alex

      This iterates through all elements of m_map and assigns a reference named ref to each of them in turn. This is covered in lesson 6.12a -- For each loops.

      • Turya

        Hi Alex,

        Is it possible to implement this for-each loop as simple for loop? How can we deal with termination condition and reference part?

        • Alex

          Yes, since m_map is a vector, we could write:

  • Anddo

    In the quiz, number 2, extra credit 1

    "We could optimize this by keeping our m_map sorted and using a binary search, so we minimize the number of elements we have to look through to find the ones we’re interested in."

    May I ask what do you mean by binary search ?

  • Connor

    Hi Alex,
    I’m struggling to understand the example in "Pointers to objects and overloaded operator[] don’t mix"

    wouldn’t this be accessing the 2nd element in an array of IntLists and trying to set it to 3?

  • Vlad

    I see now, thank you for the explanation.

  • Vlad

    Hello Alex

    I can’t get over this part of quiz 1c:

    Why do we have to add a temporary value instead of just m_map.push_back(name).name? After all, when assigning values, grade["Joe"]=’A’, the grade ‘A’ should fill up the space, no? I tried compiling this, but it doesn’t work. For some reason, I can’t see the mechanics behind, or the reason why we have to burden the function twith creating a new, temporary variable everytime a name/grade is entered. Could you shed some light, please?

    • Alex

      m_map is a std::vector of StudentGrade, so anything we push on the vector must be a StudentGrade. Pushing on name doesn’t work, because name is a std::string, not a StudentGrade. The compiler isn’t smart enough to infer that you don’t care about the grade part in this case.

      Another solution would be to create StudentGrade as a class, and provide a converting constructor to convert a std::string to a StudentGrade. Then the explicit temporary wouldn’t be necessary, because the compiler could convert the std::string into a temporary StudentGrade as needed -- but the key point here is that a StudentGrade is needed somehow because that’s what the std:vector holds.

  • Nyap

    why does this cause a run-time error


    *** Error in `/media/nyap/acb384f6-b8a7-482b-b700-4070bd5c283b/learncpp/Test/bin/Release/Test’: double free or corruption (fasttop): 0x0000000000caac40 ***

    I’ve stepped through it and it seems to occur in the delete statements in the destructors

    • Alex

      This doesn’t even compile for me on Visual Studio 2015.

      But I think I know what’s happening here. Your operators are all passed by value, not reference, meaning that they make a copy of the arguments. Beyond the performance hit from doing this, this is actually the cause of your error.

      So first oneeg and twoeg are created, and dynamic memory is allocated for those. These are then passed into operator+, where parameters one and two become copies of oneeg and twoeg accordingly. When operator+ is finished, the parameters are destroyed, which invokes the constructor for one and two, which deletes the memory being pointed to. This leaves oneeg and twoeg as dangling pointers.

      Change your parameters to const references and you should be able to avoid this.

  • Darren

    Here’s a really important issue!! In this context isn’t the plural of index indices, not indexes? You’ve got to love the English language for inconsistencies both in spelling and grammar -it’s a dyslexic’s nightmare. Including where and when to use those damnable apostrophes.

  • Dave

    Hey Alex,

    In the comment below it should be gradeJoe is left dangling?

    3) Extra credit #2: Why doesn’t this program work as expected?

    int main()
        GradeMap grades;
        char& gradeJoe = grades["Joe"];
        gradeJoe = ‘A’;
        char& gradeFrank = grades["Frank"];
        gradeFrank = ‘B’;
        std::cout << "Joe has a grade of " << gradeJoe << ‘\n’;
        std::cout << "Frank has a grade of " << gradeFrank << ‘\n’;

        return 0;

    When Frank is added, the std::vector must grow to hold it. This requires dynamically allocating a new block of memory, copying the elements in the array to that new block, and deleting the old block. When this happens, any references to existing elements in the std::vector are invalidated! In other words, after we push_back(“Frank”), gradeFrank is left as a dangling reference to deleted memory.

    • Alex

      Yes, gradeJoe is dangling, not gradeFrank. I’ve updated the answer.

      • Nazime

        Hey, i still didn’t get this point… why whene we add a new block the old one is deleted? the vector do not hold both value? Joe and Frank?

        By the way: thanx a lot for this tutorial, and sorry for my english am from Algeria

        • Alex

          Each time you overflow the capacity of a vector, it must resize itself to accommodate the new elements being added. As I noted above, this requires dynamically allocating memory, copying the old elements into the new memory, and then deleting the old memory. Because the elements are copied, the final vector will contain both Joe and Frank.

          Reference gradeJoe is fine when the vector contains just Joe. However, when we add Frank, the vector must allocate new memory, copy the elements, and delete the old memory. gradeJoe is still pointing to the memory that gets deleted when the vector gets resized, so it becomes a dangling reference. Later, when we try to use it, we’re accessing deleted memory, which will cause bad things to happen.

          • Nurlan

            Hello Alex,
            I hope you are doing well.
            You wrote on 6.16 — An introduction to std::vector as below.

            Self-cleanup prevents memory leaks

            When a vector variable goes out of scope, it automatically deallocates the memory it controls (if necessary). This is not only handy (as you don’t have to do it yourself), it also helps prevent memory leaks. Consider the following snippet:

            void doSomething(bool earlyExit)
                int *array = new int[5] { 9, 7, 5, 3, 1 };

                if (earlyExit)

               // do stuff here

                delete[] array; // never called

            If earlyExit is set to true, array will never be deallocated, and the memory will be leaked.

            However,if array is a vector, this won’t happen, because the memory will be deallocated as soon as array goes out of scope (regardless of whether the function exits early or not). This makes std::vector much safer to use than doing your own memory allocation.

            So my question. As you mentioned above the vector will be deallocated as soon as array goes out of scope.
            So how come gradeFrank is not dangling pointer. It is pointing to the address which is deallocated . See below your example:
            int main()
                GradeMap grades;

                char& gradeJoe = grades["Joe"];
                gradeJoe = ‘A’; // does a push_back

                char& gradeFrank = grades["Frank"];
                gradeFrank = ‘B’; // does a push_back

                std::cout << "Joe has a grade of " << gradeJoe << ‘n’;
                std::cout << "Frank has a grade of " << gradeFrank << ‘n’;

                return 0;
            In my opionion, gradeJoe and gradeFrank are both dangling pointer if we refer to 6.16 — An introduction to std::vector.
            I think,return by reference is not a good idea on vector. Since, Vector is deallocated once it goes from sub-function, so at the end the variable which receives  it becames to reference to destroyed address.

            Please, clarify it.
            Thanks in advance.

            • Alex

              > As you mentioned above the vector will be deallocated as soon as array goes out of scope.

              No. The vector will be deallocated when the vector goes out of scope.

              In this case, the vector is grades. grades doesn’t go out of scope until the end of the function -- thus, it’s fine to use within the function. gradeJoe and gradeFrank are never dangling (because local variables are destroyed in the reverse order of creation, gradeJoe and gradeFrank are destroyed before grades).

              If main does an early return, grades will go out of scope and deallocate itself, unlike memory that has been dynamically manually (which will not deallocate itself when it goes out of scope).


    Hey, Alex.
    I finally have found a problem in your code.

    🙂 🙂 🙂

  • siva

    This link is not accessible from index page

  • Kiran C K

    #include <iostream>
    #include <cassert>
    class Digit
        int digit1[3]{0};

        int& operator[](const int index);

        ostream& operator<<(ostream& out);

    int& Digit::operator[](const int index)
        return digit1[index];

    std::ostream& Digit::operator<<(std::ostream& out)
        int loop;
        out << "{";
        for (loop = 0; loop < 3; loop++)
            out << digit1[loop] << " ";
        out << "}";

        return out;

    int main()

        Digit n;
        n[0] = 4;
        n[1] = 3;
        n[2] = 4;

        n << std::cout;

        return 0;

    In the above example, the "ostream does not have a type" error occurs only when the member variable is an array. Whereas when I add namespace it works. Is there any reason that it works only if we add std namespace for member arrays?

  • If my class has 2 arrays of some type (as member), how can I overload the subscript operator to access different elements of both arrays:

    And, isn’t it a good idea (for C++ 11 users, and for int arrays) to initialize the arrays with all elements set to 0 like this:

    • Alex

      First, there’s a couple ways you could do this:

      * Overload the [] operator so that 0 = m_array1, and 1 = m_array2. Then you can do this: myObject[0][5]; // get the 5th element of m_array1
      * Overload the () operator and use two parameters, the first to select the array and the second to select the element

      Second, yes, it’s a good idea to initialize the arrays to zero.

      • Yup, I tried the second one and that worked. I was just too quick to ask this question 🙂

      • Kiran C K

        Can you please include an example for the first method? Thanks in advance

        • Alex

          Sorry, I’m not following what you’re asking for an example for. Can you be more specific?

          • Kiran C K

            Can you please include an example for overloading [] with 2 or more member arrays in a class?
            You have mentioned the method: Overload the [] operator so that 0 = m_array1, and 1 = m_array2. Then you can do this: myObject[0][5]; // get the 5th element of m_array1. I am getting syntax errors while doing it. I think I am missing something which I am not sure, in my code.

            • Alex

              I’ll add an example when I get around to updating this article. In the meantime, make sure your overloaded operator[] is returning an array (either a pointer to a built-in array, or a reference to an array class such as std::array or std::vector).

  • Pablo

    For the example with an array, I noticed you can use return by reference with a function as well, so you can do something like

    which does not have the clarity problem mentioned above. My question is: what is the need for overloading the [] operator then? Just having a more familiar way to work with your class?


  • anvekar

    In this are we not violating the encapsulation rule of private member, because as we are passing the reference to the original private member "m_anList[10]" i.e. in the main() we get "cMyList.m_anList[2] = 3". So we are just setting the value of the private member in the main() directly without using mutator. So this violates the rule.

    • Alex

      No, there’s no violation of encapsulation in providing direct access to a single integer via an overloaded operator[].

      If we changed the underlying implementation (from an array to a tree, for example), we could still pass back an integer reference to wherever element #2 mapped to in that new structure, and the code using this class would still function without modification.

      Encapsulation would be violated if we provided a method that returned m_anList, because that would expose the underlying data structure.

  • Rob G

    I’m surprised no one has pointed out that it is generally best to define two such operators:

    This ensures that use on the rhs of an expression does not confuse the compiler into thinking that the array may change.

  • Janez

    Would it be possible to exploit return by pointer to a private member of a class? Since we can’t access private members directly, we don’t know what is their address. But by returning a pointer to them, this would no longer hold true. Any problems that might come with that?

    • Alex

      Yes, definitely. The internal member variable could be reallocated, leaving the address you’re holding as invalid, and you’d have no way to know. For this reason, it’s usually not a good idea to hold on to pointers or references to an internal class’s members if there’s any chance this could happen.

  • blank

    The code below was to overload the [] operator to accept a list of 2 elements to access an element of
    a 3×3 matrix. It seems to work when I uses a variable assignment for the argument ie. “Indexer” , but will not work
    when I try to enter the list in the form of cAmatrix[{1,1}]. Is there a way I have [] operator take either inputs and output the same result?



    • Alex

      Not that I know of -- I don’t think C++ has a way to declare an anonymous array.

      Instead of overloading operator[] for this purpose, you really should be using an overloaded operator(). It’s much more suited to indexing multidimensional arrays.

  • kekie

    In the example, when you;

    IntList cMyList;
    cMyList[2] = 3;

    How does C++ know that the 2 is assigned to nIndex?
    Is just built-in that when overloading `[]` the argument comes from between `[` and `]` ?

  • venkatvb

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

    • Alex

      The languages designers must have had a good reason for not allowing users to overload operator[] as a friend, but I don’t know what that reason is.

  • 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?

  • 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?


    • 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:

      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.

  • Mojito

    Hi, and thanks for this post.

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

    anybody? 🙂

    • Alex

      You can do this if your overloaded operator[] returns a type that can use operator[] (either a class with an overloaded operator[] or a pointer type)

      If you want to return an element from a two-dimensional array inside your class, you’re better off overloading operator() (covered next lesson).

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

  • davidv

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

  • jarves

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


    class Whatever
    int * m_value;
    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.

  • 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
    Node* m_list;
    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)

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter