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; this was later added in C++29.
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.
import std;
using std::array;
// Declare compile-time constants constexpr double EARTH_GRAVITATIONAL_ACCELERATION = 9.8; constexpr double MOON_GRAVITATIONAL_ACCELERATION = EARTH_GRAVITATIONAL_ACCELERATION / 6.0; constexpr size_t MAX_LENGTH = 500;
// Creates an array of ints of length 500, using compile-time constant MAX_LENGTH array<int, MAX_LENGTH> numbers;
C++26 allowed constexpr structured bindings, which may bind to aggregate types (i.e. structs), arrays, and pairs/tuples. struct Point { int x; int y;
constexpr int NUMS = {1, 2, 3};
// a = 1, b = 5 constexpr auto [a, b] = Point{.x = 1, .y = 5}; // id = 3, name = "John Doe", score = 95 constexpr auto [id, name, score] = std::make_tuple(3, "John Doe", 95); // x = 1, y = 2, z = 3 constexpr auto [x, y, z] = NUMS;
The reflection object std::meta::info, which behaves as a mirror object, may exist only as a compile-time (i.e. constexpr) object. While it may exist as constinit, this is often useless as without immutability the compiler cannot splice the object.
import std;
using std::string; using std::meta::info;
struct User { string name; int age;
int main() { // reflect the User class's type metadata constexpr info clazz = ^^User;
// splice [:clazz:] into User typename [:clazz:] john { .name = "John Doe", .age = 20
std::println("The class of {} is {}", john.name, std::meta::identifier_of(clazz)); }
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. import std;
int main() { int x = 3; static float val = 3.0f;
// Error (automatic storage duration) // constexpr int* ptr = &x;
// OK (static storage duration) constexpr float* address = &val; *address = 4.5f;
std::println("{}", val); // prints 4.5 }
Meanwhile, constexpr references bind only either to variables of static storage duration or constexpr variables (regardless of storage duration). import std;
int main() { static char ch = 'A'; constexpr char& ref1 = ch; ref1 = 'c'; std::println("{}", ref1); // prints 'c'
constexpr int num = 100; constexpr int& ref2 = num;
std::println("{}", ref2 - 2); // prints 98 }
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. constexpr double div(double a, double b) noexcept { return a / b; }
constexpr double x = div(1, 0); // Prevents compilation, due to invoking undefined behavior
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.
constexpr int getFive() noexcept { return 5; }
int numbers[getFive() + 7]; // Creates an array of 12 ints
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. struct Color { uint8_t r; uint8_t g; uint8_t b;
constexpr Color(uint8_t r, uint8_t g, uint8_t b) noexcept: r{r}, g{g}, b{b} {}
constexpr ~Color() = default;
static const Color BLACK; static const Color RED; static const Color GREEN; static const Color YELLOW; static const Color BLUE; static const Color MAGENTA; static const Color CYAN; static const Color WHITE;
inline constexpr Color Color::BLACK = Color(0, 0, 0); inline constexpr Color Color::RED = Color(255, 0, 0); inline constexpr Color Color::GREEN = Color(0, 255, 0); inline constexpr Color Color::YELLOW = Color(255, 255, 0); inline constexpr Color Color::BLUE = Color(0, 0, 255); inline constexpr Color Color::MAGENTA = Color(255, 0, 255); inline constexpr Color Color::CYAN = Color(0, 255, 255); inline constexpr Color Color::WHITE = Color(255, 255, 255);
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.
import std;
using std::conditional_t; using std::is_pointer_v; using std::remove_pointer_t;
template <typename T> using Extracted = conditional_t<is_pointer_v<T>, remove_pointer_t<T>, T>;
template <typename T> constexpr Extracted<T> getValue(T t) noexcept { if constexpr (is_pointer_v<T>) { return *t; } else { return t;
int main() { int* numbers = new int{1, 2, 3, 4, 5}; int firstNumber = getValue(numbers); int five = getValue(5);
delete numbers; }
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.
template <typename T> constexpr T* arrayOfLength(size_t n) { if consteval { // Array may not leave scope of a compile-time expression return nullptr; } else { // At runtime, allow returning a pointer to a heap-allocated array return new T[n];
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