[tentative quotes tying conversion to evaluation]
It seems to me from these definitions that conversions are tied very
tightly to expressions. Conversions occur only as a result of an
operator being applied to an operand (not quite correct, but close
enough), which occurs as part of expression evaluation.
Let's say we agree on this. We can, because I can still highlight the
problem even if we assume the actual conversion is not taking place.
I'll argue that in this case, you're just stuck and have to fudge your
way out, when the standard does not require you to.
Furthermore, just as a matter of common sense (dangerous, I know),
expression evaluation happens at execution time; type determination
must happen at compilation time.
That doesn't really matter here: I already agreed evaluation is not
taking place.
That said, common sense is only half right (not bad for common sense,
but still). Constant expressions can (must not, but may) be evaluated at
compile time (6.6.2), and they're not evaluated any differently from
non-constant expressions (6.6.11).
Case in point: the very sizeof expression we've been discussing is
evaluated at compile time. This is not relevant since the discussion is
about the operand of sizeof, not the sizeof expression as a whole, but
it's fun to note.
Type determination must, for the most part, happen at compilation time,
because the compiler must issue diagnostics for constraint violations,
and most of these simply require accurate typing information. (There is
probably nothing in the standard that disallows run-time typing where
constraints would not be violated, but such a hybrid implementation
would be unattractive for obvious reasons.)
[snip]
If you believe an alternate mechanism is required or supplied by the
standard that the compiler can apply to determine the type without
going through conversion, I'd like to know. I don't see what magically
tells the compiler what the type of `*a' is without tripping over the
conversion rules.
There's nothing magical about it. For any non-VLA expression that's
the operand of a sizeof operator, the compiler has to go through the
same process it does for any expression to determine the type, but
without evaluating the expression (or rather, without generating code
to evaluate the expression).
You say that as if the undefined behavior that results from converting a
register array to a pointer is part of the code that would be generated
to evaluate the expression. It is not, or rather, nothing in the
standard says it is.
Then how *does* the standard say the type is determined?
By such phrases as "the type of <expression> is <type>", in particular
"The unary * operator denotes indirection. [...] If the operand has type
'pointer to type', the result has type 'type'." Note that evaluation of
the operand is not required, we need only know its type. By recursively
applying such rules, we can determine the type without even any
reference to evaluation. The sentence that immediately follows it, for
example, "If an invalid value has been assigned to the pointer, the
behavior of the unary * operator is undefined", is irrelevant: behavior
of the operator has no bearing on the typing.
I'll quote and simplify the actual case to demonstrate what I mean.
void f() {
register size_t a[1] = {sizeof *a};
}
According to both of us and the standard, `*a' is not evaluated. In my
view, the *only* rule in the *entire* standard that allows us to
determine the type of `*a' (which sizeof needs) is the one that
describes the indirection operator, quoted above. For this, `a' must be
of meta-type 'pointer to <t>', for some type t. But it is not.
By your view, we are now stuck as far as the rules we're given go. We
can't actually convert `a' to a `size_t *' because conversion is part of
evaluation, which we don't do, since we're in a sizeof. So, if I get you
correctly, you propose that the compiler now proceeds *as if* it
converted `a' to a `size_t *', concludes that the type of `*a' is
therefore `size_t', and applies sizeof accordingly.
This is *almost* my view, but with one crucial difference. I propose
this must follow 6.3.2.1.3 exactly: "An expression that has type 'array
of <type>' is converted to an expression with type 'pointer to <type>'
that points to the initial element of the array object and is not an
lvalue. If the array object has register storage class, the behavior is
undefined."
Here is the essence of the problem: even if we agree that the compiler
does not actually need to *do* anything to yield "an expression [...]
that points to the initial element of the array object and is not an
lvalue" because it only needs the go-ahead for the type conversion, we
*still* trigger the UB clause.
We *know* at compile time that `a' has register storage class, and there
is no other way of completing the typing process. We must follow these
rules because no others will get the job done, and they say we've messed
up and the compiler may unleash the nasal demons. Nothing in the
standard says "because you're not evaluating `a', you may pretend there
is no UB" or "because you're not evaluating `a', you may make up your
own rules about the typing that resemble the ones used when evaluating".
If it has to carry out a run-time operation (the conversion) to
determine compile-time information, we're in big trouble.
Where does it say conversion *of this kind* is a run-time operation? We
would be in big trouble if we found out we needed conversions of *actual
values* to do typing, but we don't. But at this point we simply *need*
to apply at least that part of the conversion that includes the typing
rules, and it comes with UB.
S.