https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86908
--- Comment #7 from Jonathan Wakely <redi at gcc dot gnu.org> --- (In reply to Richard Biener from comment #6) > (In reply to Jonathan Wakely from comment #1) > > No, your program has undefined behaviour. To make it valid you either need > > to use std::launder, or use the pointer that is returned by the placement > > new. > > > > The compiler is allowed to assume that the dynamic type of &strategy does > > not change (that's why you need to use std::launder). > > You need to use std::launder at every use of the object, right? Or just launder the pointer once and then use that: auto laundered = std::launder(&strategy); laundered->doIt(); // > I wonder > why static_cast<BaseStrategy*>() isn't a good enough "hint" here that > the type of strategy changed (from QOI perspective, std::launder is new). Casting from type X* to Y* where X and Y happen to be the same type can happen in generic code that was already written years ago, and doesn't play these sort of dirty tricks. It would be a shame to prevent useful optimisations because of a no-op cast. We don't need to make it easier to play nasty games with the type system. There are ways to do this sanely, without undefined behaviour, even without std::launder (just using the pointer returned by the placement new-expression should be good enough, instead of accessing through &strategy every time). > I guess a better testcase would be to declare strategy as AStrategy > and placement-new BaseStrategy over it or does the standard guaranteee > that AStrategy fits in the storage of BaseStrategy? For these types, with the Itanium ABI, it fits, and you could add a static_assert to verify it. Either way you need to placement new the original type back again, otherwise the wrong destructor gets called on scope exit, which adds more undefined behaviour.