While reading through GCC's implementation of std::optional
I noticed something interesting. I know boost::optional
is implemented as follows:
template <typename T>
class optional {
// ...
private:
bool has_value_;
aligned_storage<T, /* ... */> storage_;
}
But then both libstdc++ and libc++ (and Abseil) implement their optional
types like this:
template <typename T>
class optional {
// ...
private:
struct empty_byte {};
union {
empty_byte empty_;
T value_;
};
bool has_value_;
}
They look to me as they are functionally identical, but are there any advantages of using one over the other? (Except for the obvious lack of placement new in the latter which is really nice.)
It's not just "really nice" - it's critical for a really important bit of functionality, namely:
There are several things you cannot do in a constant expression, and those include
new
andreinterpret_cast
. If you implementedoptional
withaligned_storage
, you would need to use thenew
to create the object andreinterpret_cast
to get it back out, which would preventoptional
from beingconstexpr
friendly.With the
union
implementation, you don't have this problem, so you can useoptional
inconstexpr
programming (even before the fix for trivial copyability that Nicol is talking about,optional
was already required to be usable asconstexpr
).std::optional
cannot be implemented as aligned storage, due to a post-C++17 defect fix. Specifically,std::optional<T>
is required to be trivially copyable ifT
is trivially copyable. Aunion{empty; T t};
will satisfy this requirementInternal storage and placement-
new
/delete
usage cannot. Doing a byte copy from a TriviallyCopyable object to storage that does not yet contain an object is not sufficient in the C++ memory model to actually create that object. By contrast, the compiler-generated copy of an engagedunion
over TriviallyCopyable types will be trivial and will work to create the destination object.So
std::optional
must be implemented this way.