constexpr is a specifier keyword in the C and C++ programming languages which, roughly speaking, specifies that something may be evaluated at compile time. Unlike the usual const, which specifies read-only access, constexpr is a stronger form that implies const (in most contexts). While in C this is limited only to objects, constexpr may be used on objects, functions, or structured bindings in C++.

Additional keywords in the C++ family of "const" keywords include consteval, used for guaranteeing a function executes at compile time and compile time only, and constinit, used for guaranteeing static/thread storage duration, ensuring compile-time initialization (but not immutability).

Background

The word constexpr is a portmanteau of constant expression, first introduced in C++11. Prior to the introduction of constexpr, options for specifying compile-time constants and functions in C++ were limited to preprocessor macros (such as #define PI 3.1415926535 or procedural macros like #define SQUARE(x) ((x) * (x))), which performed textual substitution and thus lacked the type safety of a core language feature, or declaring an enumerated constant (such as enum { WINDOW_SIZE = 600 };), which was limited only to integral types. A particular motivation for the development of compile-time expressions was that std::numeric_limits<T>::max(), although being equivalent to the macro INT_MAX, produced a non-constant expression.

Initial proposals for generalised constant expressions and compile-time expressions were first proposed for inclusion into C++ by Gabriel Dos Reis in 2003, proposing generalizing constant expressions to include constant-valued functions, functions which could be evaluated at compile time, with a particular goal being to improve type-safety and portability. This proposed was further revised with the co-authorship of Bjarne Stroustrup and Jens Maurer in 2006 to introduce the constexpr keyword, along with including user-defined literals (objects of user-defined types with constexpr constructors and destructors) into the definition of constant expressions., and adopted into the draft for C++11 in July 2007. Herb Sutter, chairman of the C++ committee, in 2026 described the adoption of constexpr as one of the most 'momentous' polls of C++ history.

C++20 further introduced two keywords of the "const family": consteval and constinit. With introduction of compile-time reflection in C++26, a proposal was made for consteval variables, but was not added.

constexpr was later introduced into C, beginning in C23.

Semantics

The primary usage of constexpr is to allow for evaluating complex calculations at compile time, avoiding runtime overhead such as additional stack frames or memory without relying on preprocessor directives, as well as preventing certain cases of undefined behavior.

Variables

A variable or a variable template may be declared constexpr if it is a definition, is a literal type, is initialized at declaration, and is constant-initializable. Such a constexpr variable is implicitly made const. References may also be made constexpr provided they are initialized by constant expression and any implicit conversions invoked during initialization are also constant expressions.

C++26 allowed constexpr structured bindings, which may bind to aggregate types (i.e. structs), arrays, and pairs/tuples.

Pointers and references

In C, pointers (except nullptr), variably modified types, atomic types and volatile types may not be constexpr.

In C++, constexpr pointers and references are subject to slightly different restrictions than regular constexpr variables. In constexpr pointers, the pointer's held address must be an address of a variable with static storage duration, and is a constant pointer. Pointer arithmetic on these addresses (if not an array) and addresses of stack variables are not constant expressions.

Meanwhile, constexpr references bind only either to variables of static storage duration or constexpr variables (regardless of storage duration).

Functions

A constexpr function is a function whose return value may be computed at compile time (i.e. when the arguments are constant expressions), and when called at runtime, behaves as a regular function. A constexpr function evaluated at compile time will halt compilation once undefined behavior is invoked. constexpr functions are implicitly inline.

Because the return types of constexpr functions may be known at compile time, they may be invoked at compile time for use in compile-time expressions.

Coroutines may not be constexpr. Historically, constexpr functions had far more restrictions, including the disallowing of multiple return statements, try blocks, and inline assembly blocks, all of which have been gradually relaxed with each revision.

C++17 allowed closure types and lambda functions to be used in constant evaluation content.

Class methods and overloadable operators may also be declared with constexpr. Prior to C++14, constexpr methods had implicit const access, preventing them from being able to manipulate the class. Prior to C++20, virtual functions could not be constexpr.

Constructors and destructors

A constexpr constructor is one such that it allows the class to be initialized and declared at compile time. constexpr constructors are subject to the same requirements as constexpr functions. Until C++26, classes with virtual base classes could not have constexpr constructors. The introduction of constexpr virtual inheritance seeks eventually allowing for constexpr stream formatting through a constexpr constructor for std::ios_base. The implicitly generated constructor is guaranteed to be constexpr if the class is a literal type.

A constexpr destructor (introduced in C++20), meanwhile, is a destructor allowing destruction of the object at compile time. Like constructors, constexpr destructors are subject to the same requirements as constexpr functions. The primary motivations for the introduction of constexpr destructors were for allowing the usage of non-trivial classes at compile-time, such as std::string and std::vector, the latter of which is used extensively in the API of std::meta's compile-time reflection. This now allows for non-trivial allocations and destructions in compile-time contexts. The implicitly generated destructor is guaranteed to be constexpr if the class is a literal type. Like constexpr constructors, constexpr destructors historically disallowed destruction of classes with virtual base classes. In C++26, various standard library classes, which have been historically runtime-only, have been made compile-time eligible through constexpr destructors.

A class with static members that are instances of itself may be constexpr, but the fields inside the class must first be declared const while the constexpr declarations must reside outside the class. Trying to use declare a constexpr instance of a class as a static member of itself fails as no definition of an object may be an incomplete type, which the class is incomplete until it is closed.

Compile-time if statements

C++ features two forms of compile-time if statements: if constexpr and if consteval.

The former, if constexpr, introduced in C++17, allows for conditional branching at compile time. It requires that the condition be a constant expression convertible to bool. if constexpr discards the branch that is not taken, preventing it from compiling. This allows for conditional compilation without usage of the preprocessor. However, the discarded branch, even if not compiled, is required by the compiler to be syntactically valid or accepted. return statements in a discarded statement do not participate in function return type deduction.

The latter, if consteval, introduced in C++23, acts as a if constexpr where the condition is whether or not the if condition is being evaluated at compile time, rather than at runtime.

if consteval introduces an additional syntax: if !consteval { /* stmt1 */ } else { /* stmt2 */ } is equivalent to if consteval { /* stmt2 */ } else { /* stmt1 */ }.

Evolution

The constexpr specifier has evolved with each passing C++ standard revision either lifting restrictions on constexpr or introducing new contexts for the keyword's usage, as the historical constexpr in C++11 was subject to far more restrictions than in contemporary C++.

C++11

In the 2008 draft of C++11, constexpr functions were permitted to call themselves recursively. Later in 2010, the constexpr functions allowed to pass their parameters as const-reference and in 2011, static_assert checks and using statements were allowed within the contexts of constexpr functions. In the final C++11 standard, constexpr functions were allowed only one return statement.

C++14

C++14 lifted the restriction that all constexpr methods were implicitly const, allowing constexpr methods to be able to change values of member variables.

C++14 allowed multiple return statements, control flow blocks such as if/else and switch, loops, and variable declarations within constexpr functions. C++14 also allowed constexpr functions to have void return type. The following example shows a prime number checker which works at compile time.

C++17

C++17 specified that a constexpr specifier in the declaration of a function or a static member variable implicitly makes it inline. C++17 introduced if constexpr, influencing template metaprogramming.

Since C++17, closure types and lambda functions can be used in constant expressions. All lambdas are implicitly constexpr.

C++20

C++20 relaxed the constraints for constexpr evaluation in various contexts, including virtual function calls, transient heap allocation, constexpr destructors and others. Heap allocations in constexpr contexts must be known to be deallocated within constexpr contexts.

C++20 lifted the restriction on constexpr virtual methods.

C++23

C++23 allowed functions to contain goto statements, try blocks, static variables, and inline assembly. C++23 further introduced if consteval.

C++26

C++26 allows the usage of virtual inheritance in constructors and destructors declared with constexpr. Support for constexpr structured bindings was added, constexpr casting from void*, constexpr placement new, as well as constexpr exception throwing. Further support for constexpr standard library types was introduced.

A proposal for constexpr coroutines was published in 2025 by Hana Dusíková for adoption to C++26, but was delayed to C++29.

consteval

consteval is a function specifier that specifies the function to be an "immediate function", i.e. that every call of the function must produce a compile-time constant expression. In other words, the function may only be executed at compile time. If the compiler cannot evaluate the call at compile time, the program fails to compile. consteval is also used for if consteval blocks.

constinit

constinit is a variable specifier that guarantees static initialization, with static/global or thread-storage (thread_local) duration, and requires initialization with a constant expression. constinit produces a compilation error if used on a local variable or if it fails to initialize at compile-time. constinit does not imply immutability, and can thus be seen a mutable compile-time variable. constinit may not be used together with constexpr, but when the declared variable is a reference, it is equivalent to constexpr. constinit does not mandate constant destruction or const-qualification, allowing objects with constexpr constructors but non-constexpr destructor.

constinit is used to prevent the so-called "static initialization order fiasco", where global (or static) objects in different translation units are initialized in an unspecified order.

In other languages

Equivalents of constexpr in other languages include const in C# and Rust which declare a symbol to be compile-time. Rust additionally has const fn, equivalent to a constexpr function in C++. const in Go allows for compile-time constants, but not functions. Zig features a comptime keyword to force code to be executed at compile time.

A Java library, net.onedaybeard.constexpr, features an annotation @ConstExpr which simulates constexpr from C++ in Java. Meanwhile, a variable being declared with static final and of either primitive type or java.lang.String is considered a constant expression.

See also