llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.48k stars 11.77k forks source link

Explicit calling to base class destructor with unqualified name rejected by clang #102467

Open Rush10233 opened 2 months ago

Rush10233 commented 2 months ago

Consider:

struct A
{ 
  ~A () {}
};

struct B : public A
{ 
   ~B () {}
};

int main ()
{ 
   B *b=new B;
   b->~A();
   //b->A::~A();
  return 0;
}

The code will work only if the destructor~A() in the derived class pointer B *b is called with qualified name A::~A() . I'm not sure whether this is necessary. class.dtor_#17 says that "In an explicit destructor call, the destructor is specified by a ~ followed by a type-name or decltype-specifier that denotes the destructor's class type. The invocation of a destructor is subject to the usual rules for member functions ([class.mfct]);", but usual member function does not have to be called with a qualified name in derived class instance.

GCC is now accepting this code, but MSVC does not accept it either.

https://godbolt.org/z/sW6YEhc8j

MitalAshok commented 2 months ago

https://timsong-cpp.github.io/cppwp/n4868/basic.lookup.classref#3

If the unqualified-id is ~type-name, the type-name is looked up in the context of the entire postfix-expression. If the type T of the object expression is of a class type C, the type-name is also looked up in the scope of class C. At least one of the lookups shall find a name that refers to cv T.

Lookup for A does not find the type B, ~type-name can only call the destructor on the same type as the postfix-expression, not a base type.

llvmbot commented 2 months ago

@llvm/issue-subscribers-clang-frontend

Author: None (Rush10233)

Consider: ```c++ struct A { ~A () {} }; struct B : public A { ~B () {} }; int main () { B *b=new B; b->~A(); //b->A::~A(); return 0; } ``` The code will work only if the destructor`~A()` in the derived class pointer `B *b` is called with qualified name `A::~A()` . I'm not sure whether this is necessary. [class.dtor_#17](https://timsong-cpp.github.io/cppwp/n4868/class.dtor#17) says that "In an explicit destructor call, the destructor is specified by a ~ followed by a type-name or decltype-specifier that denotes the destructor's class type. The invocation of a destructor is subject to the usual rules for member functions ([class.mfct]);", but usual member function does not have to be called with a qualified name in derived class instance. GCC is now accepting this code, but MSVC does not accept it either. [https://godbolt.org/z/sW6YEhc8j](https://godbolt.org/z/sW6YEhc8j)
MitalAshok commented 2 months ago

These lookup rules were heavily modified by P1787R6. It looks like this is valid in C++23 but was not valid before that.

It should be accepted as an extension (in non-SFINAE contexts, with a pedantic warning) in C++20.

cor3ntin commented 2 months ago

@Endilll @sdkrystian

@MitalAshok FYI P1787R6 was accepted as a DR so we would apply the change unconditionally

Endilll commented 2 months ago

I think this is invalid in post-P1787R6 wording as well according to [basic.lookup.qual.general]/4.6:

If a qualified name Q follows a ~: — <...> — The type-name that is or contains Q shall refer to its (original) lookup context (ignoring cv-qualification) under the interpretation established by at least one (successful) lookup performed.

In the example, Q refers to A, but lookup context is decltype(*b), which is B.

This interpretation is also supported by a FIXME we have in the test for CWG399: https://github.com/llvm/llvm-project/blob/5c0eb1a6e4679cc741f75f5ddc53d4878dabc1f2/clang/test/CXX/drs/cwg3xx.cpp#L1731 (This line is in function template, hence the FIXME instead of the diagnostic we see in the original example in this issue.)