Skip to content
Shafin Chowdhury
Go back

const in C++: Constants, const Pointers, References, and constexpr

Software Developer & Computer Science Student

Published:
6 min read
Edit page
Table of Contents11 topics

One of the most powerful features of C++ is const correctness.

In C++, const is not merely a documentation comment or an optional style suggestion—it is a compile-time contract enforced strictly by the compiler. When an entity is declared const, the compiler guarantees that its value cannot be modified.

Writing const-correct code prevents subtle bugs, clarifies design intentions to other developers, and unlocks aggressive compiler optimizations.

In this final article of our C++ Fundamentals series, we demystify every facet of const: from basic variables and reference parameters to the often-feared pointer constness syntax, class member functions, and modern compile-time constexpr.


What You’ll Learn

  • What const means to the compiler and to your software architecture
  • Declaring and initializing const variables
  • Enforcing read-only contracts with const parameters and const references
  • The “Clockwise / Right-to-Left Rule” to master pointer constness:
    • Pointer to const (const int*)
    • const pointer to non-const (int* const)
    • const pointer to const (const int* const)
  • An introduction to const member functions in object-oriented classes
  • Runtime const vs. compile-time constexpr
  • Common mistakes like casting away constness

1. const Variables: Immutable Values

When a variable is marked const, its value is locked at the moment of initialization and cannot be altered thereafter:

const double GRAVITATIONAL_ACCELERATION = 9.80665;
const int MAX_RETRY_COUNT = 3;

// Attempting to mutate a const variable triggers a compile error:
// MAX_RETRY_COUNT = 5; // ERROR: assignment of read-only variable 'MAX_RETRY_COUNT'

The Initialization Rule

Because a const variable can never be assigned to later, it must be initialized immediately upon declaration:

const int port; // COMPILE ERROR: uninitialized 'const' variable 'port'

2. const References: Zero-Copy Read-Only Aliases

As explored in our parameter passing deep dive, a const reference creates an alias through which data can be read, but never modified:

int original = 42;
const int& ref = original;

cout << ref << '\n'; // Legal: Reading is fine

// ref = 99; // COMPILE ERROR: assignment of read-only reference

const references are the gold standard for function parameters accepting non-trivial types (std::string, std::vector, complex classes) because they avoid expensive copying while ensuring the function cannot corrupt the caller’s data:

void renderMesh(const vector<float>& vertices); // Safe & zero-copy

3. Pointers and const: Mastering the Syntax

Beginners are frequently confused by pointer declarations containing const. Does const apply to the pointer itself, or to the data the pointer points to?

The secret to decoding any pointer declaration in C++ is the Right-to-Left Rule: read the declaration starting from the variable name and moving from right to left.

There are four distinct variations:

1. int* p;                 Pointer to int
2. const int* p;           Pointer to const int
3. int* const p;           const pointer to int
4. const int* const p;     const pointer to const int

Variation 1: Non-Const Pointer to Non-Const Data

Both the pointer and the data it points to can be freely modified:

int a = 10, b = 20;
int* ptr = &a;

*ptr = 15;   // Legal: Modifies 'a'
ptr = &b;    // Legal: Reassigns ptr to point to 'b'

Variation 2: Pointer to const Data (const int* or int const*)

The pointer can point to different addresses, but the data located at those addresses cannot be modified through this pointer:

int a = 10, b = 20;
const int* ptr = &a; // Read right-to-left: "ptr is a pointer to an int that is const"

// *ptr = 15; // COMPILE ERROR: Cannot modify read-only location!
ptr = &b;     // Legal: ptr can still be redirected to point elsewhere!

[!NOTE] Writing const int* p is completely identical to int const* p. The style int const* (known as “east const”) places const to the right of what it modifies, making the right-to-left rule literal. Both styles are valid.


Variation 3: const Pointer to Non-Const Data (int* const)

The pointer address is permanently locked to one variable and cannot be redirected, but the data it points to can be modified:

int a = 10, b = 20;
int* const ptr = &a; // Read right-to-left: "ptr is a const pointer to an int"

*ptr = 15;   // Legal: Modifies 'a' to 15
// ptr = &b; // COMPILE ERROR: assignment of read-only variable 'ptr'!

Variation 4: const Pointer to const Data (const int* const)

Neither the pointer’s destination address nor the data it points to can be modified:

int a = 10, b = 20;
const int* const ptr = &a;

// *ptr = 15; // COMPILE ERROR: Cannot modify data!
// ptr = &b;  // COMPILE ERROR: Cannot redirect pointer!

Pointer Constness Summary Table

SyntaxCan Change the Pointer Address (ptr = &b)?Can Change the Value Pointed To (*ptr = 5)?Mental Model
int* ptrYesYesFully modifiable
const int* ptrYesNoRead-only view of data
int* const ptrNoYesFixed pointer, modifiable data
const int* const ptrNoNoCompletely immutable

