Why is there a dummy union member in some implementations of std::optional?
Asked Answered
O

1

29

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?

Occidental answered 14/8, 2019 at 14:24 Comment(0)
P
31

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.

Petuu answered 14/8, 2019 at 15:16 Comment(5)
For those hunting for the standard quote: eel.is/c++draft/dcl.dcl#dcl.constexpr-4.1Overstrung
@TedLyngmo, which version? Tried on 7.4.0 - got an error.Occidental
@r3musn0x 9.1.1. I don't think 7.4.0 implements LWG 2900.Delastre
@TedLyngmo, it looks to me that LWG 2900 is only related to the library implementation of optional, so I don't really see the connection... Anyway look here - an error! :)Occidental
@r3musn0x You are probably correct. Here's a comparison between g++ and clang++ and also a bugreport that seems to be connected to this: bugzilla #886581 It seems to hit my example when templates are involved, but in the bugreport they've managed to trigger the bug without templates.Delastre

© 2022 - 2024 — McMap. All rights reserved.