Search code examples
c++operator-overloadinggetter-settersquare-bracket

C++ square bracket overloading with and without const/ampersand


I am writing C++ code, and ran into a case where I wish to overload the square bracket operator for my class. From various sources, I find that you usually make two methods to do this, one with the const keyword, and one without the const keyword, but with an ampersand (&) before the method name. An example is given below.

int MyClass::operator[](unsigned int i) const {
    return this->values[i];
}

int & MyClass::operator[](unsigned int i) {
    return this->values[i];
}

My questions are as follows:

  1. Why do I have two methods, one with the const keyword, and one without it?
  2. What does the ampersand (&) in my second method do?
  3. Why can I set a value at a given index using square brackets, when none of my methods are setters, and which of these two methods is invoked when I set a value?

Solution

  • Why do I have two methods, one with the const keyword, and one without it?

    The const keyword sets a constraint that prevents the function from modifying the object. For example, if you use the const keyword in a setter, you'll get a compiler error.

    So the first function won't allow any modification on the object and in this case it will return a copy of the accessed element. The first function is a getter in disguise, it is a read only access, so to speak.

    What does the ampersand (&) in my second method do?

    The second function, instead, has the ampersand after the type, which means that it will return a reference of the returned value and not a copy of it. The returned value is part of the object, so returning a reference to it implies a potential modification and the const keyword wouldn't allow it.

    So this second function is a setter in disguise, it is a write access.

    Why can I set a value at a given index using square brackets, when none of my methods are setters, and which of these two methods is invoked when I set a value?

    As we've seen, the second function acts like a setter. When you use it, the returned value is a reference to the real element inside your object, and is valid for assignation (we call it a lvalue, more about them here).

    The point of this overload is: whenever you use the operators for a read operation (right side of the assignation or as a parameter) the first function will be called. If you use it for a write operation, it will be the second. In fact, this is how the same operator works for the STL containers you are probably using in your code.