N3070 Declaration contexts (C2x CD UK comment)

Joseph Myers

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 a for statement shall only declare identifiers for objects having storage class auto or register.

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.

Case 1: minimum content of declarations

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 *);

Case 2: underspecified declarations

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;

Case 3: array declarators

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.)

Case 4: for statements

C99 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; }); ;);
}