7 Expressions [expr]

7.7 Constant expressions [expr.const]

Certain contexts require expressions that satisfy additional requirements as detailed in this subclause; other contexts have different semantics depending on whether or not an expression satisfies these requirements.
Expressions that satisfy these requirements, assuming that copy elision is not performed, are called constant expressions.
[Note 1:
Constant expressions can be evaluated during translation.
— end note]
A variable or temporary object o is constant-initialized if
  • either it has an initializer or its default-initialization results in some initialization being performed, and
  • the full-expression of its initialization is a constant expression when interpreted as a constant-expression, except that if o is an object, that full-expression may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.
    [Note 2:
    Such a class can have a non-trivial destructor.
    Within this evaluation, std​::​is_­constant_­evaluated() ([meta.const.eval]) returns true.
    — end note]
A variable is potentially-constant if it is constexpr or it has reference or const-qualified integral or enumeration type.
A constant-initialized potentially-constant variable V is usable in constant expressions at a point P if V's initializing declaration D is reachable from P and
  • V is constexpr,
  • V is not initialized to a TU-local value, or
  • P is in the same translation unit as D.
An object or reference is usable in constant expressions if it is
  • a variable that is usable in constant expressions, or
  • a template parameter object, or
  • a string literal object, or
  • a temporary object of non-volatile const-qualified literal type whose lifetime is extended ([class.temporary]) to that of a variable that is usable in constant expressions, or
  • a non-mutable subobject or reference member of any of the above.
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate
  • an operation that has undefined behavior as specified in [library] through [thread],
  • an invocation of the va_­start macro ([cstdarg.syn]), or
  • a statement with an assumption ([dcl.attr.assume]) whose converted conditional-expression, if evaluated where the assumption appears, would not disqualify E from being a core constant expression and would not evaluate to true.
    [Note 4:
    E is not disqualified from being a core constant expression if the hypothetical evaluation of the converted conditional-expression would disqualify E from being a core constant expression.
    — end note]
