constexpr

constexpr is a promise that a function or a variable can be evaluated by the compiler. The function is still an ordinary function at run time; the keyword adds the possibility that a call with constant arguments happens during the build.

The benefit is not speed in the usual sense: the value is available before the program starts, so an array size, a template argument and a static_assert can use it. This lesson marks a function, a variable and a loop, and meets the call that cannot be constant.

A constexpr function is also a hint to the optimiser, which can fold a call with constant arguments into a number. The keyword's guarantee is about the language, and the folded code is the consequence that shows up in a size or a disassembly.

A constexpr function that is used at run time costs nothing extra: the compiler emits one ordinary function, and the compile-time evaluations leave no code in the binary at all.

A constexpr function

A constexpr function is a normal function that the compiler may evaluate when every argument is a constant expression. The same function is compiled again for the calls with run-time arguments.

The program squares 7 at run time and 5 in a static_assert at compile time. The assertion is checked by the compiler, and the print shows 49 and 25.

The check costs nothing at run time and fails the build when the function is wrong. That is the main use of constexpr in a library: the unit test is a compile-time assertion next to the code.

#include <iostream> constexpr int square(int value) { return value * value; } static_assert(square(5) == 25, "square is wrong"); int main() { std::cout << square(7) << ' ' << square(5) << '\n'; }

g++ -std=c++23 -Wall -Wextra function.cpp -o constexpr_function
./constexpr_function

49 25

A constant that sizes an array

A constexpr variable is a constant known at compile time, so it can size an array, initialise a template argument and appear in another constant expression. A const variable that is initialised at run time cannot.

The program declares an array with size as its length and sums the four values. The print is 4, 10 and 16, where 16 is the size of the array in bytes.

The variable is also part of the type of the array, which is why the compiler has to know it before the program runs. That is the difference between const and constexpr in one sentence: const is a promise not to write, constexpr is a value the compiler has.

#include <iostream> constexpr int size = 4; int main() { int values[size]{1, 2, 3, 4}; int total = 0; for (int value : values) { total += value; } std::cout << size << ' ' << total << ' ' << sizeof(values) << '\n'; }

g++ -std=c++23 -Wall -Wextra variable.cpp -o constexpr_variable
./constexpr_variable

4 10 16

A loop in a constant expression

Since C++14 a constexpr function may contain a loop, a local variable and a branch, so the factorial below is written like any other function. Before C++14 the same function was a recursion with a conditional expression.

The program checks factorial(5) at compile time and prints 120 and 720. The assertion and the print call the same function, one in the compiler and one in the program.

The compiler has a limit on the steps and the recursion depth of a constant evaluation, and it is implementation-defined. A constexpr function with a huge loop fails the build with a message about the limit, which is the reason the heavy work belongs at run time.

#include <iostream> constexpr int factorial(int value) { int result = 1; for (int i = 2; i <= value; ++i) { result *= i; } return result; } static_assert(factorial(5) == 120, "factorial is wrong"); int main() { std::cout << factorial(5) << ' ' << factorial(6) << '\n'; }

g++ -std=c++23 -Wall -Wextra loop.cpp -o constexpr_loop
./constexpr_loop

120 720

The function that is not constexpr

A constant expression may only call functions marked constexpr. An ordinary function is compiled for run time, so a constexpr variable initialised from one cannot be evaluated by the compiler.

The program initialises a constexpr variable with a function that has no keyword. The compiler reports that the call to a non-constexpr function is not a constant expression, and it names the function.

The fix is to mark the function constexpr, which is legal as long as its body satisfies the rules, or to drop the constexpr from the variable and accept a run-time initialisation. Marking the function is usually the right answer for a small computation.

#include <iostream> int twice(int value) { return value * 2; } constexpr int result = twice(21); int main() { std::cout << result << '\n'; }

g++ -std=c++23 -Wall -Wextra plain.cpp -o plain

plain.cpp:8:29: error: call to non-'constexpr' function 'int twice(int)'
8 | constexpr int result = twice(21);
| ~~~~~^~~~
plain.cpp:3:5: note: 'int twice(int)' declared here
3 | int twice(int value)
| ^~~~~

Exercises

  1. Add a constexpr function that returns the larger of two ints and check it with a static_assert.
  2. Use a constexpr variable as a template argument for std::array and print its size.
  3. Call the factorial function with an argument read from std::cin and explain why the call is now a run-time one.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does constexpr on a function allow?

    Answer

    The compiler may evaluate a call with constant arguments during the build

    The function remains callable at run time; the keyword adds a compile-time evaluation for constant arguments.

  2. What does static_assert require?

    Answer

    A constant expression and a message

    The condition is evaluated by the compiler, and a false one stops the build.

  3. Can a constexpr function be called with run-time values?

    Answer

    Yes, it is an ordinary function then

    The same body is compiled for run time, which is why a constexpr function is not limited to compile-time use.

  4. What does this program print? #include <iostream> constexpr int square(int value) { return value * value; } static_assert(square(5) == 25, "square is wrong"); int main() { std::cout << square(7) << ' ' << square(5); }

    Answer

    49 25

    The first call squares 7 at run time and the second squares 5, the value the assertion checked.

  5. What does this program print? #include <iostream> constexpr int size = 4; int main() { int values[size]{1, 2, 3, 4}; int total = 0; for (int value : values) total += value; std::cout << size << ' ' << total << ' ' << sizeof(values); }

    Answer

    4 10 16

    The size is 4, the sum is 10, and the array is 16 bytes.

  6. What does this program print? #include <iostream> constexpr int factorial(int value) { int result = 1; for (int i = 2; i <= value; ++i) result *= i; return result; } static_assert(factorial(5) == 120, "factorial is wrong"); int main() { std::cout << factorial(5) << ' ' << factorial(6); }

    Answer

    120 720

    factorial(5) is 120 and factorial(6) is 720.

  7. What does a constexpr variable guarantee?

    Answer

    Its value is known at compile time and usable in constant expressions

    The initialiser must be a constant expression, so the compiler has the value before the program runs.

  8. Since which standard may a constexpr function contain a loop?

    Answer

    C++14

    C++11 allowed a single return statement, and C++14 relaxed the body to loops, locals and branches.

  9. What does the compiler say about constexpr int result = twice(21); when twice is not constexpr?

    Answer

    That the call to a non-constexpr function is not a constant expression

    A constant expression may only call constexpr functions, and the compiler names the call.

  10. What limits the work a constexpr evaluation may do?

    Answer

    An implementation-defined limit on steps and depth

    The compiler has a budget for constant evaluation, and exceeding it is a build error.

The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.

Article author: Arthur Isaev