close
Jump to content

constexpr

From Wikipedia, the free encyclopedia

constexpr is a specifier keyword in the C and C++ programming languages which, roughly speaking, specifies that something may be evaluated at compile time.[1] 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,[2] constexpr may be used on objects, functions, or structured bindings in C++.[1]

Additional keywords in the C++ family of "const" keywords include consteval[3], 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)[4].

Background

[edit]

The word constexpr is a portmanteau of constant expression[5], 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.[6]

Initial proposals for generalised constant expressions and compile-time expressions were first proposed for inclusion into C++ by Gabriel Dos Reis in 2003[7], 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.[6] 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.[6], and adopted into the draft for C++11 in July 2007.[8] Herb Sutter, chairman of the C++ committee, in 2026 described the adoption of constexpr as one of the most 'momentous' polls of C++ history.[9]

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

constexpr was later introduced into C, beginning in C23.[2][13]

Semantics

[edit]

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[5] without relying on preprocessor directives, as well as preventing certain cases of undefined behavior.[14]

Variables

[edit]

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.[15] 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.[5]

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[16], which may bind to aggregate types (i.e. structs), arrays, and pairs/tuples.[17]

struct Point {
    int x;
    int y;
};

constexpr int NUMS[3] = 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;

Pointers and references

[edit]

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).[17]

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

[edit]

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.[5] A constexpr function evaluated at compile time will halt compilation once undefined behavior is invoked.[14] constexpr functions are implicitly inline.[1]

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.[1][18]

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

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.[5] Prior to C++20, virtual functions could not be constexpr.[1][20]

Constructors and destructors

[edit]

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.[19] 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.[21] The implicitly generated constructor is guaranteed to be constexpr if the class is a literal type.[22][23]

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.[19] 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.[24] This now allows for non-trivial allocations and destructions in compile-time contexts.[25] The implicitly generated destructor is guaranteed to be constexpr if the class is a literal type.[26] 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.[27]

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.[28]

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

[edit]

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

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.[29] 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.[30] return statements in a discarded statement do not participate in function return type deduction.

import std;

using std::is_pointer_v;

// The deduced return type can be spelled out in full as
// conditional_t<is_pointer_v<T>, remove_pointer_t<T>, T>
template <typename T>
constexpr auto getValue(T t) noexcept {
    if constexpr (is_pointer_v<T>) {
        return *t;
    } else {
        return t;
    }
}

int main() {
    int* numbers = new int[5]{1, 2, 3, 4, 5};
    int firstNumber = getValue(numbers);
    int five = getValue(5); 

    delete a;
}

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.[31]

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 */ }.[29]

Evolution

[edit]

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

[edit]

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

C++14

[edit]

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

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

