If you write the equivalent in C with GCC extensions, or Rust, sizeof(Foo) would be 8, the same as sizeof(int64_t); `marker` doesn't take up any extra space. In C++, however, sizeof(Foo) is 16, because `marker` must take up at least 1 byte to have a unique address, which gets expanded to 8 bytes due to alignment.
Now, as of C++20, you can reduce sizeof(Foo) to 8 by tagging `marker` as [[no_unique_address]]. However, this has drawbacks. First of all, it's easy to get situations like this in highly generic code, so it's hard to predict where [[no_unique_address]] needs to be applied (and applying it everywhere would be verbose).
Second of all, [[no_unique_address]] is dangerous, because it doesn't just allow empty fields to be omitted, it also allows nonempty fields to have trailing padding bytes reused for other fields. Normally that's okay, but if you have any code that performs memcpy or memset or similar based on the size of a type, such as:
I don’t really get the concern here. [[no_unique_address]] seems to be designed for use with empty types. As such, what does it mean to write to such a field? These are really meant to be tags, no?
Now, as of C++20, you can reduce sizeof(Foo) to 8 by tagging `marker` as [[no_unique_address]]. However, this has drawbacks. First of all, it's easy to get situations like this in highly generic code, so it's hard to predict where [[no_unique_address]] needs to be applied (and applying it everywhere would be verbose).
Second of all, [[no_unique_address]] is dangerous, because it doesn't just allow empty fields to be omitted, it also allows nonempty fields to have trailing padding bytes reused for other fields. Normally that's okay, but if you have any code that performs memcpy or memset or similar based on the size of a type, such as:
…then if that code writes to a [[no_unique_address]] field, it can overwrite adjacent fields, since sizeof(Foo) includes any trailing padding bytes!