Concepts / const and Immutable Values

const and Immutable Values

constexpr is the C++ keyword for generalized and guaranteed constant expressions.

  • Programming

Why Some Values Must Be Known Early

Most values in a C++ program are not known until the program actually runs. A number a user types, the size of a file on disk, and the result of a calculation that depends on input all fall into this group. Other values are fixed by the program text itself, such as the size of a fixed buffer or a limit used in a check.

C++ provides the constexpr keyword for values in the second group. With it, the programmer tells the compiler that a value is a constant expression and must be computed from information the compiler already has. The keyword name combines two ideas: it is generalized, because it applies beyond simple literal values, and it is guaranteed, because the compiler checks the promise the programmer makes.

This article follows one small idea through several examples. You will first see how a constexpr value is computed, then how the compiler decides whether a declaration is allowed, and finally how a function that is marked constexpr can also be used with values that only exist at run time.

Two Groups of Values

The central distinction is when a value becomes known. A compile-time value is one the compiler can determine before the program runs. A run-time value is one that only becomes known while the program executes. The literal 4, the sum 2 + 3, and a name whose value was itself fixed from a constant expression are compile-time values. A value read from the keyboard is a run-time value, because the compiler cannot know it in advance.

Literal 4Keyboard input2 + 3File sizeFixed name fromconstantResult depending oninput
Which values can the compiler know before the program runs, and which can it only know during execution?

The question to ask about any expression is therefore not only what value it produces, but whether that value is available before execution begins. This matters because some places in the language require a fixed value. An array size or a static assertion can accept only a constant expression, so the compiler must be able to evaluate it early.

Tracing a constexpr Square Function

The following small program uses a constexpr function to compute a value, stores that value in a constexpr variable, and checks it with a static assertion. Read it once through and, for each line, ask yourself whether its result is known before the program runs.

cpp
Walk through it
  1. 1
    Line 1 to 3: the compiler records that square may be evaluated at compile time when its arguments are constant.
  2. 2
    Line 5: the literal 4 is a constant expression, so side is fixed before the program runs.
  3. 3
    Line 6: square(side) is called with a constant argument, so area can be computed during compilation.
  4. 4
    Line 7: the static assertion confirms area equals 16 without any run-time check.
Notes on the code
  • square is a constexpr function that multiplies its argument by itself
  • side is a constexpr variable whose initializer is the literal 4
  • area is initialized by calling square with side, which is itself a constant expression
  • static_assert checks the value of area during compilation

What do you think happens?

Will the compiler accept the declaration constexpr int area = square(side); when side is the constexpr variable 4?

Reveal answer
Answer:

Yes, the compiler accepts it.

The initializer square(side) uses only a constant argument, so every part of it can be determined from information the compiler already has. The programmer's promise that area is a constant expression can be checked, so the declaration is valid.

Worked example

Tracing where each value comes from

Decide, for each named value in the program above, whether it is known before the program runs.

  1. 1
    Step 1: the literal

    The literal 4 is written directly in the program text, so its value is fixed before execution.

  2. 2
    Step 2: side

    side is a constexpr variable initialized from the constant expression 4, so its value is also fixed in advance.

  3. 3
    Step 3: area

    area is initialized from square(side). The function receives a constant argument, so the call can be evaluated during compilation.

  4. 4
    Step 4: the check

    The static_assert compares area with 16. Because area is known in advance, the comparison can also happen during compilation.

Result

Every value in the program is known before the program runs, so the whole chain is a set of constant expressions.

What Counts as a Constant Expression

Definition

A constant expression is an expression whose value can be determined without running the program. Its value is available to the compiler before execution begins.

Three kinds of expression qualify in the source material: a literal such as 4, a sum such as 2 + 3, and a name whose value was itself fixed from a constant expression. A value read from the keyboard does not qualify. The rule does not depend on how simple or complicated the calculation looks. It depends only on whether the compiler can determine the result without running the program.

Key point

constexpr is the C++ keyword for generalized and guaranteed constant expressions. Generalized means the idea extends beyond literal values to computations and functions. Guaranteed means that when you mark a declaration constexpr, the compiler checks the promise, and the program is rejected if the value cannot be computed at compile time.

How the Compiler Checks an Initializer

