What Is a Constexpr Function?

A constexpr function is a function that the language permits inside a constant expression. That is the whole definition, and the word to hold on to is permits. Writing constexpr in front of a function's return type does not schedule anything, does not promise a speedup, and does not change what the function computes. It widens the set of places the function is allowed to appear.

That distinction matters more than anything else in this lesson, so here it is up front:

Key Concept
constexpr on a function is permission, not a prediction. It says the function may be called where the language demands a value before the program runs. Whether any particular call is actually computed during the build depends on where you wrote it and what you passed to it.

The rest of the lesson works outwards from that: first the problem the keyword solves, then what "computed during the build" actually looks like, then the four conditions that decide whether it happens.

Why a Plain Function Cannot Be Called Here

You already know that a constexpr variable must be initialized by a constant expression, because the compiler has to bake the value into the program it is building. A call to an ordinary function is not a constant expression, no matter how simple the function is or how obvious its arguments are.

A timetabling program needs the seat count of a train made up of a given number of carriages. The function is three lines and the argument is a literal, and the program below still does not build.

#include <iostream>

int seatsOnService(int carriages)
{
    return carriages * 74;
}

int main()
{
    constexpr int rushHourSeats{ seatsOnService(8) }; // will not compile

    std::cout << rushHourSeats << " seats on the rush hour service\n";

    return 0;
}

The compiler is blunt about why:

s.cpp: In function 'int main()':
s.cpp:10:48: error: call to non-'constexpr' function 'int seatsOnService(int)'
   10 |     constexpr int rushHourSeats{ seatsOnService(8) }; // will not compile
      |                                  ~~~~~~~~~~~~~~^~~
s.cpp:3:5: note: 'int seatsOnService(int)' declared here
    3 | int seatsOnService(int carriages)
      |     ^~~~~~~~~~~~~~

Note what the error does not say. It does not complain that the multiplication is too hard, or that 8 is unsuitable. It objects to the function itself: seatsOnService() was never marked as usable in a constant expression, so the compiler refuses to consider it in one.

You could work around this here by dropping constexpr from rushHourSeats and letting the multiplication happen while the program runs. Later you will meet places in C++ where no such retreat exists, because the language needs the number before the program has started at all. In those places a function call has to be a constant expression or it has to go.

One Keyword Widens Where the Function May Be Called

The repair is a single word in front of the return type.

#include <iostream>

constexpr int seatsOnService(int carriages)
{
    return carriages * 74;
}

int main()
{
    constexpr int rushHourSeats{ seatsOnService(8) };

    std::cout << rushHourSeats << " seats on the rush hour service\n";

    return 0;
}

Output:

592 seats on the rush hour service

Nothing about the body changed. The multiplication is the same multiplication. What changed is that seatsOnService() is now admissible in a constant expression, so the initializer of rushHourSeats is one, and the compiler can proceed.

Proving the Answer Existed Before the Program Did

It is easy to accept "evaluated at compile time" as a phrase and never see evidence of it. Since the initializer of a constexpr variable is a place that requires a constant expression, the call to seatsOnService(8) is not merely allowed to be computed during the build, it is required to be. The compiler runs the function itself, gets 592, and replaces the call with that number. By the time the executable exists, no multiplication and no function call remain in main().

A static_assert can be pointed at the result to make the compiler show its work. The assertion below is wrong on purpose, so that the diagnostic has to reveal the value the compiler was holding.

#include <iostream>

constexpr int seatsOnService(int carriages)
{
    return carriages * 74;
}

int main()
{
    constexpr int rushHourSeats{ seatsOnService(8) };
    static_assert(rushHourSeats == 600); // deliberately wrong

    std::cout << rushHourSeats << " seats on the rush hour service\n";

    return 0;
}
s.cpp: In function 'int main()':
s.cpp:11:33: error: static assertion failed
   11 |     static_assert(rushHourSeats == 600); // deliberately wrong
      |                   ~~~~~~~~~~~~~~^~~~~~
  • the comparison reduces to '(592 == 600)'

