13.9 — 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 lesson 1.3 -- Introduction to objects and 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.


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

Question #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.

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

Show Solution

b) Add a class named GradeMap that contains a std::vector of StudentGrade named m_map.

Show Solution

c) 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 see if the student’s name already exists (You can use std::find_if from <algorithm>). 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, invalidating all previously returned references). 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


Since maps are common, the standard library offers std::map, which is not currently covered on learncpp. Using std::map, we can simplify our code to

Prefer using std::map over writing your own implementation.

Question #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

Question #3

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

Show Solution

13.10 -- Overloading the parenthesis operator
13.8 -- Overloading the increment and decrement operators

260 comments to 13.9 — Overloading the subscript operator

  • Kacper Bazan

    Should we be using std::size_t instead of int for the parameters that deal with indexing our array?

  • So every time we push_back() an element to the vector it needs to grow to hold that element and also deallocate the previous block of memory, this seems so time taking and also we may face the problem of dangling reference.

    >So tell me if the following is wrong-->
    But if we know how much elements we need , we must allocate that much of memory before any push_back() operations and then the vector won't need to grow every time we add elements to it and hence will not reallocate every time also we will not have problems of dangling reference{given that we are not increasing our number of elements more than the size of the vector}

    >and hence I try something like this
    instead of

    I write

    then there are lot of errors in the solution of que(1.c), most of them are

    >I don't know what is happening but I think-- Although we are giving a size of 10 to the vector , the push_back operation will always increase one size of the vector{as all the 10 places in the vector might have already filled with the default value} and hence it will reallocate this time too, and we will face the dangling reference problem again, but I thought it will run at least but I get the errors which I don't know why. Can you tell me what is happening here?

    >I think the problem can be solved in two ways here
    1.first if we make a counter variable and then override the default elements values every time we get a different name or override the grade if we get the same name{I don't know if this is possible I haven't tried}

    2.Instead of making a vector of 10 size , if we reserve{through m_map.reserve(10)} that much of size then we will not have a default value for each of them , then push_back will also not reallocate every time{Again I haven't tried}

    Maybe if you can show me how we can write the above program if we know the size of the vector before the operations on it or why I am getting those errors I will be very grateful to you.

    • Alex


      Also when a vector resizes, it typically resizes in a way that leaves room for extra elements to be added before another resize needs to take place.

  • Mark

    Just a question out of curiosity, how does c++ know that our argument for using '[]' is between the brackets?
    For example when overloading the '<<' operator the argument comes after the '<<', so why doesn't c++ think we will use the [] operator in the same way? I.e. if we would want index 2 of some array, why doesn't c++ expect us to do so by 'array[]2'?

    • Alex

      Parenthesis (), hard braces [], and curly braces {} are all defined to have the values in-between. So when the compiler is parsing these expressions, the parsing rules are set up in such a way to expect this.

  • bob loblaw

    Hi, I tried doing question 1 without using findif like so:

    I used the debugger and that std::cout prints the correct thing ('A', then 'B') and when I check the value of student.grade in the debugger it is what I expect (char 'A' and 'B'), but then for the return when that is printed out it prints a weird non-ascii character.  When I use the solution provided the return works even though (as far as I can tell) the type is the same.  I'm not sure why this is happening, can I get a pointer, please?

    • nascardriver

      Your loop variable `student` is a copy of each map element. Your function returns a reference, but `student.grade` is a local variable, which dangles when you return it.

  • Hi, i feel like this chapter should also covert what happens when you overload the operator[] with array's of object. Tho if i understood everything up to this point. it's like 2D array for the first [] for the array of object and the second[] operator for the inner array's.

  • Aphamino


    In the given solution for question #3 it says that "This requires dynamically allocating a new block of memory...". I thought that vectors handle their own memory management (isn't is a vector into which we are storing the (name, grade) pairs?

  • Rostyslav

    Very nice that you introduce routines from <algorithm> in the quiz tasks.

  • yeokaiwei

    1. Feedback

    Microsoft Visual Studio 2019 will return 2 warnings.
    C6201 'Index 7 is out of range' and C6386 'Buffer overrun'

  • CC

    In your example of overloading `operator[]` to take string indices (and, indeed, in pretty much every other example prior where you overload an operator using member functions), you declare the overloaded operator within the class but implement it outside the class.

    Is there any particular reason why you do this? I tried to see what would happen if I define the overloaded operator within the class (specifically for your string indexing example) and it worked, but I'm not sure if there's a reason not to do it that way in the first place.

    • nascardriver

      There's no point in doing so in this example. In practice, classes get separated into header and source file, so there'll rarely be function definitions in the class definition.

  • Eric

    In reference to quiz 1.c can you clarify what's happening here?:

    can we push_back a just one element of struct StudentGrade (the string element) and create a whole new StudentGrade array element (with its corresponding grade structure element) automatically? I thought we'd have to push_back the entire structure element like "push_back(student)".
    Thanks for clarifying.

    • nascardriver

      We can construct a student by using only a part of its members

      The same is happening when we use list initialization in any other place

  • Michael

    Hi there!

    I am trying to overload the subscript operator in a templated Array class that I created but I am getting a linker error. I am able to get it to work if I define the function inside the class but it doesn't work when I move it to the Array.cpp file.

    Below is all the code I have so far:



    CustomArray.cpp (main project file)

    Error received: LNK2019    unresolved external symbol "public: int & __cdecl Array<int>::operator[](int)" ([email protected]@@[email protected]) referenced in function main

    • nascardriver

      You can't define template functions in source files with their declaration being in the header unless you explicitly instantiate them, which defeats the purpose of using a template. Define the functions in the header.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]