Код: Выделить всё
// This class implements the storage used by `std::expected`. We have a few
// goals for this storage:
// 1. Whenever the underlying {_Tp | _Unex} combination has free bytes in its
// tail padding, we should reuse it to store the bool discriminator of the
// expected, so as to save space.
// 2. Whenever the `expected` as a whole has free bytes in its tail
// padding, we should allow an object following the expected to be stored in
// its tail padding.
// 3. However, we never want a user object (say `X`) that would follow an
// `expected` to be stored in the padding bytes of the
// underlying {_Tp | _Unex} union, if any. That is because we use
// `construct_at` on that union, which would end up overwriting the `X`
// member if it is stored in the tail padding of the union.
//
// To achieve this, `__expected_base`'s logic is implemented in an inner
// `__repr` class. `__expected_base` holds one `__repr` member which is
// conditionally `[[no_unique_address]]`. The `__repr` class holds the
// underlying {_Tp | _Unex} union and a boolean "has value" flag.
//
// Which one of the `__repr_`/`__union_` members is `[[no_unique_address]]`
// depends on whether the "has value" boolean fits into the tail padding of
// the underlying {_Tp | _Unex} union:
//
// - In case the "has value" bool fits into the tail padding of the union, the
// whole `__repr_` member is _not_ `[[no_unique_address]]` as it needs to be
// transparently replaced on `emplace()`/`swap()` etc.
// - In case the "has value" bool does not fit into the tail padding of the
// union, only the union member must be transparently replaced (therefore is
// _not_ `[[no_unique_address]]`) and the "has value" flag must be adjusted
// manually.
//
// This way, the member that is transparently replaced on mutating operations
// is never `[[no_unique_address]]`, satisfying the requirements from
// "[basic.life]" in the standard.
//
// Stripped away of all superfluous elements, the layout of `__expected_base`
// then looks like this:
//
// template
// class expected_base {
// union union_t {
// [[no_unique_address]] Tp val;
// [[no_unique_address]] Err unex;
// };
//
// static constexpr bool put_flag_in_tail = fits_in_tail_padding;
// static constexpr bool allow_reusing_expected_tail_padding = !put_flag_in_tail;
//
// struct repr {
// private:
// // If "has value" fits into the tail, this should be
// // `[[no_unique_address]]`, otherwise not.
// [[no_unique_address]] conditional_no_unique_address<
// put_flag_in_tail,
// union_t>::type union_;
// [[no_unique_address]] bool has_val_;
// };
//
// protected:
// // If "has value" fits into the tail, this must _not_ be
// // `[[no_unique_address]]` so that we fill out the
// // complete `expected` object.
// [[no_unique_address]] conditional_no_unique_address<
// allow_reusing_expected_tail_padding,
// repr>::type repr_;
// };
//
< /code>
Я выкопал историю коммит и обзор кода и нашел ссылку «Годбалт», которая демонстрирует поведение при использовании пустых структур: < /p>
struct T{};
struct E{};
struct s{
[[no_unique_address]] union{
[[no_unique_address]] T t;
[[no_unique_address]] E e;
} u;
bool b;
};
int main(){
return sizeof(s);
}
Код: Выделить всё
struct T {
int foo;
char bar;
// 3 bytes of tail padding
};
struct E {
char e;
};
union U {
[[no_unique_address]] T t;
[[no_unique_address]] E e;
// When engaged as E, there are 7 bytes of
// trailing padding to hit the 8-byte
// size of T, the largest member of U.
};
template
inline constexpr bool FitsInTailPadding = []() {
struct repr {
[[no_unique_address]] A a;
[[no_unique_address]] B b;
};
return sizeof(repr) == sizeof(A);
}();
int main(){
return FitsInTailPadding;
}
Подробнее здесь: https://stackoverflow.com/questions/796 ... ld-members