Compile-Time Evaluation with constexpr Variables
A constexpr function must be simple: its body is a return statement computing a value.
A Keyword Question About square
Imagine a small function that multiplies its argument by itself and returns the product. Its name is square, and the keyword constexpr sits in front of its declaration. The interesting question is what that keyword changes. Does it change how square behaves when you call it, or only when the work is allowed to happen? This article follows a single call to square through the rules that govern it, so that you can answer that question yourself by the end.
What do you think happens?
Suppose a program calls square with a value that is only decided while the program runs, and then wants to use that result where a constant is required. Can that call still count as a constant expression?
Reveal answer
No. The call is still legal, but its result is not a constant expression.
The constexpr keyword does not make a runtime argument into a constant. The call is allowed, but the value it produces cannot be treated as a compile-time constant. Keep this answer in mind as you trace the rules below.
The Shape a constexpr Function Must Have
The source states a strict structural requirement. To be constexpr, a function must be rather simple: its body is just a return statement that computes a value. A function with several statements, loops, or local work does not meet this requirement, so the keyword cannot be attached to it. The diagram below lays out this requirement as a hierarchy of what is allowed and what disqualifies a function.
The square function satisfies the rule because its entire body is one expression returned to the caller. The following declaration is a minimal example of that shape. It is generated for teaching purposes and follows the pattern the source describes.
- constexpr is the specifier that marks the function as eligible for constant-expression evaluation
- int is the return type, the type of value the function gives back
- square is the function name and x is its single parameter
- The body contains only one return statement computing x * x
Following Two Calls to square
The same constexpr function can be called in two different situations. The function body never changes between them. What changes is the argument, and the argument decides whether the call counts as a constant expression. The flowchart traces that decision for any call site.
Tracing Two Calls to square
Given the constexpr square function, classify each of two calls: square(4) where 4 is a literal, and square(n) where n is an ordinary int variable whose value is only set while the program runs.
- 1Identify the shape of the function
The body is a single return of x * x, so square meets the source's requirement and is declared constexpr.
- 2Examine the first call, square(4)
The argument 4 is a constant expression. Because the argument is constant, the call can be evaluated at compile time, and its result is itself a constant expression.
- 3Examine the second call, square(n)
The argument n is not a constant expression, because its value is decided only while the program runs. The call is still legal, so the program is not rejected.
- 4Classify the result of the second call
The result of
square(n)is not a constant expression. It is an ordinary runtime value, and it cannot be used anywhere that demands a compile-time constant.
square(4) produces a constant expression. square(n) is legal but does not produce a constant expression. The function body is identical in both cases.
a is 16 and b is 49. Only a was required to be a constant expression.square(4)has a constant argument, so the call is a constant expression and can initialize a constexpr variable- n is a runtime-style variable, so
square(n)is a legal call whose result is not a constant expression - The same square body serves both calls
One Function, Two Kinds of Arguments
A natural question is why the language does not simply require two versions of such a function, one for constant expressions and one for variables. The source explains that a constexpr function may be called with arguments that are not constant expressions in contexts that do not require constant expressions. This avoids defining essentially the same function twice, once for constant expressions and once for variables.
In practice, this means you write the computation once. Where a constant is required, pass constant expressions and the function can take part in compile-time work. Where only an ordinary runtime value is available, pass that value and use the result as a normal value, understanding that it is not a constant expression. The function does not refuse the argument. It simply produces a value that cannot be treated as a compile-time constant.
Write the computation once and decide at each call site whether its arguments are constant expressions. Do not create a duplicate function just because one call needs a compile-time constant and another does not.
Mistakes Learners Make with constexpr
Common mistake Assuming every call to a constexpr function runs at compile time
What it looks likeint b = square(n); with n an ordinary variableWhy it failsThe call is evaluated at compile time only when its arguments are constant expressions. A runtime argument makes the call an ordinary runtime call.
Do this insteadCheck each argument. Only a call with constant-expression arguments produces a constant expression.
Common mistake Believing a non-constant argument makes the call illegal
What it looks likePassing n to square and expecting a compile error
Why it failsThe source states that the call is still legal. The only consequence is that the result is not a constant expression.
Do this insteadUse the result as a normal value, and do not use it where a constant expression is required.
Common mistake Adding a second statement or a loop to a constexpr function
What it looks likeA body with a local variable followed by a return
Why it failsThe source requires the body to be a return statement computing a value. Several statements, loops, or local work disqualify the function from being constexpr.
Do this insteadKeep the body to a single return that computes the value, or remove the constexpr keyword.
Common mistake Writing separate constant and variable versions of the same computation
Why it failsA single constexpr function can serve both kinds of arguments, so duplicate definitions add maintenance cost without a benefit the source describes.
Do this insteadDefine the function once and pass constant or runtime arguments as the context requires.
Checking Your Understanding of square
Given the function constexpr int square(int x) { return x * x; } and an ordinary int variable m whose value is set while the program runs, decide for each call whether it produces a constant expression: square(3), square(m), and square(square(2)). Justify each answer by naming the argument and whether it is a constant expression.
Hints
- Ask first whether the argument of each call is a constant expression.
square(2)is a constant expression, so its result can serve as the argument of an outer call.square(m)is legal, but its argument is not a constant expression.
Key Takeaways
- A constexpr function must be simple: its body is a return statement that computes a value, and several statements, loops, or local work disqualify it.
- A call to a constexpr function produces a constant expression only when its arguments are constant expressions.
- A call with a non-constant argument is still legal, but its result is not a constant expression.
- One constexpr function serves both constant and variable arguments, so the computation is written once rather than defined twice.