the comparison reduces to '(592 == 600)' is the proof. The compiler had 592 in hand while it was still reading the source file, which is only possible if it executed seatsOnService() itself. Change 600 to 592 and the program builds and runs with no trace of the assertion in the executable, because a satisfied static_assert compiles to nothing at all.

Four Things the Compiler Needs Before It Folds a Call

Replacing a call with its result is usually called folding. Four separate things have to be true before it can happen, and each one fails in its own way.

The compiler needs You supply it with Without it
Permission to run the body during the build constexpr in front of the return type The call is rejected wherever a constant expression is required
Argument values it already knows Arguments that are themselves constant expressions The call falls back to run time
A body it is able to execute during the build Statements that do not depend on the program running The build fails the moment a caller demands a constant
A reason to do the work now A context that requires a constant expression The compiler is free to fold the call, and equally free not to

The first three are properties of the function and the call. The fourth is a property of the surrounding code, and it is the one that turns "possible" into "guaranteed". A constexpr variable initializer is the context you have met so far; a static_assert condition is another. Write a call anywhere else and the compiler may still fold it, but only as an optimization it is entitled to skip.

There is a knock-on effect worth stating now, because it explains an error you will eventually hit. Once the compiler commits to evaluating a call during the build, everything that call reaches has to be evaluatable during the build too. A compile-time evaluation cannot pause halfway through and wait for the program to start.

The Same Function, Running at Run Time

Marking a function constexpr costs you nothing at run time, because it takes nothing away. The function remains an ordinary function that can be called with ordinary variables.

Here a second constexpr function builds on the first, and both are handed values that are not constant expressions:

#include <iostream>

constexpr int seatsOnService(int carriages)
{
    return carriages * 74;
}

constexpr int spareSeats(int carriages, int booked)
{
    return seatsOnService(carriages) - booked;
}

int main()
{
    int carriagesToday{ 7 };  // an ordinary variable, not a constant expression
    int seatsBooked{ 401 };   // likewise

    std::cout << spareSeats(carriagesToday, seatsBooked) << " seats still free\n";

    return 0;
}

Output:

117 seats still free

carriagesToday and seatsBooked are plain int variables. They hold values the compiler can see in this small program, but seeing a value and having a constant expression are different things, and only the second one counts. So the second row of the table is not satisfied, the call cannot be required to fold, and the subtraction happens while the program runs.

Key Concept
When a constexpr function evaluates at run time, the keyword has no effect whatsoever. The function is entered, the body executes, a value is returned, and the result is an ordinary value with no compile-time status.

Why One Function Instead of Two

It would have been possible to design C++ so that compile-time functions were a separate species, unusable at run time. Every function you wanted in both worlds would then need two versions with identical bodies. Since C++ does not let two functions in the same scope differ only by such a marking, they would also need two different names, and every caller would have to pick the right one.

Allowing a single constexpr function to serve both cases removes all of that. spareSeats() is written once. Called from a constexpr variable initializer, it is computed during the build. Called with numbers typed in by a user, it runs like any other function. Both uses can even appear in the same program:

#include <iostream>

constexpr int seatsOnService(int carriages)
{
    return carriages * 74;
}

constexpr int spareSeats(int carriages, int booked)
{
    return seatsOnService(carriages) - booked;
}

int main()
{
    constexpr int plannedSpare{ spareSeats(7, 401) }; // computed during the build
    static_assert(plannedSpare == 117);

    int carriagesToday{ 7 };
    int seatsBooked{ 419 };
    int actualSpare{ spareSeats(carriagesToday, seatsBooked) }; // computed while running

    std::cout << "planned spare: " << plannedSpare << '\n';
    std::cout << "actual spare:  " << actualSpare << '\n';

    return 0;
}

Output:

planned spare: 117
actual spare:  99

One definition, one name, two evaluation times. The static_assert on the first result is what pins it down: an assertion that the compiler could check means the compiler had already done the arithmetic.

A Promise You Have to Be Able to Keep

Because constexpr is permission, it is also a claim about the body: you are asserting that this function could run during the build if a caller asked. The compiler does not verify that claim when it reads the definition. It verifies it the first time some caller actually demands a constant.

