https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115050
Andrew Pinski <pinskia at gcc dot gnu.org> changed: What |Removed |Added ---------------------------------------------------------------------------- Resolution|--- |INVALID Status|UNCONFIRMED |RESOLVED --- Comment #3 from Andrew Pinski <pinskia at gcc dot gnu.org> --- There is a copy of Tester involved before F's constructor fully happens so we call ~FBase (on the unwind) which then calls the bad (vtable) deconstructor. Why it happens at -O0 only is just by accident. At -O1 we do some de-virtualization which allows the (right) deconstructor to be called. Note GCC's VTable::~VTable sets the vtable to be VTable's vtable which exposes the bug. This only happens at -O0 because at -O1 and above it is considered a dead store. This is definitely undefined code without the following change because you might get ~VTable being called twice. Note if you change VTable to: ``` class VTable { public: virtual void func(int x) = 0; // #1 this line affects behavior virtual ~VTable() {__builtin_printf("~vtable\n");} }; ``` You will see there is 2 calls to ~VTable which is incorrect. Changing FBase/F to this: ``` struct FBase { static constexpr int S = sizeof(void*) + sizeof(VTable); bool constructed = false; std::byte _data[S]; ~FBase() { if (!constructed) return; static_cast<VTable*>(static_cast<void*>(&_data[0]))->~VTable(); } // #2 this line affects behavior }; struct F : FBase { template <typename T> F(T&& t) { static_assert(sizeof(Handler<std::decay_t<T>>) <= S); ::new (&_data[0]) Handler<std::decay_t<T>>(std::forward<T>(t)); constructed = true; } ~F() = default; }; ``` Fixes the issue and only one call to ~VTable happens.