Skip to content

[expr.const] Evaluation context of manifestly constant-evaluated expressions #848

@katzdm

Description

@katzdm

Full name of submitter: Dan Katz

Reference (section label): [expr.const]

Issue description:
P2996 introduced library functions that observe the translation state (e.g., is_complete_type, members_of over namespaces, is_enumerable_type, etc). Since the "point" at which constant evaluation occurs is not usually mandated by the Standard, the notion of the "evaluation context" ([expr.const]/30) was introduced to anchor the result of such functions to the lexical position of the core constant expression under evaluation. For instance, the result of a non-dependent manifestly constant-evaluated expression that checks the completeness of a type will not consider definitions of the type that follow lexically after that expression. This was the model understood and discussed by EWG and Core throughout P2996 review.

Now consider the following program:

struct S;
consteval size_t f() {
  constexpr bool b = is_complete_type(^^S);
  return b ? 1 : 2;
}

struct T;
consteval {
  define_aggregate(^^S, {});
  define_aggregate(^^T, {
    data_member_spec(^^int, {.name="m", .bit_width=f()}),
  });
}

int main() {
  return bit_size_of(^^T::m);
}

The Committee Draft arguably says that this program should exit with status 1: During the evaluation of the expression corresponding to the consteval-block, the evaluation of is_complete_type(^^S) contains the synthesized point associated with the injected declaration of S; therefore, the type is complete.

But this was never the intention of P2996; the intention was to evaluate manifestly constant-evaluated expressions at the point where they appear. The problem is that we didn't consider manifestly constant-evaluated expressions that might be executed (formally, per the abstract machine) during the evaluation of other manifestly constant-evaluated expressions. Ugh.

The solution is to define the "evaluation context" recursively: The evaluation context of an evaluation of a manifestly constant-evaluated expression should be independent of any "broader evaluation" through which the associated expression is being executed. To ease this recursion, I replace the ugly EVAL-PT metasyntactic function with a proper definition (i.e., "point of evaluation"), which I suspect might be useful in other sections.

Since this same section requires changes for CWG3127, the below changes also address that issue.

Suggested resolution:

Modify [expr.const]/30 as follows:

During an evaluation V ([intro.execution]) of an expression, conversion, or initialization E as a core constant expression, the point of evaluation of E during V is the program point P determined as follows:

  • If E is a potentially-evaluated subexpression ([intro.execution]) of a default member initializer I, and V is the evaluation of E in the evaluation of I as an immediate subexpression of a (possibly aggregate) initialization, then P is the point of evaluation of that initialization.
  • Otherwise, if E is a potentially-evaluated subexpression of a default argument A ([dcl.fct.default]), and V is the evaluation of E in the evaluation of A as an immediate subexpression of a function call ([expr.call], [intro.execution]), then P is the point of evaluation of that function call.
  • Otherwise, P is the point at which E appears.

The evaluation context evaluation context is a set of program points that determines the behavior of certain functions used for reflection ([meta.reflection]). During an evaluation V of an expression E as a core constant expression, the evaluation context of an evaluation X ([intro.execution]) consists of the following points during V is the set C of program points determined as follows:

  • If the execution of X occurs during the evaluation Y of a manifestly constant-evaluated expression, then C is the evaluation context of X during Y.
  • The program point _EVAL-PT(L), where L is the point at which E appears, and where EVAL-PT(P), for a point P, is a point R determined as follows:
    • If a potentially-evaluated subexpresison ([intro.execution]) of a default member initializer I appears at P, and a (possibly aggregate) initialization during V is using I, then R is EVAL-PT(Q) where Q is the point at which that initialization appears.
    • Otherwise, if a potentially-evaluated subexpression of a default argument ([dcl.fct.default]) apperas at P, and an invocation of a function ([expr.call]) during V is using that default argument, then R is EVAL-PT(Q) where Q is the point at which that invocation appears.
    • Otherwise, R is P.
  • Otherwise, C contains
    • the point of evaluation of E during V and each synthesized point in the instantiation context thereof, and
    • Each each synthesized point corresponding to an injected declaration produced by any evaluation executed during V that is sequenced before X ([intro.execution]).

cc @hubert-reinterpretcast

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions