My initial thought was that it is undefined behavior.
5.3.5/3: "In the second alternative (delete array) if the dynamic
type of the object to be deleted
differs from its static type, the
behavior is undefined.73).
Footnote 73 reads, "This implies that an object cannot be deleted using a pointer of type void*
because there are no objects of type void
".
Arguably the object in your example doesn't have a dynamic type, since the definition of "dynamic type" at 1.3.3 mentions "most derived object", and the definition of "most derived object" at 1.8/4 is talking about objects of class type. So I kept looking:
5.2.10/3: "[reinterpret_cast] might, or might not, produce a representation
different from the original value"
5.3.5/2: "The value of the operand of delete
shall be the pointer value
which resulted from a previous array
new-expression".
I'm not sure whether a reinterpret_cast results in the same pointer value as was input, or not. Possibly it's cleared up by some other bit of the standard which I haven't found yet. I would not call this code "OK" without finding something to definitively state that if you reinterpret_cast a pointer, the result is the same "pointer value" as before, so that by passing it to delete[] you are passing "the pointer value" from new[].
5.2.10/7: "Except that casting [between certain pointer types] and
back to its original type yields the
original pointer value, the result of
such a pointer conversion is
unspecified".
This looks like bad news to me - it conspicuously doesn't say that the cast yields the same value, only that the pair of casts over and back, yields the same value. This suggests to me that the single cast is allowed to yield a different value, but it is only suggestive, not explicit. This is the usual problem with the rule that "if the standard doesn't state the behavior, then the behavior is undefined". Just because it doesn't state it in any of the paragraphs I can find using the index, doesn't mean it doesn't state it somewhere else...
We know that in practice we can cast things to unsigned char* in order to inspect their bytes, or void* to copy PODs using memcpy, so there must be some casts guaranteed to create aliases. You might think that if your implementation does create aliases with certain casts, then you're passing in the "same value" you got from new[]. But I'm still not sure that's good enough for delete[]. I think I'm missing something important.