12

I just found out that the following code is not a valid C++ (it doesn't parse at int after ~):

int x = 5;
x.~int();

However, the following snippet does work:

int32_t x = 5;
x.~int32_t();

This is because int32_t is a typedef in my particular implementation of C++, and a destructor can, apparently, be called on any typedef'ed type.

My question is: is any implementation of C++ required to allow the second snipped to compile? In particular, is int32_t guaranteed to be a typedef, and is the compiler required to allow a destruction of a typedef if it knows that typedef typedefs something to int?

Ishamael
  • 12,583
  • 4
  • 34
  • 52
  • Yes. `int32_t` is not a built-in type, and it's not allowed to be a macro. – Cheers and hth. - Alf Dec 22 '15 at 21:32
  • @Cheersandhth.-Alf The C++14 standard draft states `typedef signed integer type int32_t;` as the definition of `int32_t`. As far as I can tell, this does not leave much space for user-defined types and all or even a macro. – cadaniluk Dec 22 '15 at 21:36
  • To avoid a close question / open question war, I'm not going to vote to close as a duplicate, BUT ... this is a duplicate of the linked question. Just because that other question doesn't mention `int32_t` doesn't mean that that other question doesn't answer yours. – David Hammen Dec 22 '15 at 22:04
  • @DavidHammen, I lost the link now, but the answer there was explaining why it works for template parameter. It doesn't mean that the same should work for typedefs, or that int32_t must be a typedef to begin with. So it technically was answering neither of my specific questions. – Ishamael Dec 22 '15 at 22:14

1 Answers1

8

There's a clear requirement that int32_t be a typedef. We start with [cstdint.syn]/2:

The header defines all functions, types, and macros the same as 7.18 in the C standard.

So from there we look at the requirement for the C library:

The typedef name intN_t designates a signed integer type with width N, no padding bits, and a two’s complement representation.

[emphasis added]

So yes, int32_t must be a "typedef name".

Although (as far as I know) it's never stated directly in normative text, the following note makes it clear that invoking a destructor for a typedef that resolves to a built-in type is intended to compile and succeed ( [class.dtor]/16):

Note: the notation for explicit call of a destructor can be used for any scalar type name (5.2.4). Allowing this makes it possible to write code without having to know if a destructor exists for a given type. For example,

typedef int I;
I* p;
p->I::~I();
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • The snippet is of course undefined behavior (member access via a wild pointer). Or does the Standard guarantee that using this syntax on a primitive is a no-op and the member access never actually happens? – Ben Voigt Dec 22 '15 at 22:22
  • @BenVoigt: Good question. I don't think the pointer should actually be dereferenced, but I'm not sure I can find a statement to say that with certainty. – Jerry Coffin Dec 22 '15 at 22:28
  • "The only effect is the evaluation of the postfix-expression before the dot or arrow." (5.2.4) So then `(*p).I::~I()` bad but `p->I::~I()` good? And it doesn't actually matter what the type is? Seems like a defect. – Ben Voigt Dec 22 '15 at 22:29
  • The only use case of calling destructor explicitly I can think of is to destruct something for constructed by `placement new`, for destruction and deallocation needs to be done separately. As PODs doesn't really do anything on destruction other than deallocating the memory, explicitly calling destructor on PODs should be NOP. – user3528438 Dec 22 '15 at 22:33
  • I think 12.4/13 would apply "The invocation of a destructor is subject to the usual rules for member functions (9.3); that is, if the object is not of the destructor’s class type and not of a class derived from the destructor’s class type (including when the destructor is invoked via a null pointer value), the program has undefined behavior." – Ben Voigt Dec 22 '15 at 22:33
  • 1
    @user3528438: That's not the only use case. One other is switching between elements of a union (now that you can have non-trivial types in a union). – Jerry Coffin Dec 22 '15 at 22:35
  • @JerryCoffin: Although to be fair, switching between elements of a union *also* requires placement new, if non-trivial types are involved. But the element you're switching from might well have been initialized by a ctor-initializer list or brace-or-equal-initializer, not placement new. – Ben Voigt Dec 22 '15 at 22:36
  • This is a pseudo-destructor call. The normative specification is in [\[expr.pseudo\]](http://eel.is/c++draft/expr.pseudo). – T.C. Dec 23 '15 at 03:10
  • @T.C.: Yes, the normative spec of a pseudo-dtor call is there, but I don't see anything there saying the left side of the `.` or `->` must be something like a typedef name or a template parameter. – Jerry Coffin Dec 23 '15 at 06:20
  • @JerryCoffin You mean the right side? That's specified by [the grammar production](http://eel.is/c++draft/expr.post#pseudo-destructor-name). – T.C. Dec 23 '15 at 07:36