That gap lets a broken function look healthy for a long time. The program below is broken on purpose: registeredCapacity() was never marked constexpr, and spareSeats() calls it.

#include <iostream>

int registeredCapacity(int carriages) // not marked constexpr
{
    return carriages * 74;
}

constexpr int spareSeats(int carriages, int booked)
{
    if (carriages < 1)
        return 0;

    return registeredCapacity(carriages) - booked;
}

int main()
{
    int flexibleSpare{ spareSeats(7, 401) };        // compiles
    constexpr int fixedSpare{ spareSeats(7, 401) }; // will not compile

    std::cout << flexibleSpare << ' ' << fixedSpare << '\n';

    return 0;
}

The two initializers are character-for-character identical apart from one keyword, and only one of them is an error:

s.cpp: In function 'int main()':
s.cpp:19:41:   in 'constexpr' expansion of 'spareSeats(7, 401)'
   19 |     constexpr int fixedSpare{ spareSeats(7, 401) }; // will not compile
      |                               ~~~~~~~~~~^~~~~~~~
s.cpp:13:30: error: call to non-'constexpr' function 'int registeredCapacity(int)'
   13 |     return registeredCapacity(carriages) - booked;
      |            ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~
s.cpp:3:5: note: 'int registeredCapacity(int)' declared here
    3 | int registeredCapacity(int carriages) // not marked constexpr
      |     ^~~~~~~~~~~~~~~~~~

flexibleSpare is fine, because a run-time evaluation of spareSeats() is allowed to call anything. fixedSpare demands compile-time evaluation, and now the third row of the table bites: the body reaches a function that has no compile-time form. Marking registeredCapacity() as constexpr fixes both lines at once.

Best Practice
Call every constexpr function from a constexpr variable initializer or a static_assert at least once. A function that has only ever been called at run time has never had its compile-time claim tested, and the day a caller tests it is the day it fails to build.
Warning
Do not write code whose correctness depends on a call being folded somewhere that does not require a constant expression. Optimization level and inlining decisions both move that line, and neither is under your control. If the work has to happen during the build, put the call where the language insists on it.

Key Terminology

  • Constexpr function: a function permitted to be called in a constant expression, and equally able to be called at run time
  • Constant expression: an expression the compiler can fully evaluate while building the program
  • Context requiring a constant expression: a place, such as a constexpr variable initializer or a static_assert condition, that cannot accept a value produced later
  • Folding: replacing a call with the value the compiler computed for it, so nothing of the call survives into the executable
  • Compile-time evaluation: the compiler executing your function body itself, during the build, rather than emitting a call for the processor to make

Looking Forward

The next lesson takes the fourth row of the table apart properly, cataloguing exactly where a call is guaranteed to fold, where it probably will, and where it never can. After that comes consteval, which converts the permission constexpr grants into an obligation, and then a survey of what a compile-time function body may contain, which is far more than most people expect. Further ahead, non-type template parameters and fixed-size arrays give you contexts that require a constant expression, and each of them becomes far more usable once you can call a function to supply the value.

Summary

A constexpr function is one the language permits in a constant expression. Mark it by writing constexpr in front of the return type. Without that mark, a call to it is rejected anywhere a constant expression is required, even when every argument is a literal.

Permission is not a schedule. The keyword never guarantees that a given call is computed during the build.

Four things must line up for a call to fold: the function is marked constexpr, every argument is itself a constant expression, the body contains nothing that only exists at run time, and the call sits in a context that requires a constant expression. The first three make folding possible; the fourth makes it certain.

A value the compiler happens to know is not a constant expression. Passing an ordinary int variable drops the call out of the guaranteed case even when its value is visible a few lines above.

At run time the keyword does nothing. The function is entered and executed exactly as an unmarked function would be, and returns an ordinary value.

One function serves both worlds, which is the point of the design. Without it you would maintain two identical bodies under two different names and force every caller to choose.

The claim is checked late. A constexpr function that calls something with no compile-time form compiles happily until a caller demands a constant, then fails. Exercise each one from a constexpr variable or a static_assert so the check happens on your schedule rather than someone else's.