Concepts / Types of Constants

Types of Constants

A constant expression is an expression that can be evaluated at compile time.

  • Programming

Two Keywords That Sound Alike

C++ offers two keywords for working with values that should not change: const and constexpr. Both are often described as ways to make something constant, which makes them easy to confuse. They answer different questions, though. const is about a promise that the program makes not to change a value. constexpr is about when a value can be computed, specifically whether it can be evaluated at compile time. Keeping these two questions separate is the key to using either keyword correctly.

What do you think happens?

Assume var is an ordinary int that is not declared constant. Will the line constexpr double max2 = 1.4 * square(var); compile?

  • Yes, it compiles

  • No, it is an error

Reveal answer
Answer:

No, it is an error.

The source example marks this line as an error because var is an ordinary variable and so is not a constant expression. A constexpr initializer must be evaluable at compile time, and the value of var is not known until the program runs.

What const Promises

Definition

const means roughly 'I promise not to change this value,' and the compiler enforces that promise. A const qualifier tells the reader and the compiler that the value will not be modified through that name.

The promise is the important part. A const declaration does not say anything about how the value was produced. It says only that, once the value exists, the program will not change it. Because the compiler enforces the promise, an attempt to modify a const value is rejected rather than silently allowed.

const is used primarily to specify interfaces. When data is passed to a function, a const qualifier lets the programmer say that the function will not modify it, so the data can be passed without fear of it being changed. The source example declares a function as double sum(const vector<double>&), noting that sum will not modify its argument. The const in that declaration is the promise, and it is part of the function's interface. A caller who reads the signature knows what the function promises not to do to its input.

Tracing Three Initializations

The examples in this part use three kinds of values. One is a named constant declared with const, called dmv, with the value 17. Another is an ordinary variable that is not declared constant, called var. The third is a vector of doubles, called v, that is also not a constant. Each is used in a different initialization. For every line the question is the same: does the compiler have enough information to evaluate this initialization before the program runs, and is the promise about the value being kept?

DeclarationWhich names it depends onKnown before the program runs?Outcome
constexpr double max1 = 1.4 * square(dmv);dmv, a named const int with value 17YesAccepted
constexpr double max2 = 1.4 * square(var);var, an ordinary intNoError
const double max3 = 1.4 * square(var);var, an ordinary intNo, but none is requiredAllowed, evaluated at run time
constexpr double s2 = sum(v);v, an ordinary vector of doublesNoError

Tracing which names the compiler can treat as fixed before the program runs

Notice that max1 and max2 have the same shape. Both multiply 1.4 by a call to square applied to a single int. The only difference is the name passed in. Changing var to dmv changes the outcome, even though both lines have the same form. This is the first clue that constexpr is not about the shape of the expression but about what the compiler can know in advance.

Constant Expressions Versus Run-Time Expressions

A constant expression is an expression that can be evaluated at compile time. An expression whose value depends on something only known when the program runs can only be evaluated at run time. The distinction is not about whether an expression looks constant on the page. It is about whether every value the expression depends on is available to the compiler before execution begins.

dmvconst int with value 17varordinary intvordinary vector of doublessquare(dmv)constant expressionsquare(var)run-time expressionsum(v)run-time expressionargumentargumentargument
Which parts of these initializations are known when the program is compiled, and which are only known when it runs?

The left column holds the only values the compiler can treat as fixed. The right column holds values whose content is not available until the program runs. A constant expression is built only from things in the left column. Any expression that touches the right column is a run-time expression.

What constexpr Demands

Definition

constexpr means roughly 'to be evaluated at compile time.' It is used primarily to specify constants and allows data to be placed in read-only memory.

The source gives two reasons for constexpr: it allows data to be placed in read-only memory, where it is unlikely to be corrupted, and it supports performance. A constexpr declaration therefore makes a stronger demand than const. A const value may be computed at run time. A constexpr variable must be initialized with a constant expression, so the compiler has to be able to compute its initializer before the program runs.

constexpr initializermust use a constantexpressionCompiler evaluatesinitializerbefore the program runsComputed valuefixed in the compiledprogramRead-only memorywhere the data can beplacedcompileresultmay be placed
When a constexpr initializer is present, at what stage does the compiler compute the value, and what does the finished program contain in place of that computation?

The pipeline above shows the stage the source describes. The initializer is not something the program works out while it runs. The compiler has to be able to evaluate it first, and because the value is fixed in advance, the data can be placed in read-only memory.

Predicting Whether a constexpr Initialization Compiles

When you are unsure whether a constexpr initialization will compile, apply the rule from the source directly. Ask whether every value that the initializer depends on is one the compiler can evaluate at compile time. If every dependency is known in advance, the initializer is a constant expression and is accepted. If any dependency is an ordinary variable or a run-time value, the initialization is an error.

constexprinitializerEvery dependencyknown at compiletime?for example a named constor square(17)Acceptedconstant expressionErrorrun-time value involvedcheckyesno
Given a constexpr initializer, what sequence of checks determines whether the compiler accepts it or rejects it?
Worked example

