It works because the grammar didn't make provisions for built-in types, but it did make provisions for aliases:
[expr.post]/1:
postfix-expression:
postfix-expression . pseudo-destructor-name
postfix-expression -> pseudo-destructor-name
pseudo-destructor-name:
~ type-name
~ decltype-specifier
And [dcl.type.simple]/1:
type-name:
class-name
enum-name
typedef-name
simple-template-id
You can imagine what each variable under type-name
stands for. For the case at hand [expr.pseudo]/1 specifies that it is just a void
expression:
The use of a pseudo-destructor-name after a dot . or arrow -> operator
represents the destructor for the non-class type denoted by type-name
or decltype-specifier. The result shall only be used as the operand
for the function call operator (), and the result of such a call has
type void. The only effect is the evaluation of the postfix-expression
before the dot or arrow.
The interesting thing to note, is that you should be able do that without an alias (if you have a named object), because the pseudo destructor call also works with a decltype
specifier:
auto a = int{1};
a.~decltype(a)();