In C++ programming, when we implement the methods for classes, we usually add const specifier to make sure that the members of the class instance will not be modified by the method. However, it is also valid that we do const overloading for the same method, i.e., a class has methods that have the exact same method name and arguments, and one of them has a const specifier.
In this blog post, I would like to talk about the const overloading for C++ class methods.
Examples
In this example, I implemented a simple Vector class mimicking the std::vector class. We could see that operator[] overloading methods have two versions, T& operator[](int n) and const T& operator[](int n) const. The data methods also have two versions, T* data() and const T* data() const. The (second) const specifier ensures that calling the method will not modify the instance members. Because the each of the member method has an implicit input pointer this, the const specifier can also be understood as making the input pointer this from a pointer into a pointer to const object. For example, the input pointer Vector* this will become Vector const* this if the member method has const specifier.
Given a class which has const overloading for certain method func(), if the instance of the class is const, calling the method func() will invoke func() const, and if the instance of the class is not const, calling the method func() will invoke the func() without the const specifier.
What if the class does not have const overloading? There would be problems. For example, let’s remove the const overloadings (const T& operator[](int n) const and const T* data() const) from the Vector class. The compiler threw errors against us complaining that “‘const Vector’ as ‘this’ argument discards qualifiers”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
$ g++ non_const_overloading.cpp -o non_const_overloading -std=c++11 non_const_overloading.cpp: In function ‘int main()’: non_const_overloading.cpp:126:42: error: passing ‘const Vector<int>’ as ‘this’ argument discards qualifiers [-fpermissive] const int* pConstVec = constVec.data(); ^ non_const_overloading.cpp:70:8: note: in call to ‘T* Vector<T>::data() [with T = int]’ T* data() ^~~~ non_const_overloading.cpp: In instantiation of ‘void printConstVector(const Vector<T>&) [with T = int]’: non_const_overloading.cpp:105:25: required from here non_const_overloading.cpp:87:25: error: passing ‘const Vector<int>’ as ‘this’ argument discards qualifiers [-fpermissive] std::cout << vec[i] << " "; ~~~^ non_const_overloading.cpp:59:8: note: in call to ‘T& Vector<T>::operator[](int) [with T = int]’ T& operator[](int n) ^~~~~~~~
This basically means that for a const class instance, it will look for methods with const specifiers when the methods are called. However, there is only one method without the const specifier declared and implemented, passing this const instance to the method without const specifier caused the problem.
If a class only has const methods, this is fine. A non-const class instance could call const methods.
Caveats
A const class method could return non-const values. For example, the following data() method returns const T* typed pointer. This means the returned pointer is const and we cannot modify the variable via dereferencing the pointer.
We could modify the variable via dereferencing the pointer! Although this is syntactically correct, sometimes the behavior is not what we really want. Given a const class instance, why would we ever want the instance to give us something that could modify its own content?
Conclusions
For some custom classes that we know we would create const instances or pass the instance by const values or references, const overloading is necessary.