cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[basic.life] It is unclear whether destroying an already destroyed scalar object is undefined behavior #361

Open frederick-vs-ja opened 1 year ago

frederick-vs-ja commented 1 year ago

Full name of submitter (unless configured in github; will be published with the issue): Géry Ogam (cplusplus/draft#4944)

Reference (section label): [basic.life], [basic.start.term], [expr.prim.id.dtor], [expr.delete]

Link to reflector thread (if any):

Issue description: P0593R6 made pseudo-destructor calls end lifetime of scalar objects. However, currently there lacks wording indicating that destroying an already destroyed scalar object is undefined behavior, which is inconsistent with class objects.

Currently, implementations tend to accept such double destruction in constant evaluation (Godbolt link). Note that Clang is inconsistent with itself, which seems to be a bug.

using I = int;
constexpr I x = (I{}.~I(), 0); // clang, gcc, and msvc accept this
constexpr I y = ((0).~I(), 0); // clang only rejects this

On the other hand, it is arguably better to keep and clarify the status quo, which avoids inceasing UB.

Suggested resolution: The changes in cplusplus/draft#4953, if it is intented to make such double destruction undefined.

sergey-anisimov-dev commented 1 year ago

If you are referring to the (second) destruction happening automatically for the materialized temporary, it's an explicit issue only for the objects with non-trivial destructors. I'd say this is no different from a more simplified example:

{
  int i;
  i.~decltype(i)();
}

The above code is fine, since no non-trivial destructor for int exists. Clang indeed denies it constant-evaluation, though.

frederick-vs-ja commented 1 year ago

If you are referring to the (second) destruction happening automatically for the materialized temporary, it's an explicit issue only for the objects with non-trivial destructors.

Yes. I think the status quo is that double destruction of int is well-defined.

However, it seems to me that [basic.life] p9 is nearly redundant, since [class.dtor] p18 covers almost all (if not all) of such cases. And [class.dtor] p18 states that UB happens even if the destructor is trivial - but pseudo-destructors are not covered.

sergey-anisimov-dev commented 1 year ago

Moreover, I'd say [class.dtor#18] itself is redundant in a normative sense. Basically, there are two substatements:

Once a destructor is invoked for an object, the object's lifetime ends

Already stated in [basic.life#1.4].

the behavior is undefined if the destructor is invoked for an object whose lifetime has ended

Already stated in [basic.life#6.2]/[basic.life#7.2], since destructors constitute non-static member functions.