[expr.const] (original) (raw)
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 calledconstant 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 non-volatile const-qualified integral or enumeration type.
A constant-initialized potentially-constant variable V isusable in constant expressions at a point P ifV'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 expressionunless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
- this ([expr.prim.this]), except
- in a constexpr function ([dcl.constexpr]) that is being evaluated as part of E or
- when appearing as the postfix-expression of an implicit or explicit class member access expression ([expr.ref]);
- a control flow that passes through a declaration of a block variable ([basic.scope.block]) with static ([basic.stc.static]) or thread ([basic.stc.thread]) storage duration, unless that variable is usable in constant expressions;
[Example 1: constexpr char test() { static const int x = 5;static constexpr char c[] = "Hello World";return *(c + x);} static_assert(' ' == test()); — _end example_] - an invocation of a non-constexpr function;68
- an invocation of an undefined constexpr function;
- an invocation of an instantiated constexpr function that is not constexpr-suitable;
- an invocation of a virtual function ([class.virtual]) for an object whose dynamic type is constexpr-unknown;
- an expression that would exceed the implementation-defined limits (see [implimits]);
- an operation that would have undefined or erroneous behavior as specified in [intro] through [cpp], excluding [dcl.attr.assume] and [dcl.attr.noreturn];69
- an lvalue-to-rvalue conversion unless it is applied to
- a non-volatile glvalue that refers to an object that is usable in constant expressions, or
- a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
- an lvalue-to-rvalue conversion that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;
- an lvalue-to-rvalue conversion that is applied to an object with an indeterminate value;
- an invocation of an implicitly-defined copy/move constructor or copy/move assignment operator for a union whose active member (if any) is mutable, unless the lifetime of the union object began within the evaluation of E;
- in a lambda-expression, a reference to this or to a variable with automatic storage duration defined outside thatlambda-expression, where the reference would be an odr-use ([basic.def.odr], [expr.prim.lambda]);
[Example 2: void g() { const int n = 0;[=] { constexpr int i = n; constexpr int j = *&n; };} — end example_]
[_Note 3:
If the odr-use occurs in an invocation of a function call operator of a closure type, it no longer refers to this or to an enclosing automatic variable due to the transformation ([expr.prim.lambda.capture]) of the id-expression into an access of the corresponding data member.
[Example 3: auto monad = [](auto v) { return [=] { return v; }; };auto bind = [](auto m) { return [=](auto fvm) { return fvm(m()); };};static_assert(bind(monad(2))(monad)() == monad(2)()); — _end example_]
— _end note_] - a conversion from a prvalue P of type “pointer to cv void” to a type “cv1 pointer to T”, where T is not cv2 void, unless Pis a null pointer value or points to an object whose type is similar to T;
- a reinterpret_cast ([expr.reinterpret.cast]);
- a modification of an object ([expr.ass], [expr.post.incr], [expr.pre.incr]) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
- an invocation of a destructor ([class.dtor]) or a function call whose postfix-expression names a pseudo-destructor ([expr.call]), in either case for an object whose lifetime did not begin within the evaluation of E;
- a new-expression ([expr.new]), unless either
- the selected allocation function is a replaceable global allocation function ([new.delete.single], [new.delete.array]) and the allocated storage is deallocated within the evaluation of E, or
- the selected allocation function is a non-allocating form ([new.delete.placement]) with an allocated type T, where
* the placement argument to the new-expression points to an object that is pointer-interconvertible with an object of type T or, if T is an array type, with the first element of an object of type T, and
* the placement argument points to storage whose duration began within the evaluation of E;
- a delete-expression ([expr.delete]), unless it deallocates a region of storage allocated within the evaluation of E;
- a call to an instance ofstd::allocator<T>::allocate ([allocator.members]), unless the allocated storage is deallocated within the evaluation of E;
- a call to an instance ofstd::allocator<T>::deallocate ([allocator.members]), unless it deallocates a region of storage allocated within the evaluation of E;
- an await-expression ([expr.await]);
- a yield-expression ([expr.yield]);
- a three-way comparison ([expr.spaceship]), relational ([expr.rel]), or equality ([expr.eq]) operator where the result is unspecified;
- a throw-expression ([expr.throw]);
- a dynamic_cast ([expr.dynamic.cast]) ortypeid ([expr.typeid]) expression on a glvalue that refers to an object whose dynamic type is constexpr-unknown or that would throw an exception;
- an asm-declaration ([dcl.asm]);
- an invocation of the va_arg macro ([cstdarg.syn]);
- a non-constant library call ([defns.nonconst.libcall]); or
- a goto statement ([stmt.goto]).
[Note 4:
A goto statement introduced by equivalence ([stmt.stmt]) is not in scope.
For example, a while statement ([stmt.while]) can be executed during constant evaluation.
— _end note_]
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 [exec],
- an invocation of the va_start macro ([cstdarg.syn]),
- a call to a function that was previously declared with the noreturn attribute ([dcl.attr.noreturn]) and that call returns to its caller, 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 5:
E is not disqualified from being a core constant expression if the hypothetical evaluation of the converted conditional-expressionwould disqualify E from being a core constant expression.
— _end note_]
[Example 4: int x; struct A { constexpr A(bool b) : m(b?42:x) { } int m;};constexpr int v = A(true).m; constexpr int w = A(false).m; constexpr int f1(int k) { constexpr int x = k; return x;} constexpr int f2(int k) { int x = k; return x;} constexpr int incr(int &n) { return ++n;} constexpr int g(int k) { constexpr int x = incr(k); return x;} constexpr int h(int k) { int x = incr(k); return x;} constexpr int y = h(1); — _end example_]
For the purposes of determining whether an expression E is a core constant expression, the evaluation of the body of a member function of std::allocator<T>as defined in [allocator.members], where T is a literal type, is ignored.
For the purposes of determining whether E is a core constant expression, the evaluation of a call to a trivial copy/move constructor or copy/move assignment operator of a union is considered to copy/move the active member of the union, if any.
[Note 6:
The copy/move of the active member is trivial.
— _end note_]
During the evaluation of an expression E as a core constant expression, all id-expressions and uses of *thisthat refer to an object or reference whose lifetime did not begin with the evaluation of Eare 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 5: 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); } constexpr auto olympic_mile() { const int ledecky = 1500;return []{ return ledecky; };} static_assert(olympic_mile()() == 1500); 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); static_assert((&swam)->phelps() == 28); Swim* pswam = &swam;static_assert(pswam->phelps() == 28); static_assert(how_many(swam) == 28); static_assert(Swim().lochte() == 12); static_assert(swam.lochte() == 12); static_assert(swam.coughlin == 12); } extern Swim dc;extern Swim& trident;constexpr auto& sandeno = typeid(dc); constexpr auto& gallagher = typeid(trident); — _end example_]
An object a is said to have constant destruction if
- it is not of class type nor (possibly multidimensional) array thereof, or
- it is of class type or (possibly multidimensional) 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 expressionis an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression.
[Note 7:
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 6: 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; struct B { int n : a; }; — _end example_]
A converted constant expressionof 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
- user-defined conversions,
- lvalue-to-rvalue conversions ([conv.lval]),
- array-to-pointer conversions ([conv.array]),
- function-to-pointer conversions ([conv.func]),
- qualification conversions ([conv.qual]),
- integral promotions ([conv.prom]),
- integral conversions ([conv.integral]) other than narrowing conversions ([dcl.init.list]),
- floating-point promotions ([conv.fpprom]),
- floating-point conversions ([conv.double]) where the source value can be represented exactly in the destination type,
- null pointer conversions ([conv.ptr]) from std::nullptr_t,
- null member pointer conversions ([conv.mem]) from std::nullptr_t, and
- function pointer conversions ([conv.fctptr]),
and where the reference binding (if any) binds directly.
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 an object of scalar type, it does not have an indeterminate or erroneous value ([basic.indet]),
- if the value is of pointer type, it is a pointer to an object with static storage duration, a pointer past the end of such an object ([expr.add]), a pointer to 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 apermitted result of a constant expressionif 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 9:
A glvalue core constant expression that either refers to or points to an unspecified object is not a constant expression.
— _end note_]
[Example 7: consteval int f() { return 42; } consteval auto g() { return f; } consteval int h(int (*p)() = g()) { return p(); } constexpr int r = h(); constexpr auto e = g(); struct S { int x;constexpr S() {} };int i() { constexpr S s; } — _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 10:
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 8: bool f() { char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; int size = 1 + int(1 + 0.2 - 0.1 - 0.1); 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 contextif it is potentially evaluated and either:
- its innermost enclosing non-block scope is a function parameter scope of an immediate function,
- it is a subexpression of a manifestly constant-evaluated expression or conversion, or
- its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).
An invocation is an immediate invocationif it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context.
An aggregate initialization is an immediate invocation if it evaluates a default member initializer that has a subexpression that is an immediate-escalating expression.
An expression or conversion is immediate-escalatingif it is not initially in an immediate function context and it is either
- a potentially-evaluated id-expressionthat denotes an immediate function that is not a subexpression of an immediate invocation, or
- an immediate invocation that is not a constant expression and is not a subexpression of an immediate invocation.
An immediate-escalating function is
- the call operator of a lambda that is not declared with the consteval specifier,
- a defaulted special member function that is not declared with the consteval specifier, or
- a function that results from the instantiation of a templated entity defined with the constexpr specifier.
An immediate-escalating expression shall appear only in an immediate-escalating function.
An immediate function is a function or constructor that is
- declared with the consteval specifier, or
- an immediate-escalating function _F_whose function body contains an immediate-escalating expression _E_such that E's innermost enclosing non-block scope is F's function parameter scope.
[Example 9: consteval int id(int i) { return i; } constexpr char id(char c) { return c; } template<class T> constexpr int f(T t) { return t + id(t);} auto a = &f<char>; auto b = &f<int>; static_assert(f(3) == 6); template<class T> constexpr int g(T t) { return t + id(42); } template<class T, class F> constexpr bool is_not(T t, F f) { return not f(t);} consteval bool is_even(int i) { return i % 2 == 0; } static_assert(is_not(5, is_even)); int x = 0;template<class T> constexpr T h(T t = id(x)) { return t;} template<class T> constexpr T hh() { return h<T>(); } int i = hh<int>(); struct A { int x;int y = id(x);};template<class T> constexpr int k(int) { return A(42).y; } — _end example_]
An expression or conversion is manifestly constant-evaluatedif 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]).70
[Example 10: template<bool> struct X {}; Xstd::is\_constant\_evaluated()\ x; int y;const int a = std::is_constant_evaluated() ? y : 1; double z[a]; const int b = std::is_constant_evaluated() ? 2 : y; int c = y + (std::is_constant_evaluated() ? 2 : y); constexpr int f() { const int n = std::is_constant_evaluated() ? 13 : 17; int m = std::is_constant_evaluated() ? 13 : 17; char arr[n] = {}; return m + sizeof(arr);} int p = f(); int q = p + f(); — _end example_]
A function or variable isneeded for constant evaluationif it is:
- a constexpr function thatis named byan expression that is potentially constant evaluated, or
- a potentially-constant variable named by a potentially constant evaluated expression.