Reputation: 63144
I have seen the following pattern several times:
// T is a type, this is at namespace scope
std::aligned_storage_t<sizeof(T), alignof(T)> storage;
T &t = reinterpret_cast<T &>(storage);
This, coupled with adequate namespacing and naming, provides a pleasant interface (t
) to users of the variable, while enabling deferred construction, reinitialization, etc of the actual object on the library side though placement new
and explicit destructor calls. You can see it working here.
Now, std::aligned_storage
is neat and all, but C++17 gave us a new tool in the box for such storage-vs-object lifetime splitting, that is std::optional
.
However, the two ways of accessing the value of an std::optional
(value()
and operator*
) both require a value to actually be there; otherwise value()
will throw std::bad_optional_access
, while operator*
will trigger undefined behaviour (via breaking the requires clause in [optional.observe]§5).
std::optional<T> storage;
T &t = *storage; // Looks okay, mines bitcoin when you're not looking
Is such a usage of std::optional
still possible somehow?
If not, what would be a reason for preventing it?
Upvotes: 3
Views: 232
Reputation: 13040
This, coupled with adequate namespacing and naming, provides a pleasant interface (t) to users of the variable, while enabling deferred construction, reinitialization, etc of the actual object on the library side.
Unfortunately, using t
to access the object constructed later at that address is undefined behavior. This is one of the reasons why std::launder
is proposed.
Note this case is different from the case described in that question. In that question, the reference/pointer is obtained after the object of type T
is created (though this may also be undefined after C++17 without std::launder
).
Is such an usage of std::optional still possible somehow?
As what you pointed out, this is undefined behavior.
If not, what would be a reason for preventing it?
An optimizer may find the address is associated with the object that provides storage for T
, and ignore any access to that address through a glvalue of type that causes undefined behavior. In fact, the reasons are essentially how strict-aliasing rules benefit an optimizer.
Upvotes: 3