constexpr bool isPrime(uint64_t number) noexcept {
    if (number < 2) {
        return false;
    } else if (number == 2 || number == 3) {
        return true;
    } else if (number % 2 == 0 || number % 3 == 0)
        return false;
    }
    for (uint64_t i = 5; i * i <= number; i += 6) {
        if (number % i == 0 || number % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}

static_assert(isPrime(264223787557));

C++17

[edit]

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

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

constexpr int FACTOR = 10;

constexpr auto scaleBy = [FACTOR](int x) -> int {
    return x * FACTOR;
};

static_assert(scaleBy(4) == 40);

C++20

[edit]

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

C++20 lifted the restriction on constexpr virtual methods.[1][20]

class Base {
public:
    virtual constexpr size_t f() const {
        return sizeof(int);
    }

    constexpr virtual ~Base() = default;
};

class MyIntArray : public Base {
private:
    int* a;
    const size_t length;
public:
    constexpr size_t f() const override {
        return length;
    }

    constexpr MyIntArray(size_t n):
        a{new int[n]}, length{n} {}

    constexpr ~MyIntArray() override {
        delete[] a;
    }

    constexpr int& operator[](size_t i) {
        return a[i];
    }

    constexpr const int& operator[](size_t i) const {
        return a[i];
    }

    MyIntArray(const MyIntArray&) = delete;
    MyIntArray& operator=(const MyIntArray&) = delete;
};

C++23

[edit]

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

C++26

[edit]

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

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

consteval

[edit]

consteval is a function specifier that specifies the function to be an "immediate function"[10], 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.[3] If the compiler cannot evaluate the call at compile time, the program fails to compile.[40] consteval is also used for if consteval blocks.[29]

import std;

using std::array;

consteval int square(int x) {
    return x * x;
}

template <size_t N>
consteval array<int, N> squaresUpTo() {
    array<int, N> squares;
    for (size_t i = 0; i < N; ++i) {
        squares[i] = square(i);
    }
    return squares;
}

int main() {
    constexpr int a = square(5); // OK: evaluated at compile time

    // OK: evaluated at compile time because square() is consteval
    int b = square(5);
    constexpr array<int, 5> table = squaresUpTo<5>();
    int x = 3;
    // NOT OK: x is not a constant expression
    // int y = square(x);
}

constinit

[edit]

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.[4] constinit does not imply immutability, and can thus be seen a mutable compile-time variable.[40] 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.

const char* g() {
    return "Dynamic initialization";
}

constexpr const char* f(bool b) {
    return b ? "Constant initializer" : g();
}

// OK: initialized at compile-time
constinit const char* s1 = f(true);

// NOT OK: g() not constant expression
// constinit const char* s2 = f(false);

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.[11]

In other languages

[edit]

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

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

See also

[edit]

References

[edit]
  1. ^ a b c d e f g h i j cppreference.com. "constexpr specifier (since C++11)". cppreference.com. cppreference.com. Retrieved 14 April 2026.
  2. ^ a b cppreference.com. "constexpr specifier (since C23)". cppreference.com. cppreference.com. Retrieved 14 April 2026.
  3. ^ a b "consteval specifier (since C++20)". cppreference.com. cppreference.com. Retrieved 3 May 2026.
  4. ^ a b "constinit specifier (since C++20)". cppreference.com. cppreference.com. Retrieved 3 May 2026.
  5. ^ a b c d e f Microsoft Learn (22 February 2023). "constexpr (C++)". learn.microsoft.com. Microsoft Learn.
  6. ^ a b c Gabriel Dos Reis, Bjarne Stroustrup, Jens Maurer (3 November 2006). "Generalized Constant Expressions - Revision 5" (PDF). open-std.org.{{cite web}}: CS1 maint: multiple names: authors list (link)
  7. ^ Gabriel Dos Reis (21 September 2003). "Generalized Constant Expressions" (PDF). open-std.org. WG21.
  8. ^ Herb Sutter (21 June 2025). "Trip report: June 2025 ISO C++ standards meeting (Sofia, Bulgaria)". herbsutter.com. Herb Sutter.
  9. ^ Sutter, Herb (21 June 2025). "Trip report: June 2025 ISO C++ standards meeting (Sofia, Bulgaria)". herbsutter.com. herbsutter.com. Retrieved 10 April 2026. Until today, perhaps the most momentous single feature poll of C++'s history was the poll in Toronto in July 2007 to adopt Bjarne Stroustrup's and Gabriel Dos Reis' first "constexpr" paper into draft C++11. Looking back now, we can see what a tectonic shift that started for C++.
  10. ^ a b Richard Smith, Andrew Sutton, Daveed Vandevoorde (4 October 2018). "Immediate functions". open-std.org. WG21.{{cite web}}: CS1 maint: multiple names: authors list (link)
  11. ^ a b Eric Fiselier (18 July 2019). "Adding the constinit keyword". open-std.org. WG21.
  12. ^ Barry Rezvin, Peter Dimov (13 March 2025). "Consteval-only Values and Consteval Variables". open-std.org. WG21.
  13. ^ Alex Gilding, Jens Gustedt (6 July 2022). "The constexpr specifier for object definitions". open-std.org. WG14.
  14. ^ a b WG21. "7 - Expressions, 7.7 - Constant expressions". eel.is. WG21. Retrieved 14 April 2026.{{cite web}}: CS1 maint: numeric names: authors list (link)
  15. ^ cppreference.com. "Constant expressions - Constant initialized entities". cppreference.com. cppreference.com. Retrieved 14 April 2026.
  16. ^ cppreference.com. "Structured binding declaration (since C++17)". cppreference.com. cppreference.com. Retrieved 11 May 2026.
  17. ^ a b c Jabot, Corentin; Bi, Brian (12 November 2024). "constexpr structured bindings and references to constexpr variables" (PDF). open-std.org. open-std.org. Retrieved 10 May 2026.
  18. ^ Richard Smith (15 March 2013). "Relaxing constraints on constexpr functions". WG21.
  19. ^ a b c d e f "constexpr specifier (since C++11)". cppreference.com. cppreference.com. 2026. Retrieved 25 April 2026.
  20. ^ a b c Peter Dimov, Vassil Vassilev (4 May 2018). "Allowing Virtual Functions Calls in Constant Expressions". open-std.org. WG21.
  21. ^ a b Hana Dusíková (17 February 2025). "constexpr virtual inheritance". open-std.org. WG21.
  22. ^ cppreference.com. "Default constructors". cppreference.com. cppreference.com. Retrieved 14 April 2026.
  23. ^ cppreference.com (14 April 2026). "Constant expressions - Literal type". cppreference.com. cppreference.com.
  24. ^ Wyatt Childers; et al. (26 June 2024). "P2996R4 - Reflection for C++26". isocpp.org. WG21.
  25. ^ a b c d Peter Dimov (19 July 2019). "More constexpr containers". open-std.org. WG21.
  26. ^ cppreference.com. "Destructors". cppreference.com. cppreference.com. Retrieved 14 April 2026.
  27. ^ a b Hana Dusíková (11 February 2025). "constexpr containers and adaptors". open-std.org. WG21.
  28. ^ WG21 (23 April 2026). "Draft C++ Standard - Declarations and definitions". eel.is. WG21. In the definition of an object, the type of that object shall not be an incomplete type ([basic.types.general]), an abstract class type ([class.abstract]), or a (possibly multidimensional) array thereof.{{cite web}}: CS1 maint: numeric names: authors list (link)
  29. ^ a b c d e f cppreference.com. "if statement". cppreference.com. cppreference.com. Retrieved 14 April 2026.
  30. ^ Jens Maurer (20 June 2016). "P0292R2: constexpr if: A slightly different syntax". open-std.org. WG21.
  31. ^ Barry Revzin, Richard Smith, Andrew Sutton, Daveed Vandevoorde (22 March 2021). "if consteval". open-std.org. WG21.{{cite web}}: CS1 maint: multiple names: authors list (link)
  32. ^ Lawrence Crowl (5 February 2009). "Issues with Constexpr". open-std.org. WG21. Retrieved 15 April 2026.
  33. ^ Stroustrup, Bjarne (14 February 2010). "Constexpr functions with const reference parameters (a summary)" (PDF). open-std.org. WG21. Retrieved 15 April 2026.
  34. ^ Merill, Jason (25 March 2011). "static_assert and list-initialization in constexpr functions". open-std.org. WG21. Retrieved 15 April 2026.
  35. ^ Richard Smith (15 March 2013). "Relaxing constraints on constexpr functions". open-std.org. WG21.
  36. ^ Corentin Jabot, David Ledger (13 February 2023). "constexpr cast from void*: towards constexpr type-erasure" (PDF). open-std.org. WG21.
  37. ^ Barry Rezvin (19 March 2024). "constexpr placement new". open-std.org. WG21.
  38. ^ Hana Dusíková (19 November 2024). "Allowing exception throwing in constant-evaluation". open-std.org. WG21.
  39. ^ Hana Dusíková (17 February 2025). "constexpr coroutines". open-std.org. WG21.
  40. ^ a b "const vs constexpr vs consteval vs constinit in C++20". cppstories.com. 28 November 2022. Retrieved 3 May 2026.
  41. ^ Microsoft Learn (26 January 2026). "The const keyword". learn.microsoft.com. Microsoft Learn.
  42. ^ Rust Language Foundation (25 March 2026). "Keyword const". doc.rust-lang.org. Rust Language Foundation.
  43. ^ Rust Language Foundation (30 April 2026). "The Rust Reference - Constant evaluation". doc-rust.lang.org. Rust Language Foundation.
  44. ^ Rob Pike (25 August 2014). "Constants". go.dev. The Go Blog.
  45. ^ zig.guide (4 January 2026). "Comptime". zig.guide. Zig Software Foundation.
  46. ^ Adrian Papari. "junkdog/constexpr-java". github.com. junkdog. Retrieved 14 April 2026.
  47. ^ Oracle Corporation. "Chapter 15. Expressions - 15.28. Constant Expressions". docs.oracle.com. Java Language Specification. Retrieved 14 April 2026.