C++ Const Overloading

Introduction

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.

const_overloading.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#include <iostream>

template <typename T>
class Vector
{
public:

// Default constructors
Vector() : mSize{0}, mBuffer{nullptr}
{
}
// Constructors which take more than zero arguments
explicit Vector(size_t size) : mSize{size}, mBuffer{new T[size]}
{
for (int i = 0; i < mSize; i ++)
{
mBuffer[i] = 0;
}
}
explicit Vector(std::initializer_list<T> lst) : mSize{lst.size()}, mBuffer{new T[lst.size()]}
{
std::copy(lst.begin(), lst.end(), this->mBuffer);
}
// Copy constructor
Vector(const Vector& vec) : mSize{vec.mSize}, mBuffer{new T[vec.mSize]}
{
std::copy(vec.mBuffer, vec.mSize, this->mBuffer);
}
// Move constructor
Vector(const Vector&& vec) : mSize{vec.mSize}, mBuffer{vec.mBuffer}
{
vec.mSize = 0;
vec.mBuffer = nullptr;
}
// Copy assignment
Vector& operator=(const Vector& vec)
{
delete[] this->mBuffer;
this->mBuffer = new T[vec.mSize];
std::copy(vec.mBuffer, vec.mSize, this->mBuffer);
this->mSize = vec.mSize;
return *this; // This line of code is not required
}
// Move assignment
Vector& operator=(const Vector&& vec)
{
delete[] this->mBuffer;
this->mBuffer = vec.mBuffer;
this->mSize = vec.mSize;
vec.mSize = 0;
return *this; // This line of code is not required
}
// Destructor
~Vector()
{
delete[] this->mBuffer;
}
// [] operator
T& operator[](int n)
{
std::cout << "Non-const operator [] called." << std::endl;
return this->mBuffer[n];
}
// [] operator const overloaded
const T& operator[](int n) const
{
std::cout << "Const operator [] called." << std::endl;
return this->mBuffer[n];
}
// Public methods
size_t size() const
{
return this->mSize;
}
// For non-const typed instance, we return normal pointers
T* data()
{
std::cout << "Non-const pointer returned." << std::endl;
return this->mBuffer;
}
// const overloaded
// For const typed instance, we return const pointers to prevent modifying the instance
const T* data() const
{
std::cout << "Const pointer returned." << std::endl;
return this->mBuffer;
}

private:

size_t mSize;
T* mBuffer;
};

template <typename T>
void printConstVector(const Vector<T>& vec)
{
for (int i = 0; i < vec.size(); i ++)
{
std::cout << vec[i] << " ";
}
std::cout << std::endl;
}

template <typename T>
void printNonConstVector(Vector<T>& vec)
{
for (int i = 0; i < vec.size(); i ++)
{
std::cout << vec[i] << " ";
}
std::cout << std::endl;
}

int main()
{
Vector<int> vec{1,2,3};
printConstVector(vec);
printNonConstVector(vec);

std::cout << "------------------------" << std::endl;

const Vector<int> constVec{1,2,3};
printConstVector(constVec);

std::cout << "------------------------" << std::endl;

int* pVec = vec.data();

std::cout << "------------------------" << std::endl;

pVec[0] = 4;
pVec[1] = 5;
pVec[2] = 6;
printNonConstVector(vec);

std::cout << "------------------------" << std::endl;

const int* pConstVec = constVec.data();
}

To compile the program, please run the following command in the terminal.

1
$ g++ const_overloading.cpp -o const_overloading -std=c++11

The expected output of the program would be as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ./const_overloading 
Const operator [] called.
1 Const operator [] called.
2 Const operator [] called.
3
Non-const operator [] called.
1 Non-const operator [] called.
2 Non-const operator [] called.
3
------------------------
Const operator [] called.
1 Const operator [] called.
2 Const operator [] called.
3
------------------------
Non-const pointer returned.
------------------------
Non-const operator [] called.
4 Non-const operator [] called.
5 Non-const operator [] called.
6
------------------------
Const pointer returned.

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.

1
2
3
4
5
const T* data() const
{
std::cout << "Const pointer returned." << std::endl;
return this->mBuffer;
}

However, if the data() method returns T* typed pointer, i.e., the implementation is

1
2
3
4
5
T* data() const
{
std::cout << "Const pointer returned." << std::endl;
return this->mBuffer;
}

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.

Author

Lei Mao

Posted on

03-08-2020

Updated on

11-26-2021

Licensed under


Comments