Question
Const Member Functions and std::set Iterators in C++
Question
Why does the following C++ program produce an error when calling getId() and getName() through a std::set<StudentT>::iterator?
#include <iostream>
#include <set>
#include <string>
using namespace std;
class StudentT {
public:
int id;
string name;
StudentT(int _id, string _name) : id(_id), name(_name) {}
int getId() {
return id;
}
string getName() {
return name;
}
};
inline bool operator<(StudentT s1, StudentT s2) {
return s1.getId() < s2.getId();
}
int main() {
set<StudentT> st;
StudentT s1(0, "Tom");
StudentT s2(1, "Tim");
st.insert(s1);
st.insert(s2);
set<StudentT>::iterator itr;
for (itr = st.begin(); itr != st.end(); ++itr) {
cout << itr->getId() << " " << itr->getName() << endl;
}
}
The compiler reports that passing a const StudentT as the this argument to getId() and getName() discards qualifiers. What is wrong, and how should the class be corrected?
Short Answer
You will learn what const means for member functions, why elements in a std::set are treated as read-only, and how to write accessors and comparison functions that work correctly with standard containers.
Concept
A member function is either allowed to modify its object or explicitly promises not to modify it.
A function such as this is not a const member function:
int getId() {
return id;
}
Its implicit this pointer is effectively StudentT* const this, so it may modify the current object. It cannot be called on a const StudentT object.
To promise that an accessor does not modify the object, place const after the parameter list:
int getId() const {
return id;
}
This makes the implicit this pointer effectively const StudentT* const this. The function can now be called for both ordinary and const objects.
Why std::set does this
A std::set stores unique elements in sorted order. Its ordering is determined by its comparison function, such as operator< in this example.
If code could modify an element while it was inside the set, especially the value used for sorting, the set could become incorrectly ordered. For that reason, dereferencing a set iterator gives access to a const element. Even a std::set<StudentT>::iterator behaves this way for its element access.
Therefore, this expression:
itr->getId()
attempts to call getId() on a const StudentT. The original getter does not have a const qualifier, so the compiler rejects the call.
Mental Model
Think of a const object as a library book behind a glass display case.
- You may read its title and author.
- You may not write in it or replace pages.
- A member function marked
constis a tool that only reads the book. - A member function without
constmight write in the book, so it is not allowed through the glass.
A std::set keeps its elements behind this glass because changing an element could change where it belongs in the sorted collection.
Syntax and Examples
Add const after a member function's parameter list when it does not change the observable state of the object.
class StudentT {
private:
int id;
std::string name;
public:
StudentT(int id, std::string name)
: id(id), name(std::move(name)) {}
int getId() const {
return id;
}
const std::string& getName() const {
return name;
}
};
getId() returns a small int, so returning it by value is appropriate.
getName() returns a const std::string&, avoiding a copy of the string while preventing callers from changing the student's stored name through the returned reference.
A suitable comparison operator also accepts const references:
bool operator<(const StudentT& left, const StudentT& right) {
return left.getId() < right.getId();
}
Passing by const reference avoids copying both objects and lets the operator compare const values.
Step by Step Execution
Consider this corrected loop:
std::set<StudentT> students;
students.insert(StudentT(2, "Ava"));
for (std::set<StudentT>::iterator it = students.begin();
it != students.end();
++it) {
std::cout << it->getId() << '\n';
}
Step by step:
students.insert(...)stores aStudentTin sorted order.students.begin()returns an iterator pointing at the first stored element.it->accesses the object referred to by the iterator.- For a
std::set, that referred-to object is treated asconst StudentT. getId() constpromises not to alter the object, so it may be called.- The function returns
id, and the program prints2. ++itadvances to the next element untilit == students.end().
If getId() did not end in const, step 5 would fail at compile time.
Real World Use Cases
Const member functions appear throughout C++ programs:
- Displaying API or database results: Read fields from records stored in a
std::set,std::map, or other collection. - Sorting and searching: Comparison functions inspect objects without changing them.
- Logging and debugging: A logger may receive
constreferences and call read-only accessors. - Function parameters: Functions often accept
const User&,const Config&, orconst Order&to avoid copying and prevent accidental changes. - Read-only application state: UI rendering code commonly reads from models without modifying them.
Whenever an operation only observes an object, making it const communicates that intent and allows it to work in more places.
Real Codebase Usage
Developers usually make fields private and expose const accessors. This protects class invariants and makes future changes safer.
class Student {
private:
int id_;
std::string name_;
public:
Student(int id, std::string name)
: id_(id), name_(std::move(name)) {}
int id() const noexcept {
return id_;
}
const std::string& name() const noexcept {
return name_;
}
};
For a std::set, projects commonly provide a dedicated comparator rather than a global operator<:
struct StudentById {
bool operator()(const Student& left, const Student& right) const {
return left.id() < right.id();
}
};
std::set<Student, StudentById> students;
Notice two separate uses of const:
const Student&: the comparator will not modify either student.operator()(...) const: calling the comparator will not modify the comparator object itself.
If an ordering field must change, do not modify an element in place inside a set. Remove the element, make the change, then insert it again. This preserves the set's ordering invariant.
Common Mistakes
Forgetting const on a getter
Broken:
class Student {
public:
int getId() {
return id;
}
private:
int id = 0;
};
Fix:
int getId() const {
return id;
}
Putting const in the wrong location
This makes the return value const, not the member function:
const int getId() {
return id;
}
For an int, const on the returned value is not useful. The needed syntax is:
int getId() const {
return id;
}
Changing an object in a const member function
This is invalid because name is modified:
std::string getName() const {
name += "!"; // Error
return name;
}
A const member function should only read normal data members.
Passing set elements by value in comparisons
This works but makes unnecessary copies:
bool operator<(StudentT left, StudentT right) {
return left.getId() < right.getId();
}
Prefer const references:
bool operator<(const StudentT& left, const StudentT& right) {
return left.getId() < right.getId();
}
Modifying a set element through an iterator
Do not expect this to work:
// it->id = 10; // Not allowed for a std::set element
Changing a key could invalidate the set's sorted structure. Erase and reinsert a changed value instead.
Comparisons
| Situation | Can modify object? | Can call getId() const? | Can call getId()? |
|---|---|---|---|
Ordinary StudentT object | Yes | Yes | Yes |
const StudentT object | No | Yes | No |
Element accessed through std::vector<StudentT>::iterator | Usually yes | Yes | Yes |
Element accessed through std::set<StudentT>::iterator | No | Yes | No |
std::set versus std::vector
std::vectoris a sequence. Its non-const iterator normally provides mutable access to elements.std::setis an ordered associative container. Its elements determine their own position, so mutable iterator access would be unsafe.
const member function versus static member function
- A const member function belongs to an object and promises not to modify that object.
- A static member function has no
thisobject at all and cannot directly access ordinary instance fields.
class Example {
public:
int read() const; // Reads one Example object
static int count(); // Does not use a particular Example object
};
Cheat Sheet
class Item {
public:
int id() const { // Callable on const and non-const objects
return id_;
}
private:
int id_ = 0;
};
- Put
constafter a member function's()to make it read-only. - A non-const member function cannot be called on a
constobject. - A const member function can be called on both const and non-const objects.
std::setelements are exposed as const because changing them could break ordering.- Use
const T&for read-only parameters when copyingTis unnecessary. - Compare set values with
bool operator<(const T&, const T&)or a comparator object. - To change a value that affects set ordering: erase it, create the updated value, and insert it again.
- Returning
const std::string&can avoid a string copy; do not return a reference to a local variable.
FAQ
Why are std::set elements const?
A set must remain sorted according to its comparator. Allowing in-place changes could make an element belong somewhere else while it remains in its old position.
Does std::set<StudentT>::iterator return a const object?
Yes, element access through a set iterator behaves as access to a const value. This is true even if the iterator type is named iterator rather than const_iterator.
Can a const member function return a value?
Yes. const describes whether the function may modify its object, not whether it may return a value.
Can I overload a member function with and without const?
Yes. C++ treats them as different overloads.
std::string& name() { return name_; }
const std::string& name() const { return name_; }
The first is selected for mutable objects; the second is selected for const objects.
Should every getter be const?
Normally, yes. A getter that only reads state should be const so it works with const objects and standard-library code.
Why should the comparison operator use const references?
const StudentT& avoids copying objects and ensures comparison does not modify either value.
Can I modify data in a const member function with mutable?
mutable permits specific members to change in a const function, usually for caches or synchronization details. Do not use it merely to bypass const-correctness for ordinary object state.
Mini Project
Description
Build a small student directory using std::set. The directory keeps students sorted by ID and prints them safely through const-qualified accessor functions. It demonstrates why a set's stored values should be treated as read-only.
Goal
Store unique students ordered by ID, then display every student's ID and name without const-related compiler errors.
Requirements
Create a Student class with private ID and name fields.
Add const-qualified accessor methods for both fields.
Store several students in a std::set ordered by ID.
Print every student by iterating over the set.
Attempt no in-place modification of an element after insertion.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.