consteval
blocksDocument #: | D3289R2 [Latest] [Status] |
Date: | 2025-01-15 |
Project: | Programming Language C++ |
Audience: |
CWG |
Reply-to: |
Wyatt Childers <wcc@edg.com> Dan Katz <dkatz85@bloomberg.net> Barry Revzin <barry.revzin@gmail.com> Daveed Vandevoorde <daveed@edg.com> |
Since [P3289R1]:
Since [P3289R0]:
static_assert
Several proposals that produce side effects as part of constant evaluation are in flight. That includes [P2996R9] and [P2758R3]. Such a capability, in turn, quickly gives rise to the desire to evaluate such constant expressions in declarative contexts.
Currently, this effect can be shoe-horned into static_assert
declarations, but the result looks arcane. For example, P2996 contains
the following code in an example:
#include <meta> template<typename... Ts> struct Tuple { struct storage; static_assert( (define_class(^storage, {data_member_spec(^Ts)...}))); is_type storage data; (): data{} {} Tuple(Ts const& ...vs): data{ vs... } {} Tuple};
Here, define_class(...)
is a constant expression with a side-effect that we want to evaluate
before parsing the member declaration that follows. It works, but it is
somewhat misleading: We’re not really trying to assert anything; we just
want to force that evaluation.
We therefore propose a simple, intuitive syntax to express that we
simply want to constant-evaluate a bit of code wherever a static_assert
declaration could appear by enclosing that code in consteval { ... }
(a construct we’ll call a
consteval
block).
Formally, we propose that a construct of the form
consteval { statement-seqopt}
is equivalent to:
static_assert( ( []() -> void consteval { statement-seqopt}(), true ) );
The static_assert
condition is a comma-expression with the first operand an
immediately-invoked
consteval
lambda.
Note that this allows a plain
return;
statement or even a return f();
statement where
f()
has type
`void. We could go out of our way to disallow that, but we cannot find
any benefit in doing so.
With the feature as proposed, the example above becomes:
Status Quo
|
Proposed
|
---|---|
|
|
In this example, there is just a single expression statement being evaluated. However, we are anticipating reflection code where more complex statement sequences will be used (you can see some examples in previous papers, e.g. [P1717R0] [P2237R0]).
We did consider other syntax variations such as
@eval expression;
@ expression
;@ statement
;but found those alternatives less general and not worth the slight improvement in brevity.
Implemented in both EDG and Clang.
[ Editor's note: The
simplest way to do the wording is to add a
consteval
block as a
kind of
static_assert-declaration
.
That’s the minimal diff. However, it’s kind of weird to say that a
consteval
block literally is a
static_assert
- plus we need to
give specific evaluation guarantees to a
consteval
block (“plainly
constant-evaluated”), so we’d rather take a few more words to get
somewhere that feels more sensible. Plus this change reduces a lot of
duplication between
empty-declaration
and
static_assert-declaration
,
which are treated the same in a lot of places anyway. ]
Change 6.2 [basic.def]/2:
2 Each entity declared by a
declaration
is also defined by thatdeclaration
unless:
Extend the wording for plainly constant-evaluated to allow a
consteval
block [ Drafting note:
this is adjusting wording that is added by [D2996R10?]
]:
21pre A non-dependent expression or conversion is plainly constant-evaluated if
- (21.1) it is the evaluating expression of a
consteval-block-declaration
(9.1 [dcl.pre]), or- (21.2) it is an initializer of a
constexpr
(9.2.6 [dcl.constexpr]) orconstinit
(9.2.7 [dcl.constinit]) variable that is not in a specialization of a variable template.[ Note 1: The evaluation of a plainly constant-evaluated expression
E
can produce injected declarations (see below). Any such declarations are reachable from a point that follows immediately afterE
. — end note ]
Change 9.1 [dcl.pre]:
name-declaration: block-declaration nodeclspec-function-declaration function-definition friend-type-declaration template-declaration deduction-guide linkage-specification namespace-definition- empty-declaration attribute-declaration module-import-declaration block-declaration: simple-declaration asm-declaration namespace-alias-definition using-declaration using-enum-declaration using-directive- static_assert-declaration alias-declaration opaque-enum-declaration+ vacant-declaration + vacant-declaration: + static_assert-declaration + empty-declaration + consteval-block-declaration static_assert-declaration: static_assert ( constant-expression ) ; static_assert ( constant-expression , static_assert-message ) ; + consteval-block-declaration: + consteval compound-statement
And then after 9.1 [dcl.pre]/13:
13 Recommended practice: When a
static_assert-declaration
fails, […]* The evaluating expression of a
consteval-block-declaration
is[] -> void consteval compound-statement ()
* The evaluating expression of a
consteval-block-declaration
shall be a constant expression ([expr.const]).[ Note 2: A non-dependent evaluating expression of a
consteval-block-declaration
is plainly constant-evaluated ([expr.const]). — end note ]14 An
empty-declaration
has no effect.
Adjust the grammar in 11.4.1 [class.mem.general] and the rule in p3:
member-declaration: attribute-specifier-seqopt decl-specifier-seqopt member-declarator-listopt; function-definition friend-type-declaration using-declaration using-enum-declaration- static_assert-declaration + vacant-declaration template-declaration explicit-specialization deduction-guide alias-declaration opaque-enum-declaration- empty-declaration
3 A
member-declaration
does not declare new members of the class if it is
And similar in 11.5.2 [class.union.anon]/1.
[ Editor's note: This
refactor allows putting in an
empty-declaration
into
an anonymous union, which is kind of a consistency drive by with other
classes. ]
1 […] Each
member-declaration
in themember-specification
of an anonymous union shall either define one or more public non-static data members or be astatic_assert-declaration
vacant-declaration
. […]
Add to the table in 15.11 [cpp.predefined]:
__cpp_consteval 202211L+ __cpp_consteval_block 2025XXL __cpp_constinit 201907L