Predicting three constexpr lines

Using the rule that every dependency must be known at compile time, predict whether each constexpr initialization compiles: max1 = 1.4 * square(dmv) with dmv a const int of 17; max2 = 1.4 * square(var) with var an ordinary int; and s2 = sum(v) with v an ordinary vector of doubles.

  1. 1
    Identify the dependencies of max1

    The initializer depends on dmv, which is a named constant with the value 17. The source comment says this is acceptable because square(17) is a constant expression.

  2. 2
    Judge max1

    Every dependency is known before the program runs, so max1 compiles.

  3. 3
    Identify the dependencies of max2

    The initializer depends on var, an ordinary variable that is not a constant expression.

  4. 4
    Judge max2

    Because a run-time value is involved, max2 is an error. Its shape matches max1, so the shape alone does not decide the outcome.

  5. 5
    Identify the dependencies of s2

    The initializer calls sum on v, an ordinary vector of doubles that is not a constant.

  6. 6
    Judge s2

    The source states that a constexpr initializer such as sum(v) with an ordinary variable is an error, so s2 does not compile.

Result

max1 compiles; max2 and s2 are errors. The deciding factor is whether each dependency is known before the program runs.

const and constexpr Compared

The two keywords share the word constant, but they differ on the questions that matter most when you write code. const answers the question of whether the program promises not to change the value. constexpr answers the question of whether the value can be computed at compile time. Because these are separate questions, a value can be const without being constexpr. This is why the line const double max3 = 1.4 * square(var); is allowed. The source comment says it may be evaluated at run time.

Questionconstconstexpr
What it is primarily used forSpecifying interfaces and promising not to change a valueSpecifying constants that can be evaluated at compile time
What it guaranteesThe program will not change the value; the compiler enforces thisThe value is evaluated at compile time
Must the initializer be known at compile time?No; sum(v) computed at run time is allowedYes; the initializer must be a constant expression
Can data be placed in read-only memory?Not stated as the purpose of const in the sourceYes, this is one of its stated reasons
Example from the sourceconst double max3 = 1.4 * square(var);constexpr double max1 = 1.4 * square(dmv);

const constrains use after a value exists; constexpr constrains when a value must be computable

Mistakes Beginners Make

  • Common mistake

    Assuming const means the value is computed at compile time

    What it looks like
    const double max3 = 1.4 * square(var); is treated as if it must be evaluated early.
    Why it fails

    const only promises that the value will not be changed after it exists. The source says the value may be evaluated at run time.

    Do this instead

    Ask whether the value must be known before the program runs only when you see constexpr.

  • Common mistake

    Assuming constexpr is just a stronger spelling of const

    What it looks like

    Believing constexpr double max2 = 1.4 * square(var); should be fine because const double max3 = 1.4 * square(var); is fine.

    Why it fails

    The two keywords answer different questions. constexpr adds a requirement about compile-time evaluation that const does not impose.

    Do this instead

    Check the dependencies of a constexpr initializer separately from the promise it makes.

  • Common mistake

    Judging a constexpr initializer by its shape

    What it looks like

    Seeing that max1 and max2 both look like 1.4 * square(something) and expecting them to behave the same way.

    Why it fails

    Changing var to dmv changes the outcome even though the form is identical. What matters is whether the dependency is a constant expression.

    Do this instead

    Look at what each name is, not how the expression is written.

  • Common mistake

    Using an ordinary vector of doubles inside a constexpr initializer, such as constexpr double s2 = sum(v);, and expecting it to compile

Trying Your Own Prediction

MEDIUM

Before answering, predict the outcome of each line, assuming dmv is a const int with value 17, var is an ordinary int, and v is an ordinary vector of doubles: (a) const double max4 = 1.4 * square(var); (b) constexpr double max5 = 1.4 * square(dmv); (c) constexpr double s3 = sum(v); For each line, state whether it compiles and name the dependency that decides it.

Hints
  • Start with the keyword: does this line use const, constexpr, or both?
  • For constexpr, list every name the initializer depends on and check whether each is known before the program runs.
  • A const line may still be evaluated at run time; that is allowed.

Key Takeaways

  • const is a promise that the program will not change a value, and the compiler enforces that promise; it is used mainly to specify interfaces, such as a function parameter that will not be modified.
  • constexpr requires that the value be evaluated at compile time, and its initializer must be a constant expression; it also supports placing data in read-only memory.
  • A constant expression depends only on values the compiler can know before the program runs. An expression that depends on an ordinary variable or a vector's contents can only be evaluated at run time.
  • A value can be const without being constexpr, as in const double max3 = 1.4 * square(var);, which is allowed because it may be evaluated at run time.
  • To predict whether a constexpr initialization compiles, check that every value it depends on is known at compile time; identical shapes can have different outcomes depending on the names involved.

Related concepts

More concepts starting with T