I don’t think so. According to https://stackoverflow.com/a/39382728 (the original SO answer that prompted the linked SO question, and which quotes the relevant parts of the spec), the allocation is not UB, but accessing the member after that (at least without the std::launder trick) is UB. It’s precisely this latter part that I don’t think is very reasonable to have in the spec.
The issue is that code outside of some_func does not know that f has been destroyed and a new object created in the same memory location, thus wouldn't know that the const member foo::x now has a new value.
As for why the spec prohibits the modification of const values: one of the reasons for compilers being allowed to treat const values as being truly immutable is for performance. If the compiler can see the actual value then it can avoid the memory read entirely, and if it doesn't know the actual value then it only needs to do one memory read.
If those const values could change without the compiler being aware then it now needs to do an awful lot more fetching from memory as it cannot guarantee that the value did not change. Some of it could be optimized away, but it would still make code a lot slower if - for example: mixing reads from const ints with pointer/reference manipulation of ints - as due to potential aliasing the compiler would likely have to read those const ints from memory each time.
Note that accessing the new object via the pointer to placement new is perfectly fine, the issue is dereferencing the old pointer that logically point to an object whose lifetime has been terminated even if it mught actually contain the same bit pattern as the new pointer. I think this is related to the ill-defined concept of pointer provenance.
Launder let you discard any provenance info for the old pointer and treat it as a new one.
Tricky and very ill-defined, and according to the another comment elsethread this now doesn't require launder anymore (it probably was proven unworkable in practice).