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

251 comments to 13.9 — Overloading the subscript operator

  • SaMIRa

    Hello nascardriver,
    Would you please check my solution for quiz #3 part extract credit #2?

    Thank you so much

    (Adam, C)
    (Joe, A)
    (Michiel, A)
    (Nadia, B)
    (Nascardriver, A)
    (Nick, E)
    (Samira, B)
    (Sarah, F)
    (Sebastian, D)

    • nascardriver


      - "(" and ")" are strings. Strings are slow. Use characters '(' and ')'.
      - You're still calling the `StudentGrade` constructor manually when you use `emplace_back`. `emplace_back` calls the constructor for you which allows it to perform a faster insertion than `push_back`.
      - `m_map` is almost always sorted (It's unsorted when you call `print`). If you want a container that's always sorted, you can use `std::set`. It does all the sorting and binary search for you.
      - Don't call `std::binary_search` AND `std::lower_bound`. `std::binary_search` only tells you if the element exists, but you can use the result of `std::lower_bound` to figure that out. By using both, you're wasting one entire search. Use `std::lower_bound`, then compare the result to `m_map.end()` to see if anything was found. Then compare the result to `index` to see if the exact value was found (Because `std::lower_bound` doesn't search for an exact match).

      • SaMIRa

        >>You're still calling the `StudentGrade` constructor manually when you use `emplace_back`. `emplace_back` calls the constructor for you which allows it to perform a faster insertion than `push_back`.

        I got the ERROR while trying to use it . That's why I called the constructor. It seems emplace_back needs a constructor.

        Severity    Code    Description    Project    File    Line    Suppression State
        Error    C2664    'StudentGrade::StudentGrade(StudentGrade &&)': cannot convert argument 1 from 'const std::string' to 'const StudentGrade &'    ConsoleApplication1    C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\include\xmemory    694    


        >>- `m_map` is almost always sorted (It's unsorted when you call `print`).

        I didn't get it. If I already had "Joe,A" in  the vector m_map, and wanted to add "Adam,A", 'Adam' will be added after 'Joe' because  push_back and emplace_back command both append a new element to the end of the container.
        P.S: I am not talking about this solution I posted. I am talking about the one Alex gave us and asked us to improved. The one with 'find_if'.


        >It does all the sorting and binary search for you.
        - Don't call `std::binary_search` AND `std::lower_bound`. `std::binary_search` only tells you if the element exists, but you can use the result of `std::lower_bound` to figure that out. By using both, you're wasting one entire search. Use `std::lower_bound`, then compare the result to `m_map.end()` to see if anything was found. Then compare the result to `index` to see if the exact value was found (Because `std::lower_bound` doesn't search for an exact match).

        I chose that solution as Alex mentioned in Quiz #3, Credit #2 and he probably would have had some perfect solution for this. I know mine sucked :))
        When you response to my post I tend not to see the solutions you offered me. I go and think a lot and a lot and then after come up with a solution then check yours with mine :) I got excited when I found I thought like you. WOW!!!

        Here is the solution: (the amazing thing about lower_bound or upper_bound is, the list can be automatically sorted ascending or descending)

        • nascardriver

          > emplace_back
          I remembered I had an issue with using `emplace_back` on an aggregate, so I checked to make sure it works before telling you to drop the explicit constructor call (`emplace_back` works with aggregates since C++20), and it worked. It appears that currently only gcc supports this new behavior, but msvc doesn't yet, sorry about that. Until msvc gets updated, your solution is the best you can do.

          > If I already had "Joe,A" in the vector [...]
          That's right. This is true for yours and the quiz's solution. The quiz doesn't sort the elements, so my comment doesn't apply to the quiz's solution. It's only about your solution, because you're sorting the elements (Which is good, assuming that the key is sortable).
          I'm trying to give suggestions how to further improve your code. You see, whenever you call your `operator[]`, you're sorting the map. You don't need to. You only need to sort the map when you insert an element. You can move the call to `std::sort` behind `emplace_back`. If you do that, `m_map` is always sorted, even when you call `print`. And after you've done that, you're simulating the behavior of `std::set`. `std::set` isn't covered on learncpp and I'm not expecting you to come up with it yourself. I'm showing you what's beyond the tutorials because the tutorials can't cover the entirety of C++.

          > I chose that solution [binary search] as Alex mentioned in Quiz #3 [...]
          Using binary search is good, but "binary search" doesn't refer to just `std::binary_search` (Confusing name, I know). `std::binary_search` performs a binary search, but it doesn't tell you which element it found. When you write a map, you don't directly care whether an element exists or not, you want to know where it is. `std::lower_bound` uses binary search (Or another efficient algorithm) to find the element, not just tell you if it exists.

          Your new solution looks great :)

          • SaMIRa

            Thank you for all the thorough explanations. That was really awesome to know! I am learning a lot. (When I first happened to know about learncpp site, I knew nothing about C++; but I think I know a tiny little about huge C++ and this means a lot to me . Thank you)

            I have another question (SORRY):

            When I was using std::binary_search, I found out when I wanted to compare 'index' (as subscript operator input) with StudentGrade's member variable 'name', I needed to pass an object "StudentGrade{index}" instead of 'index' itself , while in "std::find_if" or some other similar standard library functions which involve searching and matching an element,  passing 'index' would suffice. I didn't get why these are different! I am guessing maybe because this std::binray_search is offered in C++ 20, the community decided that whenever we wanted to compare a member variable of an object, it would be better to pass as an object. Isn't this method slower?

            • nascardriver

              Binary search can be implemented without the temporary object, but there is no standard algorithm that does this. The reason for there not being such an algorithm might be that `std::binary_search` (and friends) require the input to be ordered based on `operator<` or `comp`. If you could pass in a matching function (rather than an element), that function would have to perform the exact same comparison as `operator<` or `comp` does. If the function performs a different comparison, the ordering is no longer correct and binary search can't work. By requiring an element to search for, it is guaranteed that the ordering of the container based on the same comparison as the one that's used during the search.

  • Rushhahb

    According to the quiz question part 3, why I got bunch of garbage when trying to output the address of the following?

    Lucky has a grade of A

    • nascardriver

      `&gradeLucky` returns a `char*`. `std::cout` treats `char*` as zero-terminated strings. Because `&gradeLucky` isn't a zero-terminated string, you're invoking undefined behavior. Cast the pointer to a `void*` to print it as a pointer.

    • Rushhahb

      OMG! Super helpful! I was wondering if there is anything in C++ that you didn't know about!! How did you do that? ;)

  • SaMIRa

    Do you think the following solution is OK for quiz #1?

    >>When you do this, std::vector will add a copy of your StudentGrade to itself (resizing if needed, invalidating all previously returned references).
    Would you please explain what you meant by 'invalidating all previously returned references'?

    • nascardriver

      It's alright, it can be improved

      - You don't need to type out `std::vector<StudentGrade&;t;::iterator`. It only makes it more cumbersome to read or update your code. `auto` is fine here.
      - The good thing about `emplace_back` is that you don't have to construct the object yourself. `emplace_back` will call the constructor for you

      - `std::prev` only works with some types of iterators. If the container gives direct access to the last element, eg. `m_map.back()`, use that instead.

      These are just improvements, your code is good :)

      When a vector changes its size, it copies all elements to a new array and deletes the old array. If you had a reference into the old array, that reference now dangles, it has been invalidated.

  • Rose

    >>Because we can’t assign an integer to an IntList, this won’t compile.

    Do you mean because we can't assign an integer value to a pointer variable of type IntList, this won't compile?

    >>However, if assigning an integer was valid, this would compile and run, with undefined results.
    In that case the pointer variable 'list' would be initiated with a different value and disconnected from pointing to an object of InList and we would end up having 'dangling pointer'? Am I right?

    • nascardriver

      `list` is an `IntList*`, but `list[2]` is an `IntList`

      is an attempt to assign 3 to an `IntList`.

      causes undefined behavior, because this element doesn't exist.

  • Gamer_to_be

    I do get the following warning on line #22 soon I added 'assert' command:
    Severity    Code    Description    File    Project    Line    Suppression State
    Warning    C6385    Reading invalid data from 'this->m_list':  the readable size is '40' bytes, but 'index' bytes may be read.

  • Rushhahb

    Why did I got the following warnings?
    Severity    Code    Description    File    Project    Line    Suppression State
    Warning    C6385    Reading invalid data from 'this->m_list':  the readable size is '40' bytes, but 'i' bytes may be read.    C:\USERS\Rushhab\DOCUMENTS\VISUAL STUDIO 2019\PROJECTS\CPP\CPP.CPP    CPP    11    

    Severity    Code    Description    File    Project    Line    Suppression State
    Warning    C6386    Buffer overrun while writing to 'this->m_list':  the writable size is '40' bytes, but 'i' bytes might be written.    C:\USERS\Rushhab\DOCUMENTS\VISUAL STUDIO 2019\PROJECTS\CPP\CPP.CPP    CPP    16    

  • ammawn

    In Quiz Question #1, in the StudentGrade Struct, why do we have "non-static member initializer" with the

    and not


  • Nexteon

    Could you tell me what's wrong with accessing a member vector's struct variables?

    I think I remember in the past lessons that arrays or vectors with pass through a function default to pointers. I feel all over the place.

    • nascardriver

      I don't understand what you said, and without seeing the rest of your code, I can't tell what's going on.

      • Nexteon

        Sorry if I wasn't clear. In my class, I have a member variable that is a vector of struct StudentGrades. I didn't understand why I couldn't access struct variables using member selection or by using (::). I have a comment in my overloaded operator that gives a visual of what I am talking about.

        • m_map is a vector, it can't be indexed by std::string.

          line above will cause syntax errror.

        • nascardriver

          @dashjay is right. `m_map` needs to be index with an integer, not a string. `std::find_if` already found the element, so all you need to do is

  • koe

    Quiz question 1 was a fun synthesis of different concepts, thanks for all the work that went into these tutorials :)

  • Ahmed Alkanaq

    @nascardriver <- I tracked the grades map vector, and found that it is properly resized, and the gradeJoe reference has done its job assigning grade 'A' to "Joe" in the vector.

    If I am getting it right! The undefined results or behavior is the result of calling gradeJoe again, after another element has been added to the vector. Otherwise, both references, gradeJoe and gradeFrank, have done their jobs correctly and we should just not rely on calling them back. Is that an accurate statement?

    • nascardriver

      You don't "call" references, you "use" them. Up to, and including, line 8 in the code, everything is fine.
      Line 10 adds another element to the vector. It doesn't matter what this element is. All that matters is that the vector was resized, which caused all elements to be moved to a new memory location.
      Joe still has the correct grade. There's nothing wrong with the elements.
      The problem is `gradeJoe`, because it's a reference to old memory.

      This is what's happening behind the scenes

      There were 2 arrays. One with 1 element, and another one with 2 elements. `gradeJoe` references an element of the first array. That's fine, until we `delete` it. After that, `gradeJoe` dangles.

  • salah

    could you please illustrate why we can't overload the operator as a friend or normal function? it seems to have no problem, I tried doing so and got an error "operator [] must be a member function" why the language does not accept that ?

    • Alex

      Because the language specification requires it to be a member. As for why, I'm not sure. The language spec doesn't offer a justification for the requirement.

  • Dong

    Hi, when I practice section c of Question 1. I comment the find_if code and only keep the push_back and back function. But the result with operator[] no return.

    The result shows that "Joe has a grade of". I do not understand why? Can you explain it for me?Thanks.

    • nascardriver

      Creates a student with the given name, but an empty grade. If you use `operator[]` to access a non-existent student, you should assign a grade.

      • Dong

        Would you like to explain clearly? It can be shown as in the main code or something else.

        I do not understand your comment. Thanks.

        • nascardriver

          This issue is unrelated to the operator. Please see lesson S.4.7 about struct initialization. If an element is omitted in the initializer list, it gets initialized to its default value. In

          the grade is omitted, we're only setting the name, so the grade gets value-initialized to 0. When you access a student through the subscript operator, their grade still is 0, because it was never changed.

          • Ahmed

            What about the situation in quiz 2.c:

            Isn't assigning a name "Joe" as a push_back with grade 0 in the vector enough to use it as a value for "gradeJoe{grades["Joe"]}" which was assigned a value of 'A'?

            Even if we push back Frank as a new name, it will just get the next pushback in the vector with another grade value of 0. Isn't it?

            • nascardriver

              I'm not sure what you're getting at. Are your questions related to those of @Dong?

              • Ahmed

                I'm sorry for the confusion!

                I am using your answer to @Dong's question as an example to what seems to contradict the statement in the answer to the quiz.

                You clarified that the .push_back will still add a member to the vector with an initialized name, but the grade would be initialized to 0 as a default value. Meanwhile, in the extra credit question 3, it states:

                "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"), gradeJoe is left as a dangling reference to deleted memory. This will lead to undefined results."

                I don't understand why adding another *name-only* member would lead to a dangling reference? Isn't pushing-back by reference in all cases?

                • nascardriver

                  No contradiction here. At first, the vector has capacity 1, and it only holds Joe. When we add an element to a vector whose capacity is at its limit, the vector has to resize.
                  To resize, the vector creates a new array, copies all elements, and deletes the old array. `gradeJoe` is a reference into the old array, which is now deleted, so it dangles.

  • kavin

    Under "Pointers to objects and overloaded operator[] don’t mix", line 21,

    Is it just a pointer to an object or we are also dynamically allocating an array ?

    • nascardriver

      We're allocating a new object

      and we're default-initializing it with empty curly braces

      Then we store the pointer to the newly allocated object in a variable names `list` which we initialize with brace initialization.

      Because the type is obvious, we can use `auto` without doing harm.

  • kavin

    Hi, under the quiz 1c i am not able to understand line 35,

    Why does it need curly brackets {} ?

    Also, under Dealing with constant objects example , line 4 must be

    If we don't mention the size of array it throws this error:
    " zero-sized array in stack object will have no elements (unless the object is an aggregate that has been aggregate initialized)"

    • nascardriver

      `m_map` is a `vector` of `StudentGrade`, which means that `m_map.push_back` needs an argument of type `StudentGrade`. `name` is a `std::string`, not a `StudentGrade`. We can construct a `StudentGrade` in-place like so

      But brace initialization can deduce types in some situations, including this one. It knows that `push_back` wants a `StudentGrade`, so we can omit the explicit class name.

      > line 4 must be [...]

  • ebuka

    in example intlist, dealing with const objects, i dont understand why const was used twice, thank you

    • nascardriver

      Do you mean here?

      The first `const` is a part of the return type `const int&`. The second `const` makes the member function const. See lesson 8.10.

  • Ged

    1. Is it possible to define std::vector size in a class?

    When we write

    It works, but if we try to do something like this.

    We get an error.

    2. Is it possible for the user to input the size of array?

    For example

    3. When we write this line

    How does it initialize? m_map[0] has both values of nothing and we do pushbacks that overwrite m_map[0] and add m_map[1] and later m_map[1] gets overwritten and m_map[2] gets added ?

    • nascardriver

      Members cannot be initialized with direct initialization (I don't know why).

      Alternatively, use the member initializer list.

      Use a constructor with a member initializer list.

      Initialized to an empty vector. Vectors grow dynamically, see the lesson about vectors.

  • sito

    hello! for quiz 1c I decided to not try the find if function because  it uses iterators and i don't fully understand how the function is supposed to work so I decided to see if a name exists in a vector by using a for each loop, looping through it and compare. My problem is though that despite overloading so that i compare struct one to struct 2, because the variable in the for each loop is a struct it won't work. Is this because I don't do the comparison in main?
    here is my code below

    • nascardriver

      `m_map` is a `std::vector` of `StudentGrade`. `i` (Line 16) is a `StudentGrade`, but `` (Line 17) is a `std::string`. You can't compare a `StudentGrade` to a `std::string`. You need to compare the name of the student `i` to the name of `student`.

      I added a simple sample implementation of `find` as a quiz to lesson P.6.8a. You were probably already past that lesson when I added it. Although iterators are unknown at that point, it might help you to understand how `find` is implemented with iterators (Pointers are basic iterators).

  • Attila


    I feel like the quiz is very poorly paced.

    1a) and b) are quite self-contained and almost trivial, and then 1c) is crammed with information.
    I see, that the same was intended for 1c: "Write an overloaded operator[] for this class.", but it ended up a bit more complicated than that.

    For the sake of explaining what the program should do, 1c should be split into two sections:
    1c) for the for-each loop and it's purpuse
    1d) for the rest of the overload.

    1d) may be split further if necessary, but it defenetly needs to be rewritten. It's extremely confusing.

    • nascardriver

      Hi Attila,

      thanks for your feedback! We're currently writing new lessons about algorithms and lambdas, which will be useful in this quiz and reduce its length. I've marked this quiz for an update when the lessons are done.

  • vishs


    While I agree that overloading [] operator is helpful for better comprehension and easier looking syntax in the examples you have taken above

    But for other examples like:

    Suppose I do this:

    The above code is really not good, because class itself is having so many arrays and also non-array elements as members, in this case overloading as object[] doesn't give any idea of which list(list1 or list2) is being accessed and also what it means for "someotherData" member

    So in reality, most classes will be having many data members of different types, so overloading operators with class objects gives no idea of what members are getting affected and in which way, so i feel overloading operators reduce readability of code? is my understanding correct?

    • nascardriver

      Hi again,

      operators, just like poorly named functions, can always be used in a way that makes them more confusing than helpful. You _can_ overload `operator[]` for `Database`, but as you said, it doesn't make a lot of sense. `operator[]` is used for map- and list-like objects.
      Operators reduce code quality when they're used poorly, but increase it when used correctly. That's not a property unique to operators, you'll find it everywhere.

  • Guybrush87


    First of all, thank you for this great courses !

    I have a silly question, why the code above don't compile with the error "error: invalid conversion from 'const int*' to 'int*' [-fpermissive]"

    I don't modify nothing, why can I make my class member getList constant ?

    Thank you for your help.


    • `getList` is `const`.

      this would be legal if your code compiled, but obviously it shouldn't, because `list` is `const`.

      Don't mark `getList` as `const` or return a `const int*`.

      • Guybrush87

        Hello nascardriver,

        I'm sorry, I don't understand your answer, here is my code :

        For this code the compiler says :
        error: invalid conversion from 'const int*' to 'int*' [-fpermissive]
          int* getList() const { return m_list; }
        In my code there is nothing const except for the function member getList.
        What I don't understand is that my member getList doesn't modify any data member, why isn't it possible to declare it as a const member ?

        Thank you very much for your help !


  • potterman28wxcv

    Hello !

    The 3) part scares me a lot about C++ ! Because from a glance, the code looks alright, and you just wouldn't see such a bug easily. Sorting out that bug requires having knowledge of how std::vector is working internally.. I guess std::vector is widely used, but if you use some library defined by another programmer and only have the API, there is no way you could easily trace back that bug.

    Is there any general rule to follow in C++ to avoid writing the code of 3) ? That code really sends me shills because any reference could be "eventually dangling" as soon as you initialize them! I can't even imagine the amount of bugs that must have been caused by this kind of scenario in large projects..

    • Alex

      As a general rule, we always have to be careful when holding references or pointers to data that lives in dynamic memory, because the destruction of that memory may not be predictable.

      In this case, the scariness is mainly due to the fact that we don't expect operator[] to invalidate pointers or references, but it might. This is a function of using a std::vector to implement the class.

      I can think of two possible solutions here:
      1) Rewrite the interface to the class to make operations that might invalidate data always needs to be explicitly called by the user (e.g. have an add() function). At least that way, control of the lifetime of the data is under the user's control.
      2) Better, use an implementation that doesn't invalidate the current set of elements when new ones are added. std::map doesn't invalidate references or pointers when operator[] is used with it (, and so would be a better choice to implement this class with.

  • George

    How are the constant and non-constant versions unique? Same return type and same number and type of arguments...

    • One is marked as `const`, the other one isn't.
      The `const` version is used for `const` objects, and also for non-const objects if no non-const version exists. ie. the type of the object the function is called on determines which function gets called.

  • noobmaster

    I edited function char& GradeMap::operator[](const std::string &name) in solution for 1c

    my compiler throws me a warning 'GradeMap::operator[]': not all control paths return a value
    can someone explain why?

  • mmp52


    in the for - each loop, what is the difference between making the iterator a reference or not? Also, if we use it like that how does compiler understand we did not mean the address but the iterated element's reference?

    thanks for the amazing content !!

    • Iterating over copies is slow. Non-fundamental types should always be passed/returned/iterated over by reference.
      The first part of a for-each loop is a declaration, address-of doesn't make sense at that point, just like with regular variable declarations.

  • Hai Linh

    Quick question: Is there any reason to make index non-const? It would make sense to make index const, that way the function cannot change the value of index.

  • Vir1oN

    Could you refresh my memory, why do we use return by address instead of return by reference here?

  • Anthony

    Hi Alex,

    Given the following class definition:

    The following works:

    However, if I change


    the line will compile, but


    What is the difference between


    ? And why doesn't the line that follows compile?


    • is a function declaration. This is known as C++'s most vexing parse, because you might expect it to be a default initialization.
      Brace initializers solve this problem.

  • Anthony

    This is a very interesting lesson. I initially omitted to use a reference within the for-each statement of question 1c, leaving a dangling reference. Code::Blocks picked it up with a warning about a local variable being returned. I'm learning :)

  • Tyson


    When I try to compile my solution or Alex' solution to Question-1C, I get this error "error: 'GradeMap::m_map' should be initialized in the member initialization list [-Werror=effc++]"

    However, I was able to overcome it by adding empty curly brackets next to m_map, the private member, like this:

    My question is, is this the correct way of solving the problem?

    Thank you for the great tutorials.

    • Alex

      This issue has been fixed in the lesson. Thanks for pointing out the omission.

      • Hamza

        I don't know why would it be a problem to leave it uninitialized .. I compiled it without curly brackets and it worked fine!

        • Not explicitly initializing it will still initialize it to an empty vector.
          This only works, because `std::vector` a class-type and has automatic storage duration. If it was a fundamental type, you'd have to initialize it or risk undefined behavior on use. Since differentiating between the types (Initialize one but not the other) is extra work and could break if a type is changes in the future, initializing all types the same is best.

  • Louis Cloete

    @Alex, I don't know if you know Rust, but I have started learning it as well. In Rust, Quiz q3 will not compile, because the compiler enforces that you have only one "mutable borrow" (non-const reference in C++) OR any amount of "immutable borrows" (const references in C++). This means you can't mutate a vector while there are other references to it. This prevents dangling references/pointers and data races at compile time. Knowing this, I immediately spotted the error.

    I find that my dabbling with Rust causes me to think safer in C++, because I had to fix many compiler errors because of code like this, which is legal in C++, but causes compiler errors in Rust.

Leave a Comment

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