C++ Constants

A constant is a value that, once set, cannot be changed while the program runs. In C++, constants are used for things like mathematical values (pi), configuration limits (maximum array size), or any data that must stay fixed to keep a program correct and safe from accidental modification. Using constants instead of “magic numbers” scattered through your code makes programs easier to read, easier to maintain, and less prone to bugs.

Overview / How It Works

In C++, you create a constant by adding a qualifier — const or constexpr — to a variable declaration. This tells the compiler: “this value must be initialized once, and any attempt to modify it afterward is an error.” The compiler enforces this at compile time, meaning your code won’t even build if you try to assign a new value to a constant. This is different from simply “being careful” not to change a variable — the compiler actively checks for you.

There are two main ways to declare constants in modern C++:

  • const — the value cannot change after initialization, but it may be computed at run time (for example, from user input).
  • constexpr — the value must be computable at compile time. The compiler evaluates it before the program even runs, which can make code faster and allows the constant to be used in contexts that require compile-time values (like array sizes).

Under the hood, a const variable still occupies memory (unless the compiler optimizes it away), and the compiler tracks its type as “read-only.” Any code path that tries to write to it — direct assignment, passing it to a function expecting a non-const reference, etc. — fails to compile. A constexpr value, when possible, is substituted directly into the generated machine code as a literal, with no memory access needed at run time, similar to how the preprocessor macro #define worked in C — but with full type checking, which #define does not have.

There is also an older, macro-based way to define constants using the preprocessor:

#define PI 3.14159

This works by simple text substitution before compilation even begins — the preprocessor literally replaces every occurrence of PI with 3.14159 in the source text. It has no type, no scope, and cannot be debugged as a variable, so modern C++ style strongly prefers const or constexpr instead.

Syntax

const type name = value;
constexpr type name = value;
Part Meaning
const / constexpr Qualifier that marks the variable as unmodifiable
type The data type of the constant (e.g. int, double, char, std::string)
name The identifier used to refer to the constant
value The initial (and only) value the constant will ever hold — required at the point of declaration

Note the key rule: a constant must be initialized where it is declared. You cannot declare a constant now and assign it a value later, because that would require the ability to write to it after creation, which defeats the purpose.

Examples

Example 1: Basic const usage

#include <iostream>
using namespace std;

int main() {
    const double PI = 3.14159;
    const int MAX_STUDENTS = 30;

    cout << "PI is: " << PI << endl;
    cout << "Max students allowed: " << MAX_STUDENTS << endl;

    return 0;
}

Output:

PI is: 3.14159
Max students allowed: 30

This declares two constants of different types. Once set, PI and MAX_STUDENTS can be read anywhere in their scope, but any attempt to reassign them would cause a compile-time error.

Example 2: Using constexpr for a compile-time array size

#include <iostream>
using namespace std;

int main() {
    constexpr int SIZE = 5;
    int scores[SIZE] = {90, 85, 77, 92, 88};

    int total = 0;
    for (int i = 0; i < SIZE; i++) {
        total += scores[i];
    }

    double average = static_cast<double>(total) / SIZE;
    cout << "Average score: " << average << endl;

    return 0;
}

Output:

Average score: 86.4

Here, SIZE is declared with constexpr because it is used to define the size of a fixed-size array — something the compiler must know before the program runs. A plain const int initialized with a literal would also work in this specific case, but constexpr makes the compile-time requirement explicit and is preferred for this purpose.

Example 3: A realistic program using multiple constants together

#include <iostream>
using namespace std;

int main() {
    const double TAX_RATE = 0.08;
    const double ITEM_PRICE = 19.99;
    const int QUANTITY = 3;

    double subtotal = ITEM_PRICE * QUANTITY;
    double tax = subtotal * TAX_RATE;
    double total = subtotal + tax;

    cout << "Subtotal: $" << subtotal << endl;
    cout << "Tax: $" << tax << endl;
    cout << "Total: $" << total << endl;

    return 0;
}

Output:

Subtotal: $59.97
Tax: $4.7976
Total: $64.7676

This example models a small receipt calculation. Using named constants like TAX_RATE instead of writing 0.08 directly in the formula makes the code self-documenting: anyone reading it immediately understands what the number represents, and if the tax rate ever needs to change, there is exactly one place to update it.

How It Works Step by Step

  • The compiler parses the declaration const double PI = 3.14159; and records that PI has type double and is read-only.
  • Memory is set aside for PI (unless the compiler decides to inline the value, which it often does for simple constants), and the value 3.14159 is stored there during initialization.
  • Every later use of PI in the code reads from that memory location (or uses the inlined literal).
  • If the source code contains a statement like PI = 3.14; anywhere after the declaration, the compiler detects the attempted write to a read-only variable and stops compilation with an error — the program never even gets to run.
  • For constexpr, the compiler goes a step further: it tries to evaluate the initializing expression itself, during compilation, and requires that this be possible (using only other constexpr or literal values). If it can’t be evaluated at compile time, compilation fails.

Common Mistakes

Mistake 1: Forgetting to initialize a constant at declaration

const int MAX_USERS;   // Error: no initializer
MAX_USERS = 100;       // Error: even if it compiled, this line would fail

This fails because a constant must receive its value immediately when declared — there is no valid moment afterward to assign one. The fix is to combine the declaration and initialization:

const int MAX_USERS = 100;

Mistake 2: Trying to modify a constant later in the program

const int SPEED_LIMIT = 65;
SPEED_LIMIT = 70;   // Error: assignment of read-only variable

Once SPEED_LIMIT is declared const, the compiler will reject any later assignment, even if the new value makes logical sense in the program. If the value genuinely needs to change during execution, it should not be declared const in the first place — use a regular variable instead.

Mistake 3: Using constexpr with a value that isn’t known at compile time

int userInput;
cin >> userInput;
constexpr int LIMIT = userInput;   // Error: userInput is not a compile-time constant

Since userInput is only known once the program is running (after the user types something), it cannot be used to initialize a constexpr variable. The fix is to use const instead, which allows run-time-computed values:

const int LIMIT = userInput;

Best Practices

  • Prefer constexpr over const whenever the value is genuinely known at compile time (e.g. array sizes, mathematical constants).
  • Use const for values that are fixed after initialization but depend on run-time data, such as user input or file contents.
  • Avoid the C-style #define for constants — it has no type safety, no scope, and can’t be inspected by a debugger.
  • Name constants in a way that makes their purpose obvious, often in ALL_CAPS or clear descriptive names like MAX_RETRIES.
  • Group related constants together near the top of a file or function so they’re easy to find and update.
  • Use constants instead of “magic numbers” scattered through calculations — it documents intent and centralizes changes.

Practice Exercises

  • Write a program that declares a constexpr constant for the number of days in a week, then uses it to calculate and print how many hours are in that many days.
  • Write a program with a const double constant for a discount rate (e.g. 0.15) and a variable for an item’s original price entered by the user with cin. Calculate and print the discounted price.
  • Try writing a program that intentionally attempts to reassign a const variable after its declaration. Observe the compiler error message, then fix the code so it compiles successfully.

Summary

  • A constant holds a value that cannot change after it is initialized.
  • const allows initialization from run-time values; constexpr requires the value to be known at compile time.
  • Constants must be initialized at the point of declaration — there is no valid way to assign a value later.
  • The compiler enforces immutability, rejecting any code that tries to modify a constant.
  • Prefer const/constexpr over the older #define macro approach for type safety and clarity.
  • Using named constants instead of magic numbers makes code more readable and easier to maintain.