Both libstdc++ (GNU) and libc++ (LLVM) implement std::optional
value storage using a union and both of them include a dummy member.
GNU implementation:
using _Stored_type = remove_const_t<_Tp>;
struct _Empty_byte { };
union {
_Empty_byte _M_empty;
_Stored_type _M_payload;
};
LLVM implementation:
union
{
char __null_state_;
value_type __val_;
};
My question is: Why do we need these _M_empty
/__null_state_
members? Is there something wrong with a single-member union?
The class template std::optional manages an optional contained value, i.e. a value that may or may not be present. A common use case for optional is the return value of a function that may fail.
A union can have member functions (including constructors and destructors), but not virtual (10.3) functions. A union shall not have base classes.
Single member union creates all sorts of problem when trying to be default constructible and be constexpr compatible.
Consider this code:
struct nontrivial {
constexpr nontrivial(int o) : u{o} {}
int u;
};
union storage {
nontrivial nt;
};
struct optional {
storage s;
};
constexpr auto run() -> int {
optional o;
return o.s.nt.u;
}
int main() {
constexpr int t = run();
}
This is ill formed because optional
has a deleted constructor.
Then a simple fix would be to add a constructor that initialize no union member:
union storage {
constexpr storage() {} // standard says no
nontrivial nt;
};
But it won't work. Constexpr unions must have at least one active member. It cannot be an empty union. To workaround this limitation, a dummy member is added. This makes std::optional
useable in constexpr context.
(Thanks @Barry!) From [dcl.constexpr]/4 (emphasis mine):
The definition of a constexpr constructor whose function-body is not = delete shall additionally satisfy the following requirements:
if the class is a union having variant members ([class.union]), exactly one of them shall be initialized;
if the class is a union-like class, but is not a union, for each of its anonymous union members having variant members, exactly one of them shall be initialized;
for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
for a delegating constructor, the target constructor shall be a constexpr constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With