Doc. no.: N2027
Date: 2016-03-17
Reply to: Clark Nelson
Email: clark.nelson@intel.com

Concerning point D of DR439

Introduction

Point D of DR439 (a.k.a. N1729) raises the question of the meaning of a non-constant expression in an abstract declarator. The other points of that DR are rather more straightforward, but point D requires more thought. Here I present some additional analysis I have done on the question.

Overview

There are three places an abstract declarator or type name can appear and (at least potentially) not be part of a larger expression:

  1. parameter-declaration
  2. alignment-specifier
  3. atomic-type-specifier

(When this topic comes up, the case of a generic selection is generally raised as well. However, it should be clearly understood that a type name appearing in a generic selection is necessarily part of a larger expression, so any expression therein isn't a full expression, so DR439 does not raise any issues about it. It's certainly possible that there are issues, but if so, they need to be spelled out. Once they have been clearly defined, it will hopefully become clear whether they should be considered along with DR439 or separately.)

So let's consider three file-scope declarations:

  1. void f(int [rand()]);
  2. _Alignas(int [rand()]) int i;
  3. _Atomic(int [rand()]) a;

The first declaration has a clearly defined meaning. According to 6.7.6.2p5, the expression is “treated as if it were replaced by *”. Therefore:

These are both true even if the declaration appears in a block scope.

For the second and third declarations, the standard today gives no clue how the expression should be handled. The only thing that is clear (even though the standard doesn't say so) is that, if the declarations are at file scope, any answer that requires evaluating the expressions is wrong.

The alignment specifier case

The interesting thing about _Alignas is that the “answer” is just a number; all other details of the type named are irrelevant. Since the alignment of an array type must be the same as that of its element type, the value of the array size is irrelevant, so strictly speaking it need not be evaluated.

This is similar to sizeof; according to 6.7.6.2p5:

Where a size expression is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated.

For an alignment specifier, I would prefer that the behavior not depend on the scope in which it appears. It would also be simpler to require that a non-constant size expression not be evaluated than to permit implementation latitude. That would be consistent with the parameter declaration case, and I doubt that any real-world code would change behavior as a result.

As an aside, here is an interesting related example:

size_t s = sizeof(int [rand()]);

According to 6.5.3.4p2:

If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

If the declaration of s appeared at file scope, the fact that the result of sizeof is not an integer constant would run afoul of the constraints on initialization, so a diagnostic would be required. That might be considered enough to override the requirement that “the operand is evaluated” in an initializer at file scope – but the phrasing would seem to be suboptimal.

The atomic type specifier case

_Atomic-of-array types are already disallowed; see 6.7.2.4p3. The following example avoids that restriction, but still has a non-constant expression in an atomic specifier:

_Atomic(int (*)[rand()]) p1;

What would such a declaration mean, if it were allowed?

It would be tempting to conclude that it is not allowed at file scope, since a variably modified type is involved. Unfortunately, the standard doesn't actually say so – or at least not yet. According to 6.7.6p3:

Furthermore, any type derived by declarator type derivation from a variably modified type is itself variably modified.

But an atomic type specifier isn't described as involving declarator type derivation. There is definitely a kind of type derivation involved, but possibly of a new and different kind.

On the other hand, because of the unique dual nature of the syntax for _Atomic, the preferred answer is probably that the previous declaration would have the same meaning as this declaration:

int (*_Atomic p2)[rand()];

If a qualifier other than _Atomic were used, the interpretation of the declaration and the contained expression would be pretty clear from the standard. But for _Atomic, the words of the standard seem to more or less rewrite this declaration into the prior form, about which the standard has less to say, by and large.

If it is true, as seems likely, that any atomic type specifier containing a non-constant expression can be expressed equivalently using an atomic type qualifier instead, then presumably the question of what to do when the expression is syntactically a full expression is not all that interesting.

What remains interesting is how to express this intention normatively.