All subclause and paragraph references here are to the C2x CD, SC22 N5777, except where explicitly referenced to an old standard version.
Consider the following four provisions in the document that impose restrictions on what can occur within certain kinds of declarations, or require certain kinds of declarations to contain certain kinds of constructs or certain kinds of constructs to appear in certain declaration contexts.
Case 1: minimum contents of declarations: 6.7 paragraph 2 (a constraint):
A declaration other than a static_assert or attribute declaration shall declare at least a declarator (other than the parameters of a function or the members of a structure or union), a tag, or the members of an enumeration.
Case 2: underspecified declarations: 6.7 paragraph 5 (a constraint):
In an underspecified declaration all declared identifiers that do not have a prior declaration shall be ordinary identifiers.
and paragraph 12 (not a constraint, so violations are undefined behavior, no diagnostic required):
A declaration such that the declaration specifiers contain
no type specifier or that is declared with constexpr
is
said to be underspecified. If such a declaration is not a
definition, if it declares no or more than one ordinary identifier, if
the declared identifier already has a declaration in the same scope,
or if the declared entity is not an object, the behavior is
undefined.
Case 3:: array declarators: 6.7.6.2 paragraph 4 (not a constraint):
If the size is *
instead of being an
expression, the array type is a variable length array type of
unspecified size, which can only be used in declarations or type names
with function prototype scope
Case 4: for
statements: 6.8.5
paragraph 3 (a constraint):
The declaration part of afor
statement shall only declare identifiers for objects having storage classauto
orregister
.
In all four cases, a question of the following form arises: suppose
the token sequence for the syntax construct the provision in question
relates to contains, at some level of indirection, tokens that can be
interpreted as described in the provision (declaring something, or, in
case 3, the [*]
array declarator), but this appears
in some inner context (not directly the “main”
declaration) so it is unclear how the provision should be applied. In
all four cases, there have been some attempts to clarify the
provision, but those attempts do not cover all cases and may not
always follow the same choice for what is considered part of the
declaration. To the extent there is a consistent choice, it appears
to be to err on the side of disallowing questionable cases; in cases 2
and 4 this means a maximal interpretation of what is
part of the declaration, while in case 1 it means
a minimal interpretation of what is declared by a
declaration and in case 3 a minimal interpretation of
what is considered to be at function prototype scope. More details of
these cases follow. As usual, as well as WG14 deciding the intended
semantics, those need to be made clear in the normative text.
In C90 (6.5), this read “A declaration shall declare at least a declarator, a tag, or the members of an enumeration.”. C90 DR#115 asked about the following code:
/* Example 1. */ struct { int mbr; }; union { int mbr; };
The response said “The Committee agrees that the quoted constraint can be read either way.”. C99 added “other than the parameters of a function or the members of a structure or union”, so making this example clearly invalid. However, since that only excludes certain cases from counting as declaring a declarator, ambiguity remains in other cases, where the tag or members of an enumeration are declared but in some kind of nested context.
// Example 2. struct { struct s2 { int x2a; } x2b; }; // Example 3. typeof (struct s3 { int x3; }); // Example 4. alignas (struct s4 { int x4; }) int; // Example 5. typeof (struct s5 *);
Here, the expressions of intent involve initializers, where it is stated in 6.7.9 paragraph 3 (a Note, not normative text) that a member declaration counts for the purposes of the constraint in 6.7 paragraph 5, with the following examples considered invalid:
// Example 6. auto p = (struct { int x; } *)0; // Example 7. struct s; auto p = (struct s { int x; } *)0;
Those examples do not cover other ways in which a tag, member or
enumerator declaration might appear indirectly outside the initializer
in an underspecified declaration (noting that constexpr
declarations are also underspecified, which means cases where the tag
or member declaration appears indirectly in the type specifier must
also be considered).
// Example 8. constexpr typeof (struct s8 *) x8 = 0; // Example 9. auto alignas (struct s9 *) x9 = 0;
C99 DR#341 asked about:
// Example 10. void f(int (*)[*]); // Example 11. void f(int (*)[sizeof(int (*)[*])]);
The Committee Discussion said “There was consensus that the first example should be valid, and the second should be invalid.”. A wording change was made to the standard to clarify the meaning of scope in the case of a type name. Note that as in case 1, but unlike cases 2 and 4, the intent from the Committee Discussion means a minimal interpretation of what counts as part of a given context is being applied.
(This case, unlike the other cases, does not have any new examples raised in this document, but is included because of the similarity of the interpretation issue to the other cases.)
for
statementsC99 DR#277 asked about:
// Example 12 (code fragment). for (enum fred { jim, sheila = 10 } i = jim; i < sheila; i++) // loop body
It was stated that all three
identifiers fred
, jim
and sheila
violate the constraint. However, other
examples arise involving member declarations, declarations in the
initializer, and declarations inside typeof
or alignas
, where a similar question to the previous
examples arises of whether a minimal interpretation (as in cases 1 and
3) or a maximal interpretation (as in case 2) should be applied
of what counts as declared by the declaration.
// Example 13. void f13 (void) { for (struct { int m13; } *p13 = 0; ;); } // Example 14. void f14 (void) { for (typeof (struct { int m14; }) *p14 = 0; ;); } // Example 15. void f15 (void) { for (alignas (struct { int m15; }) int x15 = 0; ;); } // Example 16. void f16 (void) { for (int x16 = alignof (struct { int m16; }); ;); }