When the compiler meets a constexpr declaration, it does not simply accept the programmer's word. It examines the initializer. If the initializer can be evaluated during compilation, the declaration stands. If it cannot, the compiler rejects the program. A constexpr declaration is never silently turned into a run-time value.

constexprinitializerCan it be evaluatedbefore the programruns?Declaration acceptedCompile error,program rejectedcheckyesno
What checks does the compiler make on a constexpr initializer, and which branch leads to acceptance versus a compile error?

Read the flowchart as a single question asked of every constexpr initializer. The initializer 4 and the call square(side) pass the check. An initializer that depends on a value read while the program runs fails it, and the compiler stops with an error rather than producing a program with a hidden run-time value.

Calling constexpr Functions at Run Time

A constexpr function is not limited to compile-time use. The same square function can also be called with a value that is only known at run time. In that case the call is evaluated when the program runs, and the function still returns the correct result. The difference appears at the declaration, not in the function itself.

cpp
Notes on the code
  • n is read from the user, so its value is a run-time value
  • square(n) into a plain int is accepted and evaluated when the program runs
  • The commented constexpr line would need a constant expression as its initializer, which square(n) is not

What do you think happens?

If the line constexpr int bad = square(n); is uncommented, what does the compiler do?

Reveal answer
Answer:

The compiler rejects the program with an error.

A constexpr variable needs a constant expression as its initializer. The value n is read from the keyboard, so the compiler cannot know it in advance, and the declaration fails the check.

const Versus constexpr

Both const and constexpr appear in declarations that mark a value as not changed after it is set up, but they make different promises. A const variable does not carry the same compile-time requirement as a constexpr variable. Its initializer may be known only at run time. A constexpr variable must have an initializer that the compiler can evaluate before the program runs.

const int m = n;constexpr int area =square(side);Initializer may be arun-time valueInitializer must be aconstant expression
How does a const variable whose value is only known at run time differ from a constexpr variable that must be fixed during compilation?

Mistakes Learners Make with constexpr

  • Common mistake

    Assuming any value that looks fixed is a constant expression

    What it looks like
    constexpr int n2 = read_number();
    Why it fails

    The value comes from the program's run-time behavior, so the compiler cannot determine it before the program runs.

    Do this instead

    Use const for a value that is set at run time, and use constexpr only when the initializer is built from constant expressions.

  • Common mistake

    Thinking a constexpr function must always run at compile time

    What it looks like
    int ok = square(n);
    Why it fails

    A constexpr function can run at compile time with constant arguments and at run time otherwise.

    Do this instead

    Call the function normally when the argument is a run-time value. Reserve constexpr declarations for values that must be known early.

  • Common mistake

    Believing a rejected constexpr declaration will quietly become a run-time value

    Why it fails

    The source states that the program is rejected rather than silently becoming a run-time value.

    Do this instead

    Read the compiler error as a signal that the initializer is not a constant expression, then fix the initializer or change the declaration to const.

Practice Tracing Compile-Time Values

MEDIUM

For each of the following declarations, decide whether the compiler will accept it as constexpr: (a) constexpr int a = 2 + 3; (b) constexpr int b = square(7); (c) int d; cin >> d; constexpr int e = d;. Explain your decision for each one using the idea of a compile-time value.

Hints
  • Ask whether the initializer's value can be determined before the program runs.
  • A call to a constexpr function with a constant argument can be evaluated during compilation.
  • A value read with cin is known only while the program runs.

Summary of constexpr Values

  1. 1
    constexpr is the C++ keyword for generalized and guaranteed constant expressions.
  2. 2
    A constant expression is a value the compiler can determine before the program runs, such as a literal, a sum of literals, or a name fixed from a constant expression.
  3. 3
    The compiler checks every constexpr initializer and rejects the program when the initializer is not a constant expression.
  4. 4
    A constexpr function can run at compile time with constant arguments and at run time otherwise, while const does not carry the same compile-time requirement.

At a glance

  • constexpr is the C++ keyword for generalized and guaranteed constant expressions.
  • A compile-time value is known before the program runs, while a run-time value depends on input or execution.
  • The compiler accepts a constexpr declaration only when its initializer can be evaluated before the program runs, and rejects it otherwise.
  • A constexpr function can be called with constant arguments at compile time or with run-time values at run time, but a constexpr variable needs a constant expression as its initializer.

Related concepts

More concepts starting with C