Question

What `const` Means at the End of a Member Function in C++

cppdeclarationmember-functionsc++-faqfunction-qualifier

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, this is effectively:
Example* const this;
  • In a const member function, this becomes:
const Example* const this;

That means inside the function:

  • you cannot modify data members of the object
  • you cannot call non-const member functions on the same object
  • you are promising that the function is safe to call on a const object

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 const objects 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 const means foo() does not modify the foobar object

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 question
  • void 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.

Take Quiz

Syntax and Examples

Basic syntax

class Person {
public:
    int getAge() const;
    void setAge(int value);

private:
    int age;
};

Here:

  • getAge() const promises not to change the object
  • setAge(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() const converts the object to an int without modifying it
  • const char* foo() const returns 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() const
  • std::vector::empty() const
  • std::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;
Take Quiz

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 const applies 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

SyntaxMeaning
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

FeatureNon-const functionConst function
Can modify data membersYesNo, except mutable members
Can be called on const objectsNoYes
Can call non-const methodsYesNo
Typical usesetters, mutating actionsgetters, checks, read-only operations

Const object vs const return value

ExampleWhat 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 const references or const objects

Do not use it when the function:

  • changes member variables
  • performs a state-changing action
  • is meant to mutate the object
Take Quiz

Cheat Sheet

Quick rule

ReturnType functionName() const;

Trailing const means:

  • the function does not modify the object
  • it can be called on const objects
  • inside the function, this is 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() const does not modify foobar
  • foo() const does not modify foobar
  • const 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 const member 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

  • const before the function name area often affects the type being returned
  • const after 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.

Take Quiz

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 Book class with title and pages data 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 Book object in main and call the getter methods.
Take Quiz

Keep learning

Related questions

View more