My assumption has always been that std::launder is a zero-overhead abstraction (really a no-op at the machine level) whole sole purpose is to inform the static semantics elaborator about the lifetime and type of a given storage. I hope we aren’t considering anything more complicated than that.
This perfectly expresses my expectation as well. But now it looks slightly different: The type remains malleable, and rather than marking an area of storage, launder provides a single blessed reference to an unbounded region. (If reinterpret_cast and downcasts are valid on the result of launder, what’s its connection to the type system? Why can’t it operate on void*?)
And, it’s not exactly zero-overhead, as its purpose is to disable an optimization which might be harmless. For example, take
N4303’s motivating case in
std::optional. Statically,
optional’s contained object might have been created by placement-new, so the accessors always need to launder their return value. This would seem to make
optional slower than “native” objects.
namespace std {
template< typename t >
t & optional< t >::operator * ()
{ return * launder( & this->storage.object ); }
}
void foo() {
int i;
std::optional< int > o = 3;
i = *o; // Must load *o from memory because address was laundered.
i = *o + *o; // Reload twice more. Will optimizers second-guess launder?
}
Likewise, MSVC appears to be remembering the value inside my wrapper’s NSDM embedded storage. It’s a beneficial optimization to the common case of a sequence of accesses, so it would be nice not to lose it.
It would also be easier-to-use if the assignment operator could inform the compiler when a lifetime begins, as opposed to the accessors providing notification that a new lifetime might have already begun. But, I suppose that’s not feasible, or we wouldn’t need launder in the first place.