[Example 3: int x; // not constant struct A { constexpr A(bool b) : m(b?42:x) { } int m; }; constexpr int v = A(true).m; // OK, constructor call initializes m with the value 42 constexpr int w = A(false).m; // error: initializer for m is x, which is non-constant constexpr int f1(int k) { constexpr int x = k; // error: x is not initialized by a constant expression // because lifetime of k began outside the initializer of x return x; } constexpr int f2(int k) { int x = k; // OK, not required to be a constant expression // because x is not constexpr return x; } constexpr int incr(int &n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // error: incr(k) is not a core constant expression // because lifetime of k began outside the expression incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK, incr(k) is not required to be a core constant expression return x; } constexpr int y = h(1); // OK, initializes y with the value 2 // h(1) is a core constant expression because // the lifetime of k begins inside h(1) — end example]
For the purposes of determining whether an expression E is a core constant expression, the evaluation of a call to a member function of std​::​allocator<T> as defined in [allocator.members], where T is a literal type, does not disqualify E from being a core constant expression, even if the actual evaluation of such a call would otherwise fail the requirements for a core constant expression.
Similarly, the evaluation of a call to std​::​construct_­at or std​::​ranges​::​construct_­at ([specialized.construct]) does not disqualify E from being a core constant expression unless the first argument, of type T*, does not point to storage allocated with std​::​allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation of the underlying constructor call disqualifies E from being a core constant expression.
During the evaluation of an expression E as a core constant expression, all id-expressions and uses of *this that refer to an object or reference whose lifetime did not begin with the evaluation of E are treated as referring to a specific instance of that object or reference whose lifetime and that of all subobjects (including all union members) includes the entire constant evaluation.
For such an object that is not usable in constant expressions, the dynamic type of the object is constexpr-unknown.
For such a reference that is not usable in constant expressions, the reference is treated as binding to an unspecified object of the referenced type whose lifetime and that of all subobjects includes the entire constant evaluation and whose dynamic type is constexpr-unknown.
[Example 4: template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) { return N; } void use_array(int const (&gold_medal_mel)[2]) { constexpr auto gold = array_size(gold_medal_mel); // OK } constexpr auto olympic_mile() { const int ledecky = 1500; return []{ return ledecky; }; } static_assert(olympic_mile()() == 1500); // OK struct Swim { constexpr int phelps() { return 28; } virtual constexpr int lochte() { return 12; } int coughlin = 12; }; constexpr int how_many(Swim& swam) { Swim* p = &swam; return (p + 1 - 1)->phelps(); } void splash(Swim& swam) { static_assert(swam.phelps() == 28); // OK static_assert((&swam)->phelps() == 28); // OK Swim* pswam = &swam; static_assert(pswam->phelps() == 28); // error: lvalue-to-rvalue conversion on a pointer // not usable in constant expressions static_assert(how_many(swam) == 28); // OK static_assert(Swim().lochte() == 12); // OK static_assert(swam.lochte() == 12); // error: invoking virtual function on reference // with constexpr-unknown dynamic type static_assert(swam.coughlin == 12); // error: lvalue-to-rvalue conversion on an object // not usable in constant expressions } extern Swim dc; extern Swim& trident; constexpr auto& sandeno = typeid(dc); // OK, can only be typeid(Swim) constexpr auto& gallagher = typeid(trident); // error: constexpr-unknown dynamic type — end example]
An object a is said to have constant destruction if:
  • it is not of class type nor (possibly multi-dimensional) array thereof, or
  • it is of class type or (possibly multi-dimensional) array thereof, that class type has a constexpr destructor, and for a hypothetical expression E whose only effect is to destroy a, E would be a core constant expression if the lifetime of a and its non-mutable subobjects (but not its mutable subobjects) were considered to start within E.
An integral constant expression is an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression.
[Note 5:
Such expressions can be used as bit-field lengths ([class.bit]), as enumerator initializers if the underlying type is not fixed ([dcl.enum]), and as alignments.
— end note]
If an expression of literal class type is used in a context where an integral constant expression is required, then that expression is contextually implicitly converted ([conv]) to an integral or unscoped enumeration type and the selected conversion function shall be constexpr.
[Example 5: struct A { constexpr A(int i) : val(i) { } constexpr operator int() const { return val; } constexpr operator long() const { return 42; } private: int val; }; constexpr A a = alignof(int); alignas(a) int n; // error: ambiguous conversion struct B { int n : a; }; // error: ambiguous conversion — end example]
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only and where the reference binding (if any) binds directly.
[Note 6:
Such expressions can be used in new expressions ([expr.new]), as case expressions ([stmt.switch]), as enumerator initializers if the underlying type is fixed, as array bounds, and as non-type template arguments.
— end note]
A contextually converted constant expression of type bool is an expression, contextually converted to bool ([conv]), where the converted expression is a constant expression and the conversion sequence contains only the conversions above.
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:
  • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
  • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object ([expr.add]), the address of a non-immediate function, or a null pointer value,
  • if the value is of pointer-to-member-function type, it does not designate an immediate function, and
  • if the value is an object of class or array type, each subobject satisfies these constraints for the value.
An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.
[Note 7:
A glvalue core constant expression that either refers to or points to an unspecified object is not a constant expression.
— end note]
[Example 6: consteval int f() { return 42; } consteval auto g() { return f; } consteval int h(int (*p)() = g()) { return p(); } constexpr int r = h(); // OK constexpr auto e = g(); // error: a pointer to an immediate function is // not a permitted result of a constant expression — end example]
Recommended practice: Implementations should provide consistent results of floating-point evaluations, irrespective of whether the evaluation is performed during translation or during program execution.
[Note 8:
Since this document imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution.
[Example 7: bool f() { char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; // Must be evaluated during translation int size = 1 + int(1 + 0.2 - 0.1 - 0.1); // May be evaluated at runtime return sizeof(array) == size; }
It is unspecified whether the value of f() will be true or false.
— end example]
— end note]
An expression or conversion is in an immediate function context if it is potentially evaluated and either:
An expression or conversion is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context.
An immediate invocation shall be a constant expression.
An expression or conversion is manifestly constant-evaluated if it is:
  • a constant-expression, or
  • the condition of a constexpr if statement ([stmt.if]), or
  • an immediate invocation, or
  • the result of substitution into an atomic constraint expression to determine whether it is satisfied ([temp.constr.atomic]), or
  • the initializer of a variable that is usable in constant expressions or has constant initialization ([basic.start.static]).75
    [Example 8: template<bool> struct X {}; X<std::is_constant_evaluated()> x; // type X<true> int y; const int a = std::is_constant_evaluated() ? y : 1; // dynamic initialization to 1 double z[a]; // error: a is not usable // in constant expressions const int b = std::is_constant_evaluated() ? 2 : y; // static initialization to 2 int c = y + (std::is_constant_evaluated() ? 2 : y); // dynamic initialization to y+y constexpr int f() { const int n = std::is_constant_evaluated() ? 13 : 17; // n is 13 int m = std::is_constant_evaluated() ? 13 : 17; // m can be 13 or 17 (see below) char arr[n] = {}; // char[13] return m + sizeof(arr); } int p = f(); // m is 13; initialized to 26 int q = p + f(); // m is 17 for this call; initialized to 56 — end example]
[Note 9:
A manifestly constant-evaluated expression is evaluated even in an unevaluated operand.
— end note]
An expression or conversion is potentially constant evaluated if it is:
A function or variable is needed for constant evaluation if it is:
  • a constexpr function that is named by an expression that is potentially constant evaluated, or
  • a variable named by a potentially constant evaluated expression that is either a constexpr variable or is of non-volatile const-qualified integral type or of reference type.
73)73)
Overload resolution ([over.match]) is applied as usual.
74)74)
This includes, for example, signed integer overflow ([expr.pre]), certain pointer arithmetic ([expr.add]), division by zero ([expr.mul]), or certain shift operations ([expr.shift]).
75)75)
Testing this condition can involve a trial evaluation of its initializer as described above.
76)76)
In some cases, constant evaluation is needed to determine whether a narrowing conversion is performed ([dcl.init.list]).
77)77)
In some cases, constant evaluation is needed to determine whether such an expression is value-dependent ([temp.dep.constexpr]).