4. const Member Functions (Object-Oriented Programming)

When working with C++ classes and structs, you can mark member functions with the const keyword placed after the parameter list:

class BankAccount {
private:
    double m_balance;

public:
    BankAccount(double initial) : m_balance(initial) {}

    // A const member function guarantees it will NOT modify any member variables
    double getBalance() const {
        // m_balance += 10.0; // COMPILE ERROR! Cannot mutate members in const method
        return m_balance;
    }

    // A non-const member function that mutates state
    void deposit(double amount) {
        m_balance += amount;
    }
};

Why This Matters

If you hold a const reference to an object, you are only allowed to call const member functions on it:

void printAccountInfo(const BankAccount& account) {
    cout << "Balance: $" << account.getBalance() << '\n'; // Legal!

    // account.deposit(50.0); // COMPILE ERROR: Calling non-const method on const object!
}

If you forget to mark getters as const, other developers will not be able to use your classes when passing by const reference!


5. Compile-Time Constants: constexpr vs. const

Introduced in C++11 and greatly expanded in C++14/17/20, constexpr (constant expression) guarantees that a value or function can be evaluated by the compiler at compile time.

The Difference: const vs. constexpr

  • const: Guarantees that a variable is read-only after initialization. However, its value might only be known at runtime (e.g., reading from user input or calling a dynamic function).
  • constexpr: Guarantees that a value is known at compile time. Every constexpr variable is implicitly const, but not every const variable is constexpr!
#include <iostream>

using namespace std;

int getUserInput() {
    int val;
    cin >> val;
    return val;
}

int main() {
    // Runtime const: Valid! Cannot be changed, but value is only known when program runs:
    const int runtimeVal = getUserInput();

    // Compile-time constant: Must be known during compilation!
    constexpr int MAX_ITEMS = 50 * 2; // Evaluated at compile time to 100

    // constexpr int badConstexpr = getUserInput(); // COMPILE ERROR! Cannot evaluate at compile time.

    // constexpr values can be used for array bounds and template parameters:
    int buffer[MAX_ITEMS]; // Perfectly legal standard C++!

    return 0;
}

[!TIP] In modern C++, if a constant value or calculation can be determined at compile time, declare it as constexpr instead of plain const.


Common Beginner Mistakes

1. Casting Away const

Never use C-style casts or const_cast to modify an object that was originally declared const:

const int original = 100;
int* mutablePtr = const_cast<int*>(&original);
*mutablePtr = 200; // UNDEFINED BEHAVIOR! The compiler may store 'original' in read-only memory.

2. Forgetting that const Pointers Must Be Initialized

Just like const variables, a T* const p must be initialized immediately upon declaration.

3. Missing const on Member Functions

Failing to mark inspection/getter methods as const prevents those objects from being inspected when passed as const T&.


Best Practices

  1. Be Const-Correct by Default: If a variable does not need to change after initialization, mark it const or constexpr.
  2. Pass Non-Trivial Types by const T&: Protect caller memory while eliminating allocation overhead.
  3. Mark All Inspection Member Functions const: Ensure class getters can be invoked on const instances.
  4. Use constexpr for Compile-Time Values: Use constexpr for mathematical constants, fixed buffer capacities, and enum-like configurations.

Summary of the C++ Fundamentals Series

Congratulations! You have completed the 12-part C++ Fundamentals curriculum:

  1. Variables & Data Types: Memory representation, primitive types, and sizeof.
  2. Input and Output: Streams, cin, cout, getline, and <iomanip> formatting.
  3. Operators: Arithmetic, logic, bitwise masking, precedence, and short-circuit evaluation.
  4. Conditional Statements: if, else if, modern C++17 initializers, and switch fallthrough.
  5. Loops: for, while, do-while, and modern range-based iteration.
  6. Functions: Signatures, declarations, overloading, scope, and pass-by-value.
  7. Arrays: Contiguous memory, zero-based indexing, array decay, and std::array.
  8. Strings: Legacy C-strings vs. modern, dynamic std::string.
  9. Pointers: Memory addresses, dereferencing, nullptr, pointer arithmetic, and heap allocation.
  10. References: Aliases, immutability of binding, and pointer comparisons.
  11. Pass by Value vs. Pass by Reference: Call stack mechanics, register performance, and the parameter decision matrix.
  12. const Correctness: Immutability contracts, pointer constness, class methods, and constexpr.

With these fundamental principles firmly mastered, you are prepared to explore advanced systems programming, dynamic memory internals, and the Standard Template Library.


Continue Learning Advanced C++


Edit page
Share this post:

Related Articles

Continue exploring related topics and foundational concepts.


Previous Post
Python Fundamentals
Next Post
Pass by Value vs Pass by Reference in C++: Performance and Mechanics