cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2805 [expr.delete] Lookup for the deallocation function is underspecified for multiple viable destroying operator delete #439

Open leni536 opened 9 months ago

leni536 commented 9 months ago

Full name of submitter: Lénárd Szolnoki

Reference: [expr.delete]

https://github.com/cplusplus/draft/blob/4c76193e3d310ea5a18679ab86a54074fe1635e9/source/expressions.tex#L5855-L5884

If more than one deallocation function is found, the function to be called is selected as follows:

  • If any of the deallocation functions is a destroying operator delete, all deallocation functions that are not destroying operator deletes are eliminated from further consideration.
  • If the type has new-extended alignment, a function with a parameter of type std::align_val_t is preferred; otherwise a function without such a parameter is preferred. If any preferred functions are found, all non-preferred functions are eliminated from further consideration.
  • If exactly one function remains, that function is selected and the selection process terminates.
  • If the deallocation functions belong to a class scope, the one without a parameter of type std::size_t is selected.
  • If the type is complete and if, for an array delete expression only, the operand is a pointer to a class type with a non-trivial destructor or a (possibly multidimensional) array thereof, the function with a parameter of type std::size_t is selected.
  • Otherwise, it is unspecified whether a deallocation function with a parameter of type std::size_t is selected.

Issue description:

Condiser the following code:

#include <memory>

struct B {
    void operator delete(B* ptr, std::destroying_delete_t);
};

struct D : B {
    void operator delete(D* ptr, std::destroying_delete_t);
    using B::operator delete;
};

void foo(D* ptr) {
    delete ptr;
}

For the delete expression within foo the lookup for the deallocation function is not specified. There are two viable deallocation functions, void D::operator delete(D*, std::destroying_delete_t) and void B::operator delete(B*, std::destroying_delete_t). None of the lookup rules apply in [expr.delete].

It seems that it is assumed that the only way that multiple functions remain candidates at one of the last three points is when one function remains with a size_t parameter and the an other one remains without a size_t parameter.

Suggested resolution:

  1. Make this always ambiguous, or
  2. Consider ICS from the delete expression's pointer argument to the candidate operator delete's first parameter and rank them accordingly.

I have no strong opinion either way.

https://godbolt.org/z/88c4shErv

Currently gcc fails with ICE (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111776), clang treats this as ambiguous, MSVC selects the deallocation function that is declared last (depends on order using B::operator delete; and the function declaration in D).

leni536 commented 9 months ago

It's not necessary to use a destroying operator delete to hit this corner case:

struct A {
    void operator delete(void *);
};

struct B {
    void operator delete(void *);
};

struct C : A, B {
    using A::operator delete;
    using B::operator delete;
};

void foo(C* ptr) {
    delete ptr;
}

Therefore the resolution shouldn't be specific to destroying delete.

jensmaurer commented 9 months ago

CWG2805