Question
What `const` Means at the End of a Member Function in C++
Question
In C++, what does const mean when it appears at the end of a member function declaration, such as in the following example?
class foobar
{
public:
operator int() const;
const char* foo() const;
};
I want to understand what that trailing const does, how it affects the function, and what it means for the object on which the function is called.
Short Answer
By the end of this page, you will understand what a const member function is in C++, why const at the end of a method matters, how it changes the type of this, and when you should use it. You will also see examples, common mistakes, and how this concept appears in real C++ codebases.
Concept
In C++, when const appears after a member function declaration, it means the function is a const member function.
class Example {
public:
int getValue() const;
};
This tells the compiler that the function does not modify the object's observable state.
What it really means
Inside a non-static member function, C++ automatically passes a hidden pointer called this, which points to the current object.
- In a normal member function,
thisis effectively:
Example* const this;
- In a
constmember function,thisbecomes:
const Example* const this;
That means inside the function:
- you cannot modify data members of the object
- you cannot call non-
constmember functions on the same object - you are promising that the function is safe to call on a
constobject
Why it matters
This is one of the most important parts of const correctness in C++.
It helps with:
- preventing accidental changes to objects
- making APIs clearer and safer
- allowing functions to work with
constobjects and references - improving code readability and intent
Your example
class foobar
{
public:
operator int() const;
const char* foo() const;
};
operator int() const;
This means the conversion operator can convert a foobar object to int without modifying the object.
const char* foo() const;
This has two separate uses of const:
const char*means the returned characters should not be modified through that pointer- the trailing
constmeansfoo()does not modify thefoobarobject
These two consts do different jobs.
Important detail: observable state
A const member function should not change the logical value of the object. In simple beginner terms, treat it as: do not change member variables.
There is one special exception: members marked mutable can still be changed inside a const function. This is commonly used for:
- caches n- counters for debugging
- lazy computation
But as a beginner, the main rule is: a trailing const means the function should behave like a read-only operation.
Mental Model
Think of an object like a book in a library.
- A non-const member function is like writing notes in the book.
- A const member function is like reading the book without changing it.
If a function ends with const, you are telling the compiler:
"This function only reads from the object. It does not edit it."
Another useful mental model:
int getAge() const= asking a questionvoid setAge(int x)= changing something
So in practice:
- getters are usually
const - setters are usually not
const
The compiler acts like a strict librarian: if you said a function is read-only, it will stop you from changing the object inside that function.
Syntax and Examples
Basic syntax
class Person {
public:
int getAge() const;
void setAge(int value);
private:
int age;
};
Here:
getAge() constpromises not to change the objectsetAge(int value)is allowed to change the object
Example implementation
#include <iostream>
using namespace std;
class Person {
public:
Person(int a) : age(a) {}
int getAge() const {
return age;
}
void setAge(int value) {
age = value;
}
private:
int age;
};
int main() {
Person p(25);
cout << p.getAge() << '\n';
p.setAge(30);
cout << p.getAge() << '\n';
}
Why getAge() is const
getAge() only returns the value of age. It does not modify the object, so it should be marked const.
Calling methods on const objects
#include <iostream>
using namespace std;
class Person {
public:
Person(int a) : age(a) {}
int getAge() const {
return age;
}
private:
int age;
};
int main() {
const Person p(40);
cout << p.getAge() << '\n';
}
This works because getAge() is a const member function.
If getAge() were not const, this would fail to compile.
Your original pattern
class foobar {
public:
operator int() const {
return 42;
}
const char* foo() const {
return "hello";
}
};
operator int() constconverts the object to anintwithout modifying itconst char* foo() constreturns a pointer to read-only characters and does not modify the object
Step by Step Execution
Traceable example
#include <iostream>
using namespace std;
class Counter {
public:
Counter(int v) : value(v) {}
int getValue() const {
return value;
}
private:
int value;
};
int main() {
const Counter c(10);
cout << c.getValue() << '\n';
}
Step by step
1. const Counter c(10);
A Counter object is created with value = 10.
Because c is declared as const, you can only call const member functions on it.
2. c.getValue() is called
The compiler checks whether getValue() is allowed on a const Counter.
It sees:
int getValue() const
So the call is valid.
3. Inside getValue()
Because the function is const, the object is treated as read-only.
This is allowed:
return value;
This is not allowed:
value = 20; // error
4. The value is returned
The function returns 10, and it is printed.
What would fail?
class Counter {
public:
Counter(int v) : value(v) {}
int getValue() {
return value;
}
private:
int value;
};
int main() {
const Counter c(10);
c.getValue(); // error: calling non-const function on const object
}
The problem is not that getValue() changes anything. The problem is that the compiler has no promise that it won't change anything, because it is not marked const.
Real World Use Cases
Read-only accessors
A very common use is getter functions:
std::string getName() const;
int size() const;
bool empty() const;
These functions inspect an object without changing it.
Working with const references
Large objects are often passed by const reference to avoid copying:
void printUser(const User& user) {
std::cout << user.getName() << '\n';
}
For this to work, getName() must be a const member function.
Standard library style
The C++ standard library uses const member functions heavily:
std::string::size() conststd::vector::empty() conststd::map::find()has const and non-const overloads
Safer API design
Marking a function const tells other developers:
- this function is read-only
- it is safe to call in more places
- it should not have side effects on object state
Conversions and printing
Functions like conversion operators or display helpers are often const:
operator bool() const;
std::string toString() const;
Real Codebase Usage
In real C++ projects, trailing const is part of writing const-correct APIs.
Common patterns
Getters are usually const
class Config {
public:
std::string getHost() const;
int getPort() const;
};
Validation helpers are const
class Email {
public:
bool isValid() const;
};
Query methods are const
class Buffer {
public:
std::size_t size() const;
bool empty() const;
};
Overloading const and non-const versions
A common real-world pattern is to provide two versions of the same function:
class Text {
public:
char& at(std::size_t i) {
return data[i];
}
const char& at(std::size_t i) const {
return data[i];
}
private:
std::string data;
};
This allows:
- writable access for non-const objects
- read-only access for const objects
Guarding against accidental mutation
In larger codebases, marking methods const helps the compiler catch bugs early. If a function should only inspect state but accidentally modifies something, compilation fails.
Interacting with const references and pointers
Real code often passes objects around like this:
void logMessage(const Message& msg);
Inside logMessage, only const member functions can be called on msg. This encourages cleaner design.
Error handling and intent
A method that checks state should usually be const:
bool hasError() const;
std::string errorMessage() const;
That makes it clear these functions report information rather than change program state.
Common Mistakes
1. Confusing return-type const with member-function const
These are different:
const char* foo() const;
const char*applies to the returned pointer target- trailing
constapplies to the object
A beginner may think they mean the same thing, but they do not.
2. Forgetting to mark getters as const
Broken example:
class User {
public:
int getId() {
return id;
}
private:
int id;
};
void printUser(const User& u) {
// u.getId(); // compile error
}
Fix:
class User {
public:
int getId() const {
return id;
}
private:
int id;
};
3. Trying to modify members inside a const function
Broken example:
class Counter {
public:
int getAndIncrease() const {
value++; // error
return value;
}
private:
int value;
};
A const function cannot modify normal data members.
4. Calling non-const methods from const methods
Broken example:
class Test {
public:
void update() {
x++;
}
void show() const {
update(); // error
}
private:
int x;
};
Inside a const function, the object is treated as const, so non-const methods cannot be called.
5. Assuming const means absolutely nothing can change
mutable members can still be changed in a const function:
class Cache {
public:
int getValue() const {
calls++;
return 42;
}
private:
mutable int calls = 0;
};
This is legal, but use it carefully.
6. Marking a function const when it should modify the object
If the function changes meaningful object state, do not mark it const just to satisfy the compiler. That usually hides a design problem.
Comparisons
Trailing const vs other uses of const
| Syntax | Meaning |
|---|---|
const int x = 5; | x cannot be changed |
const char* p; | the characters pointed to by p should not be modified through p |
int* const p = &x; | p itself cannot point somewhere else |
int get() const; | this member function does not modify the object |
Const member function vs non-const member function
| Feature | Non-const function | Const function |
|---|---|---|
| Can modify data members | Yes | No, except mutable members |
| Can be called on const objects | No | Yes |
| Can call non-const methods | Yes | No |
| Typical use | setters, mutating actions | getters, checks, read-only operations |
Const object vs const return value
| Example | What is const? |
|---|---|
const User u; | the object u |
const char* name(); | the returned characters |
std::string getName() const; | the member function's access to the object |
When to use trailing const
Use it when the function:
- reads state
- computes a result without changing the object
- should work on
constreferences orconstobjects
Do not use it when the function:
- changes member variables
- performs a state-changing action
- is meant to mutate the object
Cheat Sheet
Quick rule
ReturnType functionName() const;
Trailing const means:
- the function does not modify the object
- it can be called on
constobjects - inside the function,
thisis treated like a pointer to const
Common examples
int size() const;
bool empty() const;
std::string name() const;
operator int() const;
Your example
class foobar {
public:
operator int() const;
const char* foo() const;
};
Meaning:
operator int() constdoes not modifyfoobarfoo() constdoes not modifyfoobarconst char*means the returned characters are read-only through that pointer
What is allowed in a const member function
- reading member variables
- returning values
- calling other
constmember functions
What is not allowed
- changing normal member variables
- calling non-const member functions on the same object
Exception
mutable int counter;
A mutable member can be modified even inside a const function.
Memory aid
constbefore the function name area often affects the type being returnedconstafter the parameter list affects the object itself
FAQ
What does const after a member function mean in C++?
It means the function promises not to modify the object it is called on, except for mutable members.
Can a const member function be called on a non-const object?
Yes. A const member function can be called on both const and non-const objects.
Why do I get an error calling a method on a const object?
Because the method is not marked const. The compiler only allows const member functions to be called on const objects.
Is const char* foo() const the same const twice?
No. const char* makes the returned characters read-only through the pointer. The trailing const means the function does not modify the object.
Can a const member function change anything at all?
Yes, it can modify members marked mutable. It can also change external objects through pointers or references if your code allows it, but it should not change the logical state of the object itself.
Should every getter be const?
Usually yes. If a getter only reads data, it should normally be marked const.
What is const correctness in C++?
It is the practice of using const consistently so the compiler can enforce read-only access where appropriate and prevent accidental mutation.
Mini Project
Description
Build a small Book class that stores a title and page count. This project demonstrates how const member functions let you safely read object data, especially when working with const objects and const references.
Goal
Create a class with read-only accessor methods marked const, and verify that they can be called on a const object.
Requirements
- Create a
Bookclass withtitleandpagesdata members. - Add a constructor to initialize both values.
- Add two getter methods marked
const. - Write a function that accepts a
const Book&and prints the book details. - Create a
const Bookobject inmainand call the getter methods.
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.