https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
--- Comment #15 from Andrew Downing <andrew2085 at gmail dot com> --- (In reply to Richard Biener from comment #10) > (In reply to Andrew Downing from comment #8) > > From the C standard: > > If a value is copied into an object having no declared type using memcpy or > > memmove, or is copied as an array of character type, then the effective type > > of the modified object for that access and for subsequent accesses that do > > not modify the value is the effective type of the object from which the > > value is copied, if it has one. For all other accesses to an object having > > no declared type, the effective type of the object is simply the type of the > > lvalue used for the access. > > > > So even using C semantics the effective type of storage and *t should not be > > changed, because they already have a declared type. > > But in your testcases 't' is a pointer and the declared object is not > visible. > So the only thing an implementation can do is take advantage of the declared > type for optimization when it is visible. That's not exactly relevant in this case though since this is C++. In C++ new has special semantics. Regardless of the type of the variable being assigned to, or the declared type of the object that the pointer argument points to, it starts the lifetime of an object of the specified type in the storage, and ends the lifetime of whatever is there. There is no equivalent of this in C. In C you can change the effective type of an object with allocated storage duration since it has no declared type, but you can not change the declared type of an object with automatic storage duration since it has a declared type. double d; has a declared type; >Note that for C++ types you can apply memcpy to the placement new is not >needed since object re-use terminates lifetime of the previous object and >starts lifetime of a new one. This means that your example can be >simplified to memcpy is needed because starting the lifetime of a new object in the storage of an existing object does not re-use the old objects representation. If the default trivial constructor is used the standard explicitly states that the value of the new object is indeterminate and accessing the value of the object will result in undefined behavior. This is why in p0593r6 section 3.8 they mention copying the object representation to another location and then copying it back after placement new. There is no way in C++ to pun in C like you can with a union with no intermediate steps. Yes I expect all these operations to be elided away. They are simply there because the standard says they have to be. In other compilers they may be required to ensure well defined behavior. This isn't code that I'm actually using anywhere I was just reading p0593r6 and tested their described implementation of std::start_lifetime_as and found this strange behavior. There is an underlying bug here, and if it's popping up in this example, it will pop up somewhere else eventually. This is all kind of besides the point anyway though, because gcc is handling everything ok except for std::launder. std::launder is only supposed to be an optimization barrier, but it's causing the opposite problem. Here, std::launder is preventing an optimization that shouldn't be taking place from NOT taking place. I've been looking at gcc's code for a while and have gotten as far as seeing that the use of std::launder is preventing dse_classify_store() in tree-ssa-dse.c from seeing the relationship between double d = 3.14159; and _6 here. // this is right before the tree-dse3 pass (I disabled some passes to prevent constant propagation) f1 () { long unsigned int u; double d; long unsigned int * _6; <bb 2> [local count: 1073741824]: // this line is removed by tree-dse3 d = 3.14158999999999988261834005243144929409027099609375e+0; _6 = .LAUNDER (&d); u_3 = MEM[(uint64_t *)_6]; d ={v} {CLOBBER}; return u_3; } If the implementation of std::launder in gcc simply disallows optimization passes from seeing through it, I think that is a mistake. std::launder being an optimization barrier means disallowing checks that enable an optimization from seeing through it as well as allowing checks that disable an optimization from seeing through it.