GeezerVison is showing timsong-cpp.github.io — [expr.const.core] 7 Expressions [expr]7.7.2 Core constant expressions [expr.const.core]A type has constexpr-unknown representation if it
- is a union,
- is std::meta::info,
- is a pointer or pointer-to-member type,
- is volatile-qualified,
- is a class type with a non-static data member of reference type, or
- has a base class or a non-static member whose
type has constexpr-unknown representation.
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:
- this ([expr.prim.this]), except
- 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;⁶¹
- 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 Annex [implimits]);
- an operation that would have undefined or erroneous behavior
as specified in Clause [intro] through Clause [cpp];⁶²
- an arithmetic expression ([expr.pre]) where
- all operands are finite and the result is not finite,
- any operand is a signaling NaN, or
- no operand is NaN and the result is NaN;
- an lvalue-to-rvalue conversion unless
it is applied to
- a glvalue of type cv std::nullptr_t,
- 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 that
lambda-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; // OK, n is not odr-used here
constexpr int j = *&n; // error: &n would be an odr-use of n
};
}
— end example]
[ Note 1: 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
variable with automatic storage duration
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()); };
};
// OK to capture objects with automatic storage duration created during constant expression evaluation.
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 P
is a null pointer value or
points to an object whose type is similar to T;
- a reinterpret_cast ([expr.reinterpret.cast]);
- pointer arithmetic ([expr.add]) where
one (possibly converted) operand
points to the first element of an array of unknown bound and
the other (possibly converted) operand
is of integral type with non-zero value;
- a modification of an object ([expr.assign], [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 transparently replaceable ([basic.life]) by
the object created by the new-expression
or, if T is an array type,
to the first element of such an object, 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 of
std::allocator<T>::allocate ([allocator.members]),
unless the allocated storage is deallocated within the evaluation of E;
- a call to an instance of
std::allocator<T>::deallocate ([allocator.members]),
unless it deallocates a region of storage
allocated within the evaluation of E;
- a construction of an exception object,
unless the exception object and
all of its implicit copies created by invocations of
std::current_exception or std::rethrow_exception ([propagation])
are destroyed within the evaluation of E;
- a throw-expression ([expr.throw]) with no operand,
unless there is a currently handled exception
whose exception object was constructed 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;
- an equality operator comparing pointers to potentially non-unique objects,
if the pointer values of the two operands
are associated with different evaluations ([basic.compound]) and either
they can both point to the same offset within
the same potentially non-unique object or
one of them points to an object whose
type has constexpr-unknown representation;
[ Example 4: constexpr const char *f() { return "foo"; }
constexpr bool b1 = +"foo" == "foo"; // error: non-constant
constexpr bool b2 = f() == f(); // error: non-constant
constexpr const char *p = f();
constexpr bool b3 = p == p; // OK, value of b3 is true
constexpr bool b4 = "xfoo" + 1 == "foo\0y"; // error: non-constant; string literal
// object could contain "xfoo\0y"
constexpr bool b5 = "foo" == "bar" + 0; // OK, value of b5 is false
constexpr bool b6 = (const char*)"foo" == "oo"; // OK, value of b6 is false; offsets would
// be different in a merged string literal object
constexpr std::initializer_list<int *> il1 = { (int *)nullptr };
constexpr std::initializer_list<unsigned long> il2 = { 0 };
constexpr bool b7 = il1.begin() == (void *)il2.begin(); // error: non-constant
— end example] - a dynamic_cast ([expr.dynamic.cast]) or
typeid ([expr.typeid]) expression
on a glvalue that refers to an object
whose dynamic type is constexpr-unknown;
- a dynamic_cast ([expr.dynamic.cast]) expression,
typeid ([expr.typeid]) expression, or
new-expression ([expr.new])
that would throw an exception
where no definition of the exception type is reachable;
- an expression that would produce an injected declaration (see below),
unless E is the corresponding expression of
a consteval-block-declaration ([dcl.pre]);
- 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 2: A goto statement introduced by equivalence ( Clause [stmt])
is not in scope . For example, a while statement ( [stmt.while])
can be executed during constant evaluation . — end note]
It is
implementation-defined
whether E is a core constant expression
if E satisfies the constraints of a core constant expression, but
evaluation of E has runtime-undefined behavior .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
[ Example 5: 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 the body of a member function of std::allocator<T>
as defined in [allocator.members], 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 3: The copy/move of the active member is trivial . — end note] For the purposes of determining whether E is a core constant expression,
the evaluation of an id-expression
that names a structured binding v ( [dcl.struct.bind]) has the
following semantics:
If v is an lvalue referring to the object bound to an invented reference r,
the behavior is as if r were nominated .Otherwise, if v names an array element or class member,
the behavior is that of
evaluating e[i] or e.m, respectively,
where e is the name of the variable
initialized from the initializer of the structured binding declaration, and
i is the index of the element referred to by v or
m is the name of the member referred to by v, respectively .
[ Example 6: #include <tuple>
struct S {
mutable int m;
constexpr S(int m): m(m) {}
virtual int g() const;
};
void f(std::tuple<S&> t) {
auto [r] = t;
static_assert(r.g() >= 0); // error: dynamic type is constexpr-unknown
constexpr auto [m] = S(1);
static_assert(m == 1); // error: lvalue-to-rvalue conversion on mutable
// subobject e.m, where e is a constexpr object of type S
using A = int[2];
constexpr auto [v0, v1] = A{2, 3};
static_assert(v0 + v1 == 5); // OK, equivalent to e[0] + e[1] where e is a constexpr array
}
— end example] During the evaluation of an expression E as a core constant expression,
all id-expressions, splice-expressions, and
uses of *this
that refer to an object or reference
whose lifetime did not begin within 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 7: 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] |