aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Kokemüller <jan.kokemueller@gmail.com>2024-01-22 15:05:39 +0100
committerGitHub <noreply@github.com>2024-01-22 09:05:39 -0500
commit4f4690530e8b40cdf3a17c76a352b26c2fb0446c (patch)
treec157c476049d6032b0a734ab92e73dbc6d6734e1
parentbf7b8dae0615884816fff54cac08bc691746b1ee (diff)
downloadllvm-4f4690530e8b40cdf3a17c76a352b26c2fb0446c.zip
llvm-4f4690530e8b40cdf3a17c76a352b26c2fb0446c.tar.gz
llvm-4f4690530e8b40cdf3a17c76a352b26c2fb0446c.tar.bz2
[libc++] Ensure that std::expected has no tail padding (#69673)
Currently std::expected can have some padding bytes in its tail due to [[no_unique_address]]. Those padding bytes can be used by other objects. For example, in the current implementation: sizeof(std::expected<std::optional<int>, bool>) == sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>) As a result, the data layout of an std::expected<std::expected<std::optional<int>, bool>, bool> can look like this: +-- optional "has value" flag | +--padding /---int---\ | | 00 00 00 00 01 00 00 00 | | | +- "outer" expected "has value" flag | +- expected "has value" flag This is problematic because `emplace()`ing the "inner" expected can not only overwrite the "inner" expected "has value" flag (issue #68552) but also the tail padding where other objects might live. This patch fixes the problem by ensuring that std::expected has no tail padding, which is achieved by conditional usage of [[no_unique_address]] based on the tail padding that this would create. This is an ABI breaking change because the following property changes: sizeof(std::expected<std::optional<int>, bool>) < sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>) Before the change, this relation didn't hold. After the change, the relation does hold, which means that the size of std::expected in these cases increases after this patch. The data layout will change in the following cases where tail padding can be reused by other objects: class foo : std::expected<std::optional<int>, bool> { bool b; }; or using [[no_unique_address]]: struct foo { [[no_unique_address]] std::expected<std::optional<int>, bool> e; bool b; }; The vendor communication is handled in #70820. Fixes: #70494 Co-authored-by: philnik777 <nikolasklauser@berlin.de> Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
-rw-r--r--libcxx/docs/ReleaseNotes/18.rst22
-rw-r--r--libcxx/include/__expected/expected.h1140
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp45
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp7
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp38
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp6
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp14
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp23
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp23
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp14
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp4
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp4
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp16
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp17
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp4
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/types.h137
20 files changed, 1201 insertions, 401 deletions
diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index 7d6331a..c619f94 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -284,6 +284,28 @@ ABI Affecting Changes
against different configurations of it being used in different translation
units.
+- The amount of padding bytes available for use at the end of certain ``std::expected`` instantiations has changed in this
+ release. This is an ABI break for any code that held a ``std::expected`` member with ``[[no_unique_address]]`` in an
+ ABI-facing type. In those cases, the layout of the enclosing type will change, breaking the ABI. However, the
+ ``std::expected<T, E>`` member requires a few characteristics in order to be affected by this change:
+
+ - A type equivalent to ``union {T ; E}`` needs to have more than one byte of padding available.
+ - The ``std::expected<T, E>`` member must have been in a situation where its padding bytes were previously reused by
+ another object, which can happen in a few cases (this is probably not exhaustive):
+
+ - It is a member with ``[[no_unique_address]]`` applied to it, and it is followed by another data member, or
+ - It is a member with ``[[no_unique_address]]`` applied to it, and it is the last member of the user-defined type,
+ and that user-defined type is used in ways that its padding bytes can be reused, or
+ - It is inherited from
+
+ We expect that this will not be a very frequent occurrence. However, there is unfortunately no technique we can use
+ in the library to catch such misuse. Indeed, even applying an ABI tag to ``std::expected`` would not help since ABI
+ tags are not propagated to containing types. As a result, if you notice very difficult to explain bugs around the
+ usage of a ``std::expected``, you should consider checking whether you are hitting this ABI break. This change was
+ done to fix `#70494 <https://github.com/llvm/llvm-project/issues/70494>`_ and the vendor communication is handled
+ in `#70820 <https://github.com/llvm/llvm-project/issues/70820>`_.
+
+
Build System Changes
--------------------
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 9f36c41..443d925 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -88,8 +88,372 @@ _LIBCPP_HIDE_FROM_ABI void __throw_bad_expected_access(_Arg&& __arg) {
# endif
}
+// If parameter type `_Tp` of `__conditional_no_unique_address` is neither
+// copyable nor movable, a constructor with this tag is provided. For that
+// constructor, the user has to provide a function and arguments. The function
+// must return an object of type `_Tp`. When the function is invoked by the
+// constructor, guaranteed copy elision kicks in and the `_Tp` is constructed
+// in place.
+struct __conditional_no_unique_address_invoke_tag {};
+
+// This class implements an object with `[[no_unique_address]]` conditionally applied to it,
+// based on the value of `_NoUnique`.
+//
+// A member of this class must always have `[[no_unique_address]]` applied to
+// it. Otherwise, the `[[no_unique_address]]` in the "`_NoUnique == true`" case
+// would not have any effect. In the `false` case, the `__v` is not
+// `[[no_unique_address]]`, so nullifies the effects of the "outer"
+// `[[no_unique_address]]` regarding data layout.
+//
+// If we had a language feature, this class would basically be replaced by `[[no_unique_address(condition)]]`.
+template <bool _NoUnique, class _Tp>
+struct __conditional_no_unique_address;
+
+template <class _Tp>
+struct __conditional_no_unique_address<true, _Tp> {
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __conditional_no_unique_address(in_place_t, _Args&&... __args)
+ : __v(std::forward<_Args>(__args)...) {}
+
+ template <class _Func, class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __conditional_no_unique_address(
+ __conditional_no_unique_address_invoke_tag, _Func&& __f, _Args&&... __args)
+ : __v(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
+
+ _LIBCPP_NO_UNIQUE_ADDRESS _Tp __v;
+};
+
+template <class _Tp>
+struct __conditional_no_unique_address<false, _Tp> {
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __conditional_no_unique_address(in_place_t, _Args&&... __args)
+ : __v(std::forward<_Args>(__args)...) {}
+
+ template <class _Func, class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __conditional_no_unique_address(
+ __conditional_no_unique_address_invoke_tag, _Func&& __f, _Args&&... __args)
+ : __v(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
+
+ _Tp __v;
+};
+
+// This function returns whether the type `_Second` can be stuffed into the tail padding
+// of the `_First` type if both of them are given `[[no_unique_address]]`.
+template <class _First, class _Second>
+inline constexpr bool __fits_in_tail_padding = []() {
+ struct __x {
+ _LIBCPP_NO_UNIQUE_ADDRESS _First __first;
+ _LIBCPP_NO_UNIQUE_ADDRESS _Second __second;
+ };
+ return sizeof(__x) == sizeof(_First);
+}();
+
+// 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<_Tp, _Unex>` 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<_Tp, _Unex>` 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 Tp, class Err>
+// 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<union_t, bool>;
+// 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_;
+// };
+//
template <class _Tp, class _Err>
-class expected {
+class __expected_base {
+ // use named union because [[no_unique_address]] cannot be applied to an unnamed union,
+ // also guaranteed elision into a potentially-overlapping subobject is unsettled (and
+ // it's not clear that it's implementable, given that the function is allowed to clobber
+ // the tail padding) - see https://github.com/itanium-cxx-abi/cxx-abi/issues/107.
+ union __union_t {
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&)
+ requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err> &&
+ is_trivially_copy_constructible_v<_Tp> && is_trivially_copy_constructible_v<_Err>)
+ = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(__union_t&&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(__union_t&&)
+ requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err> &&
+ is_trivially_move_constructible_v<_Tp> && is_trivially_move_constructible_v<_Err>)
+ = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(__union_t&&) = delete;
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(in_place_t, _Args&&... __args)
+ : __val_(std::forward<_Args>(__args)...) {}
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(unexpect_t, _Args&&... __args)
+ : __unex_(std::forward<_Args>(__args)...) {}
+
+ template <class _Func, class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
+ std::__expected_construct_in_place_from_invoke_tag, _Func&& __f, _Args&&... __args)
+ : __val_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
+
+ template <class _Func, class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
+ std::__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
+ : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
+
+ _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
+ requires(is_trivially_destructible_v<_Tp> && is_trivially_destructible_v<_Err>)
+ = default;
+
+ // __repr's destructor handles this
+ _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t() {}
+
+ _LIBCPP_NO_UNIQUE_ADDRESS _Tp __val_;
+ _LIBCPP_NO_UNIQUE_ADDRESS _Err __unex_;
+ };
+
+ static constexpr bool __put_flag_in_tail = __fits_in_tail_padding<__union_t, bool>;
+ static constexpr bool __allow_reusing_expected_tail_padding = !__put_flag_in_tail;
+
+ struct __repr {
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr() = delete;
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(in_place_t __tag, _Args&&... __args)
+ : __union_(in_place, __tag, std::forward<_Args>(__args)...), __has_val_(true) {}
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(unexpect_t __tag, _Args&&... __args)
+ : __union_(in_place, __tag, std::forward<_Args>(__args)...), __has_val_(false) {}
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(std::__expected_construct_in_place_from_invoke_tag __tag,
+ _Args&&... __args)
+ : __union_(in_place, __tag, std::forward<_Args>(__args)...), __has_val_(true) {}
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(std::__expected_construct_unexpected_from_invoke_tag __tag,
+ _Args&&... __args)
+ : __union_(in_place, __tag, std::forward<_Args>(__args)...), __has_val_(false) {}
+
+ // The return value of `__make_union` must be constructed in place in the
+ // `__v` member of `__union_`, relying on guaranteed copy elision. To do
+ // this, the `__conditional_no_unique_address_invoke_tag` constructor is
+ // called with a lambda that is immediately called inside
+ // `__conditional_no_unique_address`'s constructor.
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(bool __has_val, _OtherUnion&& __other)
+ requires(__allow_reusing_expected_tail_padding)
+ : __union_(__conditional_no_unique_address_invoke_tag{},
+ [&] { return __make_union(__has_val, std::forward<_OtherUnion>(__other)); }),
+ __has_val_(__has_val) {}
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(const __repr&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(const __repr&)
+ requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err> &&
+ is_trivially_copy_constructible_v<_Tp> && is_trivially_copy_constructible_v<_Err>)
+ = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(__repr&&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(__repr&&)
+ requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err> &&
+ is_trivially_move_constructible_v<_Tp> && is_trivially_move_constructible_v<_Err>)
+ = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr& operator=(const __repr&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr& operator=(__repr&&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr ~__repr()
+ requires(is_trivially_destructible_v<_Tp> && is_trivially_destructible_v<_Err>)
+ = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr ~__repr()
+ requires(!is_trivially_destructible_v<_Tp> || !is_trivially_destructible_v<_Err>)
+ {
+ __destroy_union_member();
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_union()
+ requires(__allow_reusing_expected_tail_padding &&
+ (is_trivially_destructible_v<_Tp> && is_trivially_destructible_v<_Err>))
+ {
+ // Note: Since the destructor of the union is trivial, this does nothing
+ // except to end the lifetime of the union.
+ std::destroy_at(&__union_.__v);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_union()
+ requires(__allow_reusing_expected_tail_padding &&
+ (!is_trivially_destructible_v<_Tp> || !is_trivially_destructible_v<_Err>))
+ {
+ __destroy_union_member();
+ std::destroy_at(&__union_.__v);
+ }
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __construct_union(in_place_t, _Args&&... __args)
+ requires(__allow_reusing_expected_tail_padding)
+ {
+ std::construct_at(&__union_.__v, in_place, std::forward<_Args>(__args)...);
+ __has_val_ = true;
+ }
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __construct_union(unexpect_t, _Args&&... __args)
+ requires(__allow_reusing_expected_tail_padding)
+ {
+ std::construct_at(&__union_.__v, unexpect, std::forward<_Args>(__args)...);
+ __has_val_ = false;
+ }
+
+ private:
+ template <class, class>
+ friend class __expected_base;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_union_member()
+ requires(!is_trivially_destructible_v<_Tp> || !is_trivially_destructible_v<_Err>)
+ {
+ if (__has_val_) {
+ std::destroy_at(std::addressof(__union_.__v.__val_));
+ } else {
+ std::destroy_at(std::addressof(__union_.__v.__unex_));
+ }
+ }
+
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI static constexpr __union_t __make_union(bool __has_val, _OtherUnion&& __other)
+ requires(__allow_reusing_expected_tail_padding)
+ {
+ if (__has_val)
+ return __union_t(in_place, std::forward<_OtherUnion>(__other).__val_);
+ else
+ return __union_t(unexpect, std::forward<_OtherUnion>(__other).__unex_);
+ }
+
+ _LIBCPP_NO_UNIQUE_ADDRESS __conditional_no_unique_address<__put_flag_in_tail, __union_t> __union_;
+ _LIBCPP_NO_UNIQUE_ADDRESS bool __has_val_;
+ };
+
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI static constexpr __repr __make_repr(bool __has_val, _OtherUnion&& __other)
+ requires(__put_flag_in_tail)
+ {
+ if (__has_val)
+ return __repr(in_place, std::forward<_OtherUnion>(__other).__val_);
+ else
+ return __repr(unexpect, std::forward<_OtherUnion>(__other).__unex_);
+ }
+
+protected:
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __expected_base(_Args&&... __args)
+ : __repr_(in_place, std::forward<_Args>(__args)...) {}
+
+ // In case we copy/move construct from another `expected` we need to create
+ // our `expected` so that it either has a value or not, depending on the "has
+ // value" flag of the other `expected`. To do this without falling back on
+ // `std::construct_at` we rely on guaranteed copy elision using two helper
+ // functions `__make_repr` and `__make_union`. There have to be two since
+ // there are two data layouts with different members being
+ // `[[no_unique_address]]`. GCC (as of version 13) does not do guaranteed
+ // copy elision when initializing `[[no_unique_address]]` members. The two
+ // cases are:
+ //
+ // - `__make_repr`: This is used when the "has value" flag lives in the tail
+ // of the union. In this case, the `__repr` member is _not_
+ // `[[no_unique_address]]`.
+ // - `__make_union`: When the "has value" flag does _not_ fit in the tail of
+ // the union, the `__repr` member is `[[no_unique_address]]` and the union
+ // is not.
+ //
+ // This constructor "catches" the first case and leaves the second case to
+ // `__union_t`, its constructors and `__make_union`.
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __expected_base(bool __has_val, _OtherUnion&& __other)
+ requires(__put_flag_in_tail)
+ : __repr_(__conditional_no_unique_address_invoke_tag{},
+ [&] { return __make_repr(__has_val, std::forward<_OtherUnion>(__other)); }) {}
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy() {
+ if constexpr (__put_flag_in_tail)
+ std::destroy_at(&__repr_.__v);
+ else
+ __repr_.__v.__destroy_union();
+ }
+
+ template <class _Tag, class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __construct(_Tag __tag, _Args&&... __args) {
+ if constexpr (__put_flag_in_tail)
+ std::construct_at(&__repr_.__v, __tag, std::forward<_Args>(__args)...);
+ else
+ __repr_.__v.__construct_union(__tag, std::forward<_Args>(__args)...);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr bool __has_val() const { return __repr_.__v.__has_val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t& __union() { return __repr_.__v.__union_.__v; }
+ _LIBCPP_HIDE_FROM_ABI constexpr const __union_t& __union() const { return __repr_.__v.__union_.__v; }
+ _LIBCPP_HIDE_FROM_ABI constexpr _Tp& __val() { return __repr_.__v.__union_.__v.__val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& __val() const { return __repr_.__v.__union_.__v.__val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr _Err& __unex() { return __repr_.__v.__union_.__v.__unex_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr const _Err& __unex() const { return __repr_.__v.__union_.__v.__unex_; }
+
+private:
+ _LIBCPP_NO_UNIQUE_ADDRESS __conditional_no_unique_address<__allow_reusing_expected_tail_padding, __repr> __repr_;
+};
+
+template <class _Tp, class _Err>
+class expected : private __expected_base<_Tp, _Err> {
static_assert(!is_reference_v<_Tp> && !is_function_v<_Tp> && !is_same_v<remove_cv_t<_Tp>, in_place_t> &&
!is_same_v<remove_cv_t<_Tp>, unexpect_t> && !__is_std_unexpected<remove_cv_t<_Tp>>::value &&
__valid_std_unexpected<_Err>::value,
@@ -102,6 +466,8 @@ class expected {
template <class _Up, class _OtherErr>
friend class expected;
+ using __base = __expected_base<_Tp, _Err>;
+
public:
using value_type = _Tp;
using error_type = _Err;
@@ -113,7 +479,7 @@ public:
// [expected.object.ctor], constructors
_LIBCPP_HIDE_FROM_ABI constexpr expected() noexcept(is_nothrow_default_constructible_v<_Tp>) // strengthened
requires is_default_constructible_v<_Tp>
- : __union_(std::in_place), __has_val_(true) {}
+ : __base(in_place) {}
_LIBCPP_HIDE_FROM_ABI constexpr expected(const expected&) = delete;
@@ -126,7 +492,7 @@ public:
is_nothrow_copy_constructible_v<_Tp> && is_nothrow_copy_constructible_v<_Err>) // strengthened
requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err> &&
!(is_trivially_copy_constructible_v<_Tp> && is_trivially_copy_constructible_v<_Err>))
- : __union_(__other.__has_val_, __other.__union_), __has_val_(__other.__has_val_) {}
+ : __base(__other.__has_val(), __other.__union()) {}
_LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err> && is_trivially_move_constructible_v<_Tp> &&
@@ -137,7 +503,7 @@ public:
is_nothrow_move_constructible_v<_Tp> && is_nothrow_move_constructible_v<_Err>)
requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err> &&
!(is_trivially_move_constructible_v<_Tp> && is_trivially_move_constructible_v<_Err>))
- : __union_(__other.__has_val_, std::move(__other.__union_)), __has_val_(__other.__has_val_) {}
+ : __base(__other.__has_val(), std::move(__other.__union())) {}
private:
template <class _Up, class _OtherErr, class _UfQual, class _OtherErrQual>
@@ -162,12 +528,12 @@ private:
template <class _Func, class... _Args>
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(
std::__expected_construct_in_place_from_invoke_tag __tag, _Func&& __f, _Args&&... __args)
- : __union_(__tag, std::forward<_Func>(__f), std::forward<_Args>(__args)...), __has_val_(true) {}
+ : __base(__tag, std::forward<_Func>(__f), std::forward<_Args>(__args)...) {}
template <class _Func, class... _Args>
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(
std::__expected_construct_unexpected_from_invoke_tag __tag, _Func&& __f, _Args&&... __args)
- : __union_(__tag, std::forward<_Func>(__f), std::forward<_Args>(__args)...), __has_val_(false) {}
+ : __base(__tag, std::forward<_Func>(__f), std::forward<_Args>(__args)...) {}
public:
template <class _Up, class _OtherErr>
@@ -177,14 +543,14 @@ public:
expected(const expected<_Up, _OtherErr>& __other) noexcept(
is_nothrow_constructible_v<_Tp, const _Up&> &&
is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
- : __union_(__other.__has_val_, __other.__union_), __has_val_(__other.__has_val_) {}
+ : __base(__other.__has_val(), __other.__union()) {}
template <class _Up, class _OtherErr>
requires __can_convert<_Up, _OtherErr, _Up, _OtherErr>::value
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp> || !is_convertible_v<_OtherErr, _Err>)
expected(expected<_Up, _OtherErr>&& __other) noexcept(
is_nothrow_constructible_v<_Tp, _Up> && is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
- : __union_(__other.__has_val_, std::move(__other.__union_)), __has_val_(__other.__has_val_) {}
+ : __base(__other.__has_val(), std::move(__other.__union())) {}
template <class _Up = _Tp>
requires(!is_same_v<remove_cvref_t<_Up>, in_place_t> && !is_same_v<expected, remove_cvref_t<_Up>> &&
@@ -192,80 +558,67 @@ public:
(!is_same_v<remove_cv_t<_Tp>, bool> || !__is_std_expected<remove_cvref_t<_Up>>::value))
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp>)
expected(_Up&& __u) noexcept(is_nothrow_constructible_v<_Tp, _Up>) // strengthened
- : __union_(std::in_place, std::forward<_Up>(__u)), __has_val_(true) {}
+ : __base(in_place, std::forward<_Up>(__u)) {}
template <class _OtherErr>
requires is_constructible_v<_Err, const _OtherErr&>
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>) expected(
const unexpected<_OtherErr>& __unex) noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
- : __union_(std::unexpect, __unex.error()), __has_val_(false) {}
+ : __base(unexpect, __unex.error()) {}
template <class _OtherErr>
requires is_constructible_v<_Err, _OtherErr>
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
expected(unexpected<_OtherErr>&& __unex) noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
- : __union_(std::unexpect, std::move(__unex.error())), __has_val_(false) {}
+ : __base(unexpect, std::move(__unex.error())) {}
template <class... _Args>
requires is_constructible_v<_Tp, _Args...>
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t, _Args&&... __args) noexcept(
is_nothrow_constructible_v<_Tp, _Args...>) // strengthened
- : __union_(std::in_place, std::forward<_Args>(__args)...), __has_val_(true) {}
+ : __base(in_place, std::forward<_Args>(__args)...) {}
template <class _Up, class... _Args>
requires is_constructible_v< _Tp, initializer_list<_Up>&, _Args... >
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t, initializer_list<_Up> __il, _Args&&... __args) noexcept(
is_nothrow_constructible_v<_Tp, initializer_list<_Up>&, _Args...>) // strengthened
- : __union_(std::in_place, __il, std::forward<_Args>(__args)...), __has_val_(true) {}
+ : __base(in_place, __il, std::forward<_Args>(__args)...) {}
template <class... _Args>
requires is_constructible_v<_Err, _Args...>
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args) noexcept(
is_nothrow_constructible_v<_Err, _Args...>) // strengthened
- : __union_(std::unexpect, std::forward<_Args>(__args)...), __has_val_(false) {}
+ : __base(unexpect, std::forward<_Args>(__args)...) {}
template <class _Up, class... _Args>
requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args) noexcept(
is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
- : __union_(std::unexpect, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
+ : __base(unexpect, __il, std::forward<_Args>(__args)...) {}
// [expected.object.dtor], destructor
- _LIBCPP_HIDE_FROM_ABI constexpr ~expected()
- requires(is_trivially_destructible_v<_Tp> && is_trivially_destructible_v<_Err>)
- = default;
-
- _LIBCPP_HIDE_FROM_ABI constexpr ~expected()
- requires(!is_trivially_destructible_v<_Tp> || !is_trivially_destructible_v<_Err>)
- {
- if (__has_val_) {
- std::destroy_at(std::addressof(__union_.__val_));
- } else {
- std::destroy_at(std::addressof(__union_.__unex_));
- }
- }
+ _LIBCPP_HIDE_FROM_ABI constexpr ~expected() = default;
private:
- template <class _T1, class _T2, class... _Args>
- _LIBCPP_HIDE_FROM_ABI static constexpr void __reinit_expected(_T1& __newval, _T2& __oldval, _Args&&... __args) {
+ template <class _Tag, class _OtherTag, class _T1, class _T2, class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __reinit_expected(_T2& __oldval, _Args&&... __args) {
if constexpr (is_nothrow_constructible_v<_T1, _Args...>) {
- std::destroy_at(std::addressof(__oldval));
- std::construct_at(std::addressof(__newval), std::forward<_Args>(__args)...);
+ this->__destroy();
+ this->__construct(_Tag{}, std::forward<_Args>(__args)...);
} else if constexpr (is_nothrow_move_constructible_v<_T1>) {
_T1 __tmp(std::forward<_Args>(__args)...);
- std::destroy_at(std::addressof(__oldval));
- std::construct_at(std::addressof(__newval), std::move(__tmp));
+ this->__destroy();
+ this->__construct(_Tag{}, std::move(__tmp));
} else {
static_assert(
is_nothrow_move_constructible_v<_T2>,
"To provide strong exception guarantee, T2 has to satisfy `is_nothrow_move_constructible_v` so that it can "
"be reverted to the previous state in case an exception is thrown during the assignment.");
_T2 __tmp(std::move(__oldval));
- std::destroy_at(std::addressof(__oldval));
- auto __trans =
- std::__make_exception_guard([&] { std::construct_at(std::addressof(__oldval), std::move(__tmp)); });
- std::construct_at(std::addressof(__newval), std::forward<_Args>(__args)...);
+ this->__destroy();
+ auto __trans = std::__make_exception_guard([&] { this->__construct(_OtherTag{}, std::move(__tmp)); });
+ this->__construct(_Tag{}, std::forward<_Args>(__args)...);
__trans.__complete();
}
}
@@ -281,17 +634,15 @@ public:
is_copy_constructible_v<_Err> &&
(is_nothrow_move_constructible_v<_Tp> || is_nothrow_move_constructible_v<_Err>))
{
- if (__has_val_ && __rhs.__has_val_) {
- __union_.__val_ = __rhs.__union_.__val_;
- } else if (__has_val_) {
- __reinit_expected(__union_.__unex_, __union_.__val_, __rhs.__union_.__unex_);
- } else if (__rhs.__has_val_) {
- __reinit_expected(__union_.__val_, __union_.__unex_, __rhs.__union_.__val_);
+ if (this->__has_val() && __rhs.__has_val()) {
+ this->__val() = __rhs.__val();
+ } else if (this->__has_val()) {
+ __reinit_expected<unexpect_t, in_place_t, _Err, _Tp>(this->__val(), __rhs.__unex());
+ } else if (__rhs.__has_val()) {
+ __reinit_expected<in_place_t, unexpect_t, _Tp, _Err>(this->__unex(), __rhs.__val());
} else {
- __union_.__unex_ = __rhs.__union_.__unex_;
+ this->__unex() = __rhs.__unex();
}
- // note: only reached if no exception+rollback was done inside __reinit_expected
- __has_val_ = __rhs.__has_val_;
return *this;
}
@@ -302,17 +653,15 @@ public:
is_move_assignable_v<_Err> &&
(is_nothrow_move_constructible_v<_Tp> || is_nothrow_move_constructible_v<_Err>))
{
- if (__has_val_ && __rhs.__has_val_) {
- __union_.__val_ = std::move(__rhs.__union_.__val_);
- } else if (__has_val_) {
- __reinit_expected(__union_.__unex_, __union_.__val_, std::move(__rhs.__union_.__unex_));
- } else if (__rhs.__has_val_) {
- __reinit_expected(__union_.__val_, __union_.__unex_, std::move(__rhs.__union_.__val_));
+ if (this->__has_val() && __rhs.__has_val()) {
+ this->__val() = std::move(__rhs.__val());
+ } else if (this->__has_val()) {
+ __reinit_expected<unexpect_t, in_place_t, _Err, _Tp>(this->__val(), std::move(__rhs.__unex()));
+ } else if (__rhs.__has_val()) {
+ __reinit_expected<in_place_t, unexpect_t, _Tp, _Err>(this->__unex(), std::move(__rhs.__val()));
} else {
- __union_.__unex_ = std::move(__rhs.__union_.__unex_);
+ this->__unex() = std::move(__rhs.__unex());
}
- // note: only reached if no exception+rollback was done inside __reinit_expected
- __has_val_ = __rhs.__has_val_;
return *this;
}
@@ -323,11 +672,10 @@ public:
(is_nothrow_constructible_v<_Tp, _Up> || is_nothrow_move_constructible_v<_Tp> ||
is_nothrow_move_constructible_v<_Err>))
{
- if (__has_val_) {
- __union_.__val_ = std::forward<_Up>(__v);
+ if (this->__has_val()) {
+ this->__val() = std::forward<_Up>(__v);
} else {
- __reinit_expected(__union_.__val_, __union_.__unex_, std::forward<_Up>(__v));
- __has_val_ = true;
+ __reinit_expected<in_place_t, unexpect_t, _Tp, _Err>(this->__unex(), std::forward<_Up>(__v));
}
return *this;
}
@@ -346,11 +694,10 @@ public:
template <class _OtherErr>
requires(__can_assign_from_unexpected<const _OtherErr&>)
_LIBCPP_HIDE_FROM_ABI constexpr expected& operator=(const unexpected<_OtherErr>& __un) {
- if (__has_val_) {
- __reinit_expected(__union_.__unex_, __union_.__val_, __un.error());
- __has_val_ = false;
+ if (this->__has_val()) {
+ __reinit_expected<unexpect_t, in_place_t, _Err, _Tp>(this->__val(), __un.error());
} else {
- __union_.__unex_ = __un.error();
+ this->__unex() = __un.error();
}
return *this;
}
@@ -358,11 +705,10 @@ public:
template <class _OtherErr>
requires(__can_assign_from_unexpected<_OtherErr>)
_LIBCPP_HIDE_FROM_ABI constexpr expected& operator=(unexpected<_OtherErr>&& __un) {
- if (__has_val_) {
- __reinit_expected(__union_.__unex_, __union_.__val_, std::move(__un.error()));
- __has_val_ = false;
+ if (this->__has_val()) {
+ __reinit_expected<unexpect_t, in_place_t, _Err, _Tp>(this->__val(), std::move(__un.error()));
} else {
- __union_.__unex_ = std::move(__un.error());
+ this->__unex() = std::move(__un.error());
}
return *this;
}
@@ -370,27 +716,17 @@ public:
template <class... _Args>
requires is_nothrow_constructible_v<_Tp, _Args...>
_LIBCPP_HIDE_FROM_ABI constexpr _Tp& emplace(_Args&&... __args) noexcept {
- if (__has_val_) {
- std::destroy_at(std::addressof(__union_.__val_));
- } else {
- std::destroy_at(std::addressof(__union_.__unex_));
- }
- std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...);
- __has_val_ = true;
- return __union_.__val_;
+ this->__destroy();
+ this->__construct(in_place, std::forward<_Args>(__args)...);
+ return this->__val();
}
template <class _Up, class... _Args>
- requires is_nothrow_constructible_v< _Tp, initializer_list<_Up>&, _Args... >
+ requires is_nothrow_constructible_v<_Tp, initializer_list<_Up>&, _Args...>
_LIBCPP_HIDE_FROM_ABI constexpr _Tp& emplace(initializer_list<_Up> __il, _Args&&... __args) noexcept {
- if (__has_val_) {
- std::destroy_at(std::addressof(__union_.__val_));
- } else {
- std::destroy_at(std::addressof(__union_.__unex_));
- }
- std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...);
- __has_val_ = true;
- return __union_.__val_;
+ this->__destroy();
+ this->__construct(in_place, __il, std::forward<_Args>(__args)...);
+ return this->__val();
}
public:
@@ -402,48 +738,42 @@ public:
is_move_constructible_v<_Err> &&
(is_nothrow_move_constructible_v<_Tp> || is_nothrow_move_constructible_v<_Err>))
{
- auto __swap_val_unex_impl = [&](expected& __with_val, expected& __with_err) {
+ auto __swap_val_unex_impl = [](expected& __with_val, expected& __with_err) {
if constexpr (is_nothrow_move_constructible_v<_Err>) {
- _Err __tmp(std::move(__with_err.__union_.__unex_));
- std::destroy_at(std::addressof(__with_err.__union_.__unex_));
- auto __trans = std::__make_exception_guard([&] {
- std::construct_at(std::addressof(__with_err.__union_.__unex_), std::move(__tmp));
- });
- std::construct_at(std::addressof(__with_err.__union_.__val_), std::move(__with_val.__union_.__val_));
+ _Err __tmp(std::move(__with_err.__unex()));
+ __with_err.__destroy();
+ auto __trans = std::__make_exception_guard([&] { __with_err.__construct(unexpect, std::move(__tmp)); });
+ __with_err.__construct(in_place, std::move(__with_val.__val()));
__trans.__complete();
- std::destroy_at(std::addressof(__with_val.__union_.__val_));
- std::construct_at(std::addressof(__with_val.__union_.__unex_), std::move(__tmp));
+ __with_val.__destroy();
+ __with_val.__construct(unexpect, std::move(__tmp));
} else {
static_assert(is_nothrow_move_constructible_v<_Tp>,
"To provide strong exception guarantee, Tp has to satisfy `is_nothrow_move_constructible_v` so "
"that it can be reverted to the previous state in case an exception is thrown during swap.");
- _Tp __tmp(std::move(__with_val.__union_.__val_));
- std::destroy_at(std::addressof(__with_val.__union_.__val_));
- auto __trans = std::__make_exception_guard([&] {
- std::construct_at(std::addressof(__with_val.__union_.__val_), std::move(__tmp));
- });
- std::construct_at(std::addressof(__with_val.__union_.__unex_), std::move(__with_err.__union_.__unex_));
+ _Tp __tmp(std::move(__with_val.__val()));
+ __with_val.__destroy();
+ auto __trans = std::__make_exception_guard([&] { __with_val.__construct(in_place, std::move(__tmp)); });
+ __with_val.__construct(unexpect, std::move(__with_err.__unex()));
__trans.__complete();
- std::destroy_at(std::addressof(__with_err.__union_.__unex_));
- std::construct_at(std::addressof(__with_err.__union_.__val_), std::move(__tmp));
+ __with_err.__destroy();
+ __with_err.__construct(in_place, std::move(__tmp));
}
- __with_val.__has_val_ = false;
- __with_err.__has_val_ = true;
};
- if (__has_val_) {
- if (__rhs.__has_val_) {
+ if (this->__has_val()) {
+ if (__rhs.__has_val()) {
using std::swap;
- swap(__union_.__val_, __rhs.__union_.__val_);
+ swap(this->__val(), __rhs.__val());
} else {
__swap_val_unex_impl(*this, __rhs);
}
} else {
- if (__rhs.__has_val_) {
+ if (__rhs.__has_val()) {
__swap_val_unex_impl(__rhs, *this);
} else {
using std::swap;
- swap(__union_.__unex_, __rhs.__union_.__unex_);
+ swap(this->__unex(), __rhs.__unex());
}
}
}
@@ -456,105 +786,115 @@ public:
// [expected.object.obs], observers
_LIBCPP_HIDE_FROM_ABI constexpr const _Tp* operator->() const noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__has_val_, "expected::operator-> requires the expected to contain a value");
- return std::addressof(__union_.__val_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ this->__has_val(), "expected::operator-> requires the expected to contain a value");
+ return std::addressof(this->__val());
}
_LIBCPP_HIDE_FROM_ABI constexpr _Tp* operator->() noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__has_val_, "expected::operator-> requires the expected to contain a value");
- return std::addressof(__union_.__val_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ this->__has_val(), "expected::operator-> requires the expected to contain a value");
+ return std::addressof(this->__val());
}
_LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__has_val_, "expected::operator* requires the expected to contain a value");
- return __union_.__val_;
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ this->__has_val(), "expected::operator* requires the expected to contain a value");
+ return this->__val();
}
_LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__has_val_, "expected::operator* requires the expected to contain a value");
- return __union_.__val_;
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ this->__has_val(), "expected::operator* requires the expected to contain a value");
+ return this->__val();
}
_LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__has_val_, "expected::operator* requires the expected to contain a value");
- return std::move(__union_.__val_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ this->__has_val(), "expected::operator* requires the expected to contain a value");
+ return std::move(this->__val());
}
_LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__has_val_, "expected::operator* requires the expected to contain a value");
- return std::move(__union_.__val_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ this->__has_val(), "expected::operator* requires the expected to contain a value");
+ return std::move(this->__val());
}
- _LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __has_val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return this->__has_val(); }
- _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __has_val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return this->__has_val(); }
_LIBCPP_HIDE_FROM_ABI constexpr const _Tp& value() const& {
static_assert(is_copy_constructible_v<_Err>, "error_type has to be copy constructible");
- if (!__has_val_) {
+ if (!this->__has_val()) {
std::__throw_bad_expected_access<_Err>(std::as_const(error()));
}
- return __union_.__val_;
+ return this->__val();
}
_LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() & {
static_assert(is_copy_constructible_v<_Err>, "error_type has to be copy constructible");
- if (!__has_val_) {
+ if (!this->__has_val()) {
std::__throw_bad_expected_access<_Err>(std::as_const(error()));
}
- return __union_.__val_;
+ return this->__val();
}
_LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& value() const&& {
static_assert(is_copy_constructible_v<_Err> && is_constructible_v<_Err, decltype(std::move(error()))>,
"error_type has to be both copy constructible and constructible from decltype(std::move(error()))");
- if (!__has_val_) {
+ if (!this->__has_val()) {
std::__throw_bad_expected_access<_Err>(std::move(error()));
}
- return std::move(__union_.__val_);
+ return std::move(this->__val());
}
_LIBCPP_HIDE_FROM_ABI constexpr _Tp&& value() && {
static_assert(is_copy_constructible_v<_Err> && is_constructible_v<_Err, decltype(std::move(error()))>,
"error_type has to be both copy constructible and constructible from decltype(std::move(error()))");
- if (!__has_val_) {
+ if (!this->__has_val()) {
std::__throw_bad_expected_access<_Err>(std::move(error()));
}
- return std::move(__union_.__val_);
+ return std::move(this->__val());
}
_LIBCPP_HIDE_FROM_ABI constexpr const _Err& error() const& noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return __union_.__unex_;
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return this->__unex();
}
_LIBCPP_HIDE_FROM_ABI constexpr _Err& error() & noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return __union_.__unex_;
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return this->__unex();
}
_LIBCPP_HIDE_FROM_ABI constexpr const _Err&& error() const&& noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return std::move(__union_.__unex_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return std::move(this->__unex());
}
_LIBCPP_HIDE_FROM_ABI constexpr _Err&& error() && noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return std::move(__union_.__unex_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return std::move(this->__unex());
}
template <class _Up>
_LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) const& {
static_assert(is_copy_constructible_v<_Tp>, "value_type has to be copy constructible");
static_assert(is_convertible_v<_Up, _Tp>, "argument has to be convertible to value_type");
- return __has_val_ ? __union_.__val_ : static_cast<_Tp>(std::forward<_Up>(__v));
+ return this->__has_val() ? this->__val() : static_cast<_Tp>(std::forward<_Up>(__v));
}
template <class _Up>
_LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
static_assert(is_move_constructible_v<_Tp>, "value_type has to be move constructible");
static_assert(is_convertible_v<_Up, _Tp>, "argument has to be convertible to value_type");
- return __has_val_ ? std::move(__union_.__val_) : static_cast<_Tp>(std::forward<_Up>(__v));
+ return this->__has_val() ? std::move(this->__val()) : static_cast<_Tp>(std::forward<_Up>(__v));
}
template <class _Up = _Err>
@@ -584,7 +924,7 @@ public:
static_assert(is_same_v<typename _Up::error_type, _Err>,
"The result of f(**this) must have the same error_type as this expected");
if (has_value()) {
- return std::invoke(std::forward<_Func>(__f), __union_.__val_);
+ return std::invoke(std::forward<_Func>(__f), this->__val());
}
return _Up(unexpect, error());
}
@@ -597,7 +937,7 @@ public:
static_assert(is_same_v<typename _Up::error_type, _Err>,
"The result of f(**this) must have the same error_type as this expected");
if (has_value()) {
- return std::invoke(std::forward<_Func>(__f), __union_.__val_);
+ return std::invoke(std::forward<_Func>(__f), this->__val());
}
return _Up(unexpect, error());
}
@@ -611,7 +951,7 @@ public:
static_assert(is_same_v<typename _Up::error_type, _Err>,
"The result of f(std::move(**this)) must have the same error_type as this expected");
if (has_value()) {
- return std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
+ return std::invoke(std::forward<_Func>(__f), std::move(this->__val()));
}
return _Up(unexpect, std::move(error()));
}
@@ -625,7 +965,7 @@ public:
static_assert(is_same_v<typename _Up::error_type, _Err>,
"The result of f(std::move(**this)) must have the same error_type as this expected");
if (has_value()) {
- return std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
+ return std::invoke(std::forward<_Func>(__f), std::move(this->__val()));
}
return _Up(unexpect, std::move(error()));
}
@@ -638,7 +978,7 @@ public:
static_assert(is_same_v<typename _Gp::value_type, _Tp>,
"The result of f(error()) must have the same value_type as this expected");
if (has_value()) {
- return _Gp(in_place, __union_.__val_);
+ return _Gp(in_place, this->__val());
}
return std::invoke(std::forward<_Func>(__f), error());
}
@@ -651,7 +991,7 @@ public:
static_assert(is_same_v<typename _Gp::value_type, _Tp>,
"The result of f(error()) must have the same value_type as this expected");
if (has_value()) {
- return _Gp(in_place, __union_.__val_);
+ return _Gp(in_place, this->__val());
}
return std::invoke(std::forward<_Func>(__f), error());
}
@@ -665,7 +1005,7 @@ public:
static_assert(is_same_v<typename _Gp::value_type, _Tp>,
"The result of f(std::move(error())) must have the same value_type as this expected");
if (has_value()) {
- return _Gp(in_place, std::move(__union_.__val_));
+ return _Gp(in_place, std::move(this->__val()));
}
return std::invoke(std::forward<_Func>(__f), std::move(error()));
}
@@ -679,7 +1019,7 @@ public:
static_assert(is_same_v<typename _Gp::value_type, _Tp>,
"The result of f(std::move(error())) must have the same value_type as this expected");
if (has_value()) {
- return _Gp(in_place, std::move(__union_.__val_));
+ return _Gp(in_place, std::move(this->__val()));
}
return std::invoke(std::forward<_Func>(__f), std::move(error()));
}
@@ -693,9 +1033,9 @@ public:
}
if constexpr (!is_void_v<_Up>) {
return expected<_Up, _Err>(
- __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), __union_.__val_);
+ __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), this->__val());
} else {
- std::invoke(std::forward<_Func>(__f), __union_.__val_);
+ std::invoke(std::forward<_Func>(__f), this->__val());
return expected<_Up, _Err>();
}
}
@@ -709,9 +1049,9 @@ public:
}
if constexpr (!is_void_v<_Up>) {
return expected<_Up, _Err>(
- __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), __union_.__val_);
+ __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), this->__val());
} else {
- std::invoke(std::forward<_Func>(__f), __union_.__val_);
+ std::invoke(std::forward<_Func>(__f), this->__val());
return expected<_Up, _Err>();
}
}
@@ -725,9 +1065,9 @@ public:
}
if constexpr (!is_void_v<_Up>) {
return expected<_Up, _Err>(
- __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(__union_.__val_));
+ __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(this->__val()));
} else {
- std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
+ std::invoke(std::forward<_Func>(__f), std::move(this->__val()));
return expected<_Up, _Err>();
}
}
@@ -741,9 +1081,9 @@ public:
}
if constexpr (!is_void_v<_Up>) {
return expected<_Up, _Err>(
- __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(__union_.__val_));
+ __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(this->__val()));
} else {
- std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
+ std::invoke(std::forward<_Func>(__f), std::move(this->__val()));
return expected<_Up, _Err>();
}
}
@@ -755,7 +1095,7 @@ public:
static_assert(__valid_std_unexpected<_Gp>::value,
"The result of f(error()) must be a valid template argument for unexpected");
if (has_value()) {
- return expected<_Tp, _Gp>(in_place, __union_.__val_);
+ return expected<_Tp, _Gp>(in_place, this->__val());
}
return expected<_Tp, _Gp>(__expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), error());
}
@@ -767,7 +1107,7 @@ public:
static_assert(__valid_std_unexpected<_Gp>::value,
"The result of f(error()) must be a valid template argument for unexpected");
if (has_value()) {
- return expected<_Tp, _Gp>(in_place, __union_.__val_);
+ return expected<_Tp, _Gp>(in_place, this->__val());
}
return expected<_Tp, _Gp>(__expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), error());
}
@@ -779,7 +1119,7 @@ public:
static_assert(__valid_std_unexpected<_Gp>::value,
"The result of f(std::move(error())) must be a valid template argument for unexpected");
if (has_value()) {
- return expected<_Tp, _Gp>(in_place, std::move(__union_.__val_));
+ return expected<_Tp, _Gp>(in_place, std::move(this->__val()));
}
return expected<_Tp, _Gp>(
__expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), std::move(error()));
@@ -792,7 +1132,7 @@ public:
static_assert(__valid_std_unexpected<_Gp>::value,
"The result of f(std::move(error())) must be a valid template argument for unexpected");
if (has_value()) {
- return expected<_Tp, _Gp>(in_place, std::move(__union_.__val_));
+ return expected<_Tp, _Gp>(in_place, std::move(this->__val()));
}
return expected<_Tp, _Gp>(
__expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), std::move(error()));
@@ -802,123 +1142,220 @@ public:
template <class _T2, class _E2>
requires(!is_void_v<_T2>)
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
- if (__x.__has_val_ != __y.__has_val_) {
+ if (__x.__has_val() != __y.__has_val()) {
return false;
} else {
- if (__x.__has_val_) {
- return __x.__union_.__val_ == __y.__union_.__val_;
+ if (__x.__has_val()) {
+ return __x.__val() == __y.__val();
} else {
- return __x.__union_.__unex_ == __y.__union_.__unex_;
+ return __x.__unex() == __y.__unex();
}
}
}
template <class _T2>
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v) {
- return __x.__has_val_ && static_cast<bool>(__x.__union_.__val_ == __v);
+ return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
}
template <class _E2>
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e) {
- return !__x.__has_val_ && static_cast<bool>(__x.__union_.__unex_ == __e.error());
+ return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
}
+};
-private:
- template <class _ValueType, class _ErrorType>
+template <class _Err>
+class __expected_void_base {
+ struct __empty_t {};
+ // use named union because [[no_unique_address]] cannot be applied to an unnamed union,
+ // also guaranteed elision into a potentially-overlapping subobject is unsettled (and
+ // it's not clear that it's implementable, given that the function is allowed to clobber
+ // the tail padding) - see https://github.com/itanium-cxx-abi/cxx-abi/issues/107.
union __union_t {
- template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::in_place_t, _Args&&... __args)
- : __val_(std::forward<_Args>(__args)...) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&)
+ requires(is_copy_constructible_v<_Err> && is_trivially_copy_constructible_v<_Err>)
+ = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(__union_t&&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t(__union_t&&)
+ requires(is_move_constructible_v<_Err> && is_trivially_move_constructible_v<_Err>)
+ = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(__union_t&&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(in_place_t) : __empty_() {}
template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(unexpect_t, _Args&&... __args)
: __unex_(std::forward<_Args>(__args)...) {}
template <class _Func, class... _Args>
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
- std::__expected_construct_in_place_from_invoke_tag, _Func&& __f, _Args&&... __args)
- : __val_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
-
- template <class _Func, class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
- std::__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
+ __expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
: __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
- template <class _Union>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
- if (__has_val)
- std::construct_at(std::addressof(__val_), std::forward<_Union>(__other).__val_);
- else
- std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
- }
-
_LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
- requires(is_trivially_destructible_v<_ValueType> && is_trivially_destructible_v<_ErrorType>)
+ requires(is_trivially_destructible_v<_Err>)
= default;
- // the expected's destructor handles this
- _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t() {}
+ // __repr's destructor handles this
+ _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
+ requires(!is_trivially_destructible_v<_Err>)
+ {}
- _ValueType __val_;
- _ErrorType __unex_;
+ _LIBCPP_NO_UNIQUE_ADDRESS __empty_t __empty_;
+ _LIBCPP_NO_UNIQUE_ADDRESS _Err __unex_;
};
- // use named union because [[no_unique_address]] cannot be applied to an unnamed union,
- // also guaranteed elision into a potentially-overlapping subobject is unsettled (and
- // it's not clear that it's implementable, given that the function is allowed to clobber
- // the tail padding) - see https://github.com/itanium-cxx-abi/cxx-abi/issues/107.
- template <class _ValueType, class _ErrorType>
- requires(is_trivially_move_constructible_v<_ValueType> && is_trivially_move_constructible_v<_ErrorType>)
- union __union_t<_ValueType, _ErrorType> {
- _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = default;
- _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
+ static constexpr bool __put_flag_in_tail = __fits_in_tail_padding<__union_t, bool>;
+ static constexpr bool __allow_reusing_expected_tail_padding = !__put_flag_in_tail;
+
+ struct __repr {
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr() = delete;
template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::in_place_t, _Args&&... __args)
- : __val_(std::forward<_Args>(__args)...) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(in_place_t __tag) : __union_(in_place, __tag), __has_val_(true) {}
template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
- : __unex_(std::forward<_Args>(__args)...) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(unexpect_t __tag, _Args&&... __args)
+ : __union_(in_place, __tag, std::forward<_Args>(__args)...), __has_val_(false) {}
- template <class _Func, class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
- std::__expected_construct_in_place_from_invoke_tag, _Func&& __f, _Args&&... __args)
- : __val_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(std::__expected_construct_unexpected_from_invoke_tag __tag,
+ _Args&&... __args)
+ : __union_(in_place, __tag, std::forward<_Args>(__args)...), __has_val_(false) {}
+
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __repr(bool __has_val, _OtherUnion&& __other)
+ requires(__allow_reusing_expected_tail_padding)
+ : __union_(__conditional_no_unique_address_invoke_tag{},
+ [&] { return __make_union(__has_val, std::forward<_OtherUnion>(__other)); }),
+ __has_val_(__has_val) {}
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(const __repr&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(const __repr&)
+ requires(is_copy_constructible_v<_Err> && is_trivially_copy_constructible_v<_Err>)
+ = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(__repr&&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr(__repr&&)
+ requires(is_move_constructible_v<_Err> && is_trivially_move_constructible_v<_Err>)
+ = default;
- template <class _Func, class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
- std::__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
- : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr& operator=(const __repr&) = delete;
+ _LIBCPP_HIDE_FROM_ABI constexpr __repr& operator=(__repr&&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr ~__repr()
+ requires(is_trivially_destructible_v<_Err>)
+ = default;
- template <class _Union>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
+ _LIBCPP_HIDE_FROM_ABI constexpr ~__repr()
+ requires(!is_trivially_destructible_v<_Err>)
+ {
+ __destroy_union_member();
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_union()
+ requires(__allow_reusing_expected_tail_padding && is_trivially_destructible_v<_Err>)
+ {
+ std::destroy_at(&__union_.__v);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_union()
+ requires(__allow_reusing_expected_tail_padding && !is_trivially_destructible_v<_Err>)
+ {
+ __destroy_union_member();
+ std::destroy_at(&__union_.__v);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __construct_union(in_place_t)
+ requires(__allow_reusing_expected_tail_padding)
+ {
+ std::construct_at(&__union_.__v, in_place);
+ __has_val_ = true;
+ }
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __construct_union(unexpect_t, _Args&&... __args)
+ requires(__allow_reusing_expected_tail_padding)
+ {
+ std::construct_at(&__union_.__v, unexpect, std::forward<_Args>(__args)...);
+ __has_val_ = false;
+ }
+
+ private:
+ template <class>
+ friend class __expected_void_base;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_union_member()
+ requires(!is_trivially_destructible_v<_Err>)
+ {
+ if (!__has_val_)
+ std::destroy_at(std::addressof(__union_.__v.__unex_));
+ }
+
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI static constexpr __union_t __make_union(bool __has_val, _OtherUnion&& __other)
+ requires(__allow_reusing_expected_tail_padding)
+ {
if (__has_val)
- std::construct_at(std::addressof(__val_), std::forward<_Union>(__other).__val_);
+ return __union_t(in_place);
else
- std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
+ return __union_t(unexpect, std::forward<_OtherUnion>(__other).__unex_);
}
- _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
- requires(is_trivially_destructible_v<_ValueType> && is_trivially_destructible_v<_ErrorType>)
- = default;
+ _LIBCPP_NO_UNIQUE_ADDRESS __conditional_no_unique_address<__put_flag_in_tail, __union_t> __union_;
+ _LIBCPP_NO_UNIQUE_ADDRESS bool __has_val_;
+ };
- // the expected's destructor handles this
- _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
- requires(!is_trivially_destructible_v<_ValueType> || !is_trivially_destructible_v<_ErrorType>)
- {}
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI static constexpr __repr __make_repr(bool __has_val, _OtherUnion&& __other)
+ requires(__put_flag_in_tail)
+ {
+ if (__has_val)
+ return __repr(in_place);
+ else
+ return __repr(unexpect, std::forward<_OtherUnion>(__other).__unex_);
+ }
- _LIBCPP_NO_UNIQUE_ADDRESS _ValueType __val_;
- _LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
- };
+protected:
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __expected_void_base(_Args&&... __args)
+ : __repr_(in_place, std::forward<_Args>(__args)...) {}
+
+ template <class _OtherUnion>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __expected_void_base(bool __has_val, _OtherUnion&& __other)
+ requires(__put_flag_in_tail)
+ : __repr_(__conditional_no_unique_address_invoke_tag{},
+ [&] { return __make_repr(__has_val, std::forward<_OtherUnion>(__other)); }) {}
- _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Tp, _Err> __union_;
- bool __has_val_;
+ _LIBCPP_HIDE_FROM_ABI constexpr void __destroy() {
+ if constexpr (__put_flag_in_tail)
+ std::destroy_at(&__repr_.__v);
+ else
+ __repr_.__v.__destroy_union();
+ }
+
+ template <class _Tag, class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __construct(_Tag __tag, _Args&&... __args) {
+ if constexpr (__put_flag_in_tail)
+ std::construct_at(&__repr_.__v, __tag, std::forward<_Args>(__args)...);
+ else
+ __repr_.__v.__construct_union(__tag, std::forward<_Args>(__args)...);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr bool __has_val() const { return __repr_.__v.__has_val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr __union_t& __union() { return __repr_.__v.__union_.__v; }
+ _LIBCPP_HIDE_FROM_ABI constexpr const __union_t& __union() const { return __repr_.__v.__union_.__v; }
+ _LIBCPP_HIDE_FROM_ABI constexpr _Err& __unex() { return __repr_.__v.__union_.__v.__unex_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr const _Err& __unex() const { return __repr_.__v.__union_.__v.__unex_; }
+
+private:
+ _LIBCPP_NO_UNIQUE_ADDRESS __conditional_no_unique_address<__allow_reusing_expected_tail_padding, __repr> __repr_;
};
template <class _Tp, class _Err>
requires is_void_v<_Tp>
-class expected<_Tp, _Err> {
+class expected<_Tp, _Err> : private __expected_void_base<_Err> {
static_assert(__valid_std_unexpected<_Err>::value,
"[expected.void.general] A program that instantiates expected<T, E> with a E that is not a "
"valid argument for unexpected<E> is ill-formed");
@@ -935,6 +1372,8 @@ class expected<_Tp, _Err> {
_Not<is_constructible<unexpected<_Err>, const expected<_Up, _OtherErr>&>>,
_Not<is_constructible<unexpected<_Err>, const expected<_Up, _OtherErr>>>>;
+ using __base = __expected_void_base<_Err>;
+
public:
using value_type = _Tp;
using error_type = _Err;
@@ -944,7 +1383,7 @@ public:
using rebind = expected<_Up, error_type>;
// [expected.void.ctor], constructors
- _LIBCPP_HIDE_FROM_ABI constexpr expected() noexcept : __has_val_(true) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr expected() noexcept : __base(in_place) {}
_LIBCPP_HIDE_FROM_ABI constexpr expected(const expected&) = delete;
@@ -955,7 +1394,7 @@ public:
_LIBCPP_HIDE_FROM_ABI constexpr expected(const expected& __rhs) noexcept(
is_nothrow_copy_constructible_v<_Err>) // strengthened
requires(is_copy_constructible_v<_Err> && !is_trivially_copy_constructible_v<_Err>)
- : __union_(__rhs.__has_val_, __rhs.__union_), __has_val_(__rhs.__has_val_) {}
+ : __base(__rhs.__has_val(), __rhs.__union()) {}
_LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
requires(is_move_constructible_v<_Err> && is_trivially_move_constructible_v<_Err>)
@@ -963,93 +1402,93 @@ public:
_LIBCPP_HIDE_FROM_ABI constexpr expected(expected&& __rhs) noexcept(is_nothrow_move_constructible_v<_Err>)
requires(is_move_constructible_v<_Err> && !is_trivially_move_constructible_v<_Err>)
- : __union_(__rhs.__has_val_, std::move(__rhs.__union_)), __has_val_(__rhs.__has_val_) {}
+ : __base(__rhs.__has_val(), std::move(__rhs.__union())) {}
template <class _Up, class _OtherErr>
requires __can_convert<_Up, _OtherErr, const _OtherErr&>::value
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
expected(const expected<_Up, _OtherErr>& __rhs) noexcept(
is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
- : __union_(__rhs.__has_val_, __rhs.__union_), __has_val_(__rhs.__has_val_) {}
+ : __base(__rhs.__has_val(), __rhs.__union()) {}
template <class _Up, class _OtherErr>
requires __can_convert<_Up, _OtherErr, _OtherErr>::value
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
expected(expected<_Up, _OtherErr>&& __rhs) noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
- : __union_(__rhs.__has_val_, std::move(__rhs.__union_)), __has_val_(__rhs.__has_val_) {}
+ : __base(__rhs.__has_val(), std::move(__rhs.__union())) {}
template <class _OtherErr>
requires is_constructible_v<_Err, const _OtherErr&>
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>) expected(
const unexpected<_OtherErr>& __unex) noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
- : __union_(std::unexpect, __unex.error()), __has_val_(false) {}
+ : __base(unexpect, __unex.error()) {}
template <class _OtherErr>
requires is_constructible_v<_Err, _OtherErr>
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
expected(unexpected<_OtherErr>&& __unex) noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
- : __union_(std::unexpect, std::move(__unex.error())), __has_val_(false) {}
+ : __base(unexpect, std::move(__unex.error())) {}
- _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t) noexcept : __has_val_(true) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t) noexcept : __base(in_place) {}
template <class... _Args>
requires is_constructible_v<_Err, _Args...>
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args) noexcept(
is_nothrow_constructible_v<_Err, _Args...>) // strengthened
- : __union_(std::unexpect, std::forward<_Args>(__args)...), __has_val_(false) {}
+ : __base(unexpect, std::forward<_Args>(__args)...) {}
template <class _Up, class... _Args>
requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args) noexcept(
is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
- : __union_(std::unexpect, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
+ : __base(unexpect, __il, std::forward<_Args>(__args)...) {}
private:
- template <class _Func>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(__expected_construct_in_place_from_invoke_tag, _Func&& __f)
- : __has_val_(true) {
- std::invoke(std::forward<_Func>(__f));
- }
-
template <class _Func, class... _Args>
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(
__expected_construct_unexpected_from_invoke_tag __tag, _Func&& __f, _Args&&... __args)
- : __union_(__tag, std::forward<_Func>(__f), std::forward<_Args>(__args)...), __has_val_(false) {}
+ : __base(__tag, std::forward<_Func>(__f), std::forward<_Args>(__args)...) {}
public:
// [expected.void.dtor], destructor
- _LIBCPP_HIDE_FROM_ABI constexpr ~expected()
- requires is_trivially_destructible_v<_Err>
- = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr ~expected() = default;
- _LIBCPP_HIDE_FROM_ABI constexpr ~expected()
- requires(!is_trivially_destructible_v<_Err>)
- {
- if (!__has_val_) {
- std::destroy_at(std::addressof(__union_.__unex_));
- }
+private:
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __reinit_expected(unexpect_t, _Args&&... __args) {
+ _LIBCPP_ASSERT_INTERNAL(this->__has_val(), "__reinit_expected(unexpect_t, ...) needs value to be set");
+
+ this->__destroy();
+ auto __trans = std::__make_exception_guard([&] { this->__construct(in_place); });
+ this->__construct(unexpect, std::forward<_Args>(__args)...);
+ __trans.__complete();
}
- // [expected.void.assign], assignment
+ _LIBCPP_HIDE_FROM_ABI constexpr void __reinit_expected(in_place_t) {
+ _LIBCPP_ASSERT_INTERNAL(!this->__has_val(), "__reinit_expected(in_place_t, ...) needs value to be unset");
+ this->__destroy();
+ this->__construct(in_place);
+ }
+
+public:
+ // [expected.void.assign], assignment
_LIBCPP_HIDE_FROM_ABI constexpr expected& operator=(const expected&) = delete;
_LIBCPP_HIDE_FROM_ABI constexpr expected& operator=(const expected& __rhs) noexcept(
is_nothrow_copy_assignable_v<_Err> && is_nothrow_copy_constructible_v<_Err>) // strengthened
requires(is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>)
{
- if (__has_val_) {
- if (!__rhs.__has_val_) {
- std::construct_at(std::addressof(__union_.__unex_), __rhs.__union_.__unex_);
- __has_val_ = false;
+ if (this->__has_val()) {
+ if (!__rhs.__has_val()) {
+ __reinit_expected(unexpect, __rhs.__unex());
}
} else {
- if (__rhs.__has_val_) {
- std::destroy_at(std::addressof(__union_.__unex_));
- __has_val_ = true;
+ if (__rhs.__has_val()) {
+ __reinit_expected(in_place);
} else {
- __union_.__unex_ = __rhs.__union_.__unex_;
+ this->__unex() = __rhs.__unex();
}
}
return *this;
@@ -1061,17 +1500,15 @@ public:
operator=(expected&& __rhs) noexcept(is_nothrow_move_assignable_v<_Err> && is_nothrow_move_constructible_v<_Err>)
requires(is_move_assignable_v<_Err> && is_move_constructible_v<_Err>)
{
- if (__has_val_) {
- if (!__rhs.__has_val_) {
- std::construct_at(std::addressof(__union_.__unex_), std::move(__rhs.__union_.__unex_));
- __has_val_ = false;
+ if (this->__has_val()) {
+ if (!__rhs.__has_val()) {
+ __reinit_expected(unexpect, std::move(__rhs.__unex()));
}
} else {
- if (__rhs.__has_val_) {
- std::destroy_at(std::addressof(__union_.__unex_));
- __has_val_ = true;
+ if (__rhs.__has_val()) {
+ __reinit_expected(in_place);
} else {
- __union_.__unex_ = std::move(__rhs.__union_.__unex_);
+ this->__unex() = std::move(__rhs.__unex());
}
}
return *this;
@@ -1080,11 +1517,10 @@ public:
template <class _OtherErr>
requires(is_constructible_v<_Err, const _OtherErr&> && is_assignable_v<_Err&, const _OtherErr&>)
_LIBCPP_HIDE_FROM_ABI constexpr expected& operator=(const unexpected<_OtherErr>& __un) {
- if (__has_val_) {
- std::construct_at(std::addressof(__union_.__unex_), __un.error());
- __has_val_ = false;
+ if (this->__has_val()) {
+ __reinit_expected(unexpect, __un.error());
} else {
- __union_.__unex_ = __un.error();
+ this->__unex() = __un.error();
}
return *this;
}
@@ -1092,19 +1528,17 @@ public:
template <class _OtherErr>
requires(is_constructible_v<_Err, _OtherErr> && is_assignable_v<_Err&, _OtherErr>)
_LIBCPP_HIDE_FROM_ABI constexpr expected& operator=(unexpected<_OtherErr>&& __un) {
- if (__has_val_) {
- std::construct_at(std::addressof(__union_.__unex_), std::move(__un.error()));
- __has_val_ = false;
+ if (this->__has_val()) {
+ __reinit_expected(unexpect, std::move(__un.error()));
} else {
- __union_.__unex_ = std::move(__un.error());
+ this->__unex() = std::move(__un.error());
}
return *this;
}
_LIBCPP_HIDE_FROM_ABI constexpr void emplace() noexcept {
- if (!__has_val_) {
- std::destroy_at(std::addressof(__union_.__unex_));
- __has_val_ = true;
+ if (!this->__has_val()) {
+ __reinit_expected(in_place);
}
}
@@ -1113,23 +1547,23 @@ public:
swap(expected& __rhs) noexcept(is_nothrow_move_constructible_v<_Err> && is_nothrow_swappable_v<_Err>)
requires(is_swappable_v<_Err> && is_move_constructible_v<_Err>)
{
- auto __swap_val_unex_impl = [&](expected& __with_val, expected& __with_err) {
- std::construct_at(std::addressof(__with_val.__union_.__unex_), std::move(__with_err.__union_.__unex_));
- std::destroy_at(std::addressof(__with_err.__union_.__unex_));
- __with_val.__has_val_ = false;
- __with_err.__has_val_ = true;
+ auto __swap_val_unex_impl = [](expected& __with_val, expected& __with_err) {
+ // May throw, but will re-engage `__with_val` in that case.
+ __with_val.__reinit_expected(unexpect, std::move(__with_err.__unex()));
+ // Will not throw.
+ __with_err.__reinit_expected(in_place);
};
- if (__has_val_) {
- if (!__rhs.__has_val_) {
+ if (this->__has_val()) {
+ if (!__rhs.__has_val()) {
__swap_val_unex_impl(*this, __rhs);
}
} else {
- if (__rhs.__has_val_) {
+ if (__rhs.__has_val()) {
__swap_val_unex_impl(__rhs, *this);
} else {
using std::swap;
- swap(__union_.__unex_, __rhs.__union_.__unex_);
+ swap(this->__unex(), __rhs.__unex());
}
}
}
@@ -1141,46 +1575,51 @@ public:
}
// [expected.void.obs], observers
- _LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __has_val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return this->__has_val(); }
- _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __has_val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return this->__has_val(); }
_LIBCPP_HIDE_FROM_ABI constexpr void operator*() const noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__has_val_, "expected::operator* requires the expected to contain a value");
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ this->__has_val(), "expected::operator* requires the expected to contain a value");
}
_LIBCPP_HIDE_FROM_ABI constexpr void value() const& {
static_assert(is_copy_constructible_v<_Err>);
- if (!__has_val_) {
- std::__throw_bad_expected_access<_Err>(__union_.__unex_);
+ if (!this->__has_val()) {
+ std::__throw_bad_expected_access<_Err>(this->__unex());
}
}
_LIBCPP_HIDE_FROM_ABI constexpr void value() && {
static_assert(is_copy_constructible_v<_Err> && is_move_constructible_v<_Err>);
- if (!__has_val_) {
- std::__throw_bad_expected_access<_Err>(std::move(__union_.__unex_));
+ if (!this->__has_val()) {
+ std::__throw_bad_expected_access<_Err>(std::move(this->__unex()));
}
}
_LIBCPP_HIDE_FROM_ABI constexpr const _Err& error() const& noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return __union_.__unex_;
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return this->__unex();
}
_LIBCPP_HIDE_FROM_ABI constexpr _Err& error() & noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return __union_.__unex_;
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return this->__unex();
}
_LIBCPP_HIDE_FROM_ABI constexpr const _Err&& error() const&& noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return std::move(__union_.__unex_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return std::move(this->__unex());
}
_LIBCPP_HIDE_FROM_ABI constexpr _Err&& error() && noexcept {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__has_val_, "expected::error requires the expected to contain an error");
- return std::move(__union_.__unex_);
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !this->__has_val(), "expected::error requires the expected to contain an error");
+ return std::move(this->__unex());
}
template <class _Up = _Err>
@@ -1416,96 +1855,17 @@ public:
template <class _T2, class _E2>
requires is_void_v<_T2>
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
- if (__x.__has_val_ != __y.__has_val_) {
+ if (__x.__has_val() != __y.__has_val()) {
return false;
} else {
- return __x.__has_val_ || static_cast<bool>(__x.__union_.__unex_ == __y.__union_.__unex_);
+ return __x.__has_val() || static_cast<bool>(__x.__unex() == __y.__unex());
}
}
template <class _E2>
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y) {
- return !__x.__has_val_ && static_cast<bool>(__x.__union_.__unex_ == __y.error());
+ return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
}
-
-private:
- struct __empty_t {};
-
- template <class _ErrorType>
- union __union_t {
- _LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {}
-
- template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
- : __unex_(std::forward<_Args>(__args)...) {}
-
- template <class _Func, class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
- __expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
- : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
-
- template <class _Union>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
- if (__has_val)
- std::construct_at(std::addressof(__empty_));
- else
- std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
- }
-
- _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
- requires(is_trivially_destructible_v<_ErrorType>)
- = default;
-
- // the expected's destructor handles this
- _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t() {}
-
- __empty_t __empty_;
- _ErrorType __unex_;
- };
-
- // use named union because [[no_unique_address]] cannot be applied to an unnamed union,
- // also guaranteed elision into a potentially-overlapping subobject is unsettled (and
- // it's not clear that it's implementable, given that the function is allowed to clobber
- // the tail padding) - see https://github.com/itanium-cxx-abi/cxx-abi/issues/107.
- template <class _ErrorType>
- requires is_trivially_move_constructible_v<_ErrorType>
- union __union_t<_ErrorType> {
- _LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {}
- _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = default;
- _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
-
- template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
- : __unex_(std::forward<_Args>(__args)...) {}
-
- template <class _Func, class... _Args>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
- __expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
- : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
-
- template <class _Union>
- _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
- if (__has_val)
- std::construct_at(std::addressof(__empty_));
- else
- std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
- }
-
- _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
- requires(is_trivially_destructible_v<_ErrorType>)
- = default;
-
- // the expected's destructor handles this
- _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
- requires(!is_trivially_destructible_v<_ErrorType>)
- {}
-
- _LIBCPP_NO_UNIQUE_ADDRESS __empty_t __empty_;
- _LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
- };
-
- _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Err> __union_;
- bool __has_val_;
};
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp
index c8c2170..cf1909b 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp
@@ -12,7 +12,10 @@
// test [[no_unique_address]] is applied to the union
+#include <__type_traits/datasizeof.h>
#include <expected>
+#include <optional>
+#include <memory>
struct Empty {};
@@ -23,13 +26,49 @@ struct A {
struct B : public A {
int z_;
+ short z2_;
virtual ~B() = default;
};
+struct BoolWithPadding {
+ explicit operator bool() { return b; }
+
+private:
+ alignas(1024) bool b = false;
+};
+
static_assert(sizeof(std::expected<Empty, Empty>) == sizeof(bool));
static_assert(sizeof(std::expected<Empty, A>) == 2 * sizeof(int) + alignof(std::expected<Empty, A>));
-static_assert(sizeof(std::expected<Empty, B>) == sizeof(B) + alignof(std::expected<Empty, B>));
+static_assert(sizeof(std::expected<Empty, B>) == sizeof(B));
static_assert(sizeof(std::expected<A, Empty>) == 2 * sizeof(int) + alignof(std::expected<A, Empty>));
static_assert(sizeof(std::expected<A, A>) == 2 * sizeof(int) + alignof(std::expected<A, A>));
-static_assert(sizeof(std::expected<B, Empty>) == sizeof(B) + alignof(std::expected<B, Empty>));
-static_assert(sizeof(std::expected<B, B>) == sizeof(B) + alignof(std::expected<B, B>));
+static_assert(sizeof(std::expected<B, Empty>) == sizeof(B));
+static_assert(sizeof(std::expected<B, B>) == sizeof(B));
+
+// Check that `expected`'s datasize is large enough for the parameter type(s).
+static_assert(sizeof(std::expected<BoolWithPadding, Empty>) ==
+ std::__libcpp_datasizeof<std::expected<BoolWithPadding, Empty>>::value);
+static_assert(sizeof(std::expected<Empty, BoolWithPadding>) ==
+ std::__libcpp_datasizeof<std::expected<Empty, BoolWithPadding>>::value);
+
+// In this case, there should be tail padding in the `expected` because `A`
+// itself does _not_ have tail padding.
+static_assert(sizeof(std::expected<A, A>) > std::__libcpp_datasizeof<std::expected<A, A>>::value);
+
+// Test with some real types.
+static_assert(sizeof(std::expected<std::optional<int>, int>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<std::optional<int>, int>>::value == 8);
+
+static_assert(sizeof(std::expected<int, std::optional<int>>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<int, std::optional<int>>>::value == 8);
+
+static_assert(sizeof(std::expected<int, int>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<int, int>>::value == 5);
+
+// clang-format off
+static_assert(std::__libcpp_datasizeof<int>::value == 4);
+static_assert(std::__libcpp_datasizeof<std::expected<int, int>>::value == 5);
+static_assert(std::__libcpp_datasizeof<std::expected<std::expected<int, int>, int>>::value == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<std::expected<std::expected<int, int>, int>, int>>::value == 9);
+static_assert(std::__libcpp_datasizeof<std::expected<std::expected<std::expected<std::expected<int, int>, int>, int>, int>>::value == 12);
+// clang-format on
diff --git a/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp
index 82024a0..5bf094b 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp
@@ -7,11 +7,15 @@
//===----------------------------------------------------------------------===//
// Clang-18 fixed some spurious clang diagnostics. Once clang-18 is the
-// minumum required version these obsolete tests can be removed.
+// minimum required version these obsolete tests can be removed.
// TODO(LLVM-20) remove spurious clang diagnostic tests.
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// With clang-cl, some warnings have a 'which is a Microsoft extension' suffix
+// which break the tests.
+// XFAIL: msvc
+
// Test the mandates
// template<class F> constexpr auto transform_error(F&& f) &;
@@ -52,6 +56,7 @@ void test() {
e.transform_error(return_unexpected<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
// expected-error-re@*:* {{static assertion failed {{.*}}[expected.object.general] A program that instantiates the definition of template expected<T, E> for {{.*}} is ill-formed.}}
+ // expected-error-re@*:* {{union member {{.*}} has reference type {{.*}}}}
e.transform_error(return_no_object<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
diff --git a/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp
index dc59a62..fdee8b7 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp
@@ -11,8 +11,13 @@
// XFAIL: msvc
// test [[no_unique_address]] is applied to the union
+// In particular, we ensure that we reuse tail padding in the T
+// to store the discriminator whenever we can.
+#include <__type_traits/datasizeof.h>
#include <expected>
+#include <optional>
+#include <memory>
struct Empty {};
@@ -23,9 +28,40 @@ struct A {
struct B : public A {
int z_;
+ short z2_;
virtual ~B() = default;
};
+struct BoolWithPadding {
+ explicit operator bool() { return b; }
+
+private:
+ alignas(1024) bool b = false;
+};
+
static_assert(sizeof(std::expected<void, Empty>) == sizeof(bool));
static_assert(sizeof(std::expected<void, A>) == 2 * sizeof(int) + alignof(std::expected<void, A>));
-static_assert(sizeof(std::expected<void, B>) == sizeof(B) + alignof(std::expected<void, B>));
+static_assert(sizeof(std::expected<void, B>) == sizeof(B));
+
+// Check that `expected`'s datasize is large enough for the parameter type(s).
+static_assert(sizeof(std::expected<void, BoolWithPadding>) ==
+ std::__libcpp_datasizeof<std::expected<void, BoolWithPadding>>::value);
+
+// In this case, there should be tail padding in the `expected` because `A`
+// itself does _not_ have tail padding.
+static_assert(sizeof(std::expected<void, A>) > std::__libcpp_datasizeof<std::expected<void, A>>::value);
+
+// Test with some real types.
+static_assert(sizeof(std::expected<void, std::optional<int>>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::optional<int>>>::value == 8);
+
+static_assert(sizeof(std::expected<void, int>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<void, int>>::value == 5);
+
+// clang-format off
+static_assert(std::__libcpp_datasizeof<int>::value == 4);
+static_assert(std::__libcpp_datasizeof<std::expected<void, int>>::value == 5);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::expected<void, int>>>::value == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::expected<void, std::expected<void, int>>>>::value == 9);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::expected<void, std::expected<void, std::expected<void, int>>>>>::value == 12);
+// clang-format on
diff --git a/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp
index 0f2fb58..4f4f5839 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp
@@ -12,6 +12,10 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// With clang-cl, some warnings have a 'which is a Microsoft extension' suffix
+// which break the tests.
+// XFAIL: msvc
+
// Test the mandates
// template<class F> constexpr auto transform_error(F&& f) &;
@@ -52,6 +56,8 @@ void test() {
e.transform_error(return_unexpected<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
// expected-error-re@*:* {{static assertion failed {{.*}}A program that instantiates expected<T, E> with a E that is not a valid argument for unexpected<E> is ill-formed}}
+ // expected-error-re@*:* {{call to deleted constructor of {{.*}}}}
+ // expected-error-re@*:* {{union member {{.*}} has reference type {{.*}}}}
e.transform_error(return_no_object<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
index 1c8750b..2d3b036 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
@@ -310,6 +310,20 @@ constexpr bool test() {
assert(e.value().j == 8);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e1(std::unexpect);
+ e1 = 42;
+ assert(e1.check());
+ }
+ {
+ CheckForInvalidWrites<false> e1(std::unexpect);
+ e1 = true;
+ assert(e1.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp
index 03fe888..2f52913 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp
@@ -240,6 +240,29 @@ constexpr bool test() {
assert(e1.error().data_ == 10);
assert(oldState.copyAssignCalled);
}
+
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e1(std::unexpect);
+ CheckForInvalidWrites<true> e2;
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false> e1(std::unexpect);
+ CheckForInvalidWrites<false> e2;
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp
index 8c419af..065827a 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp
@@ -258,6 +258,29 @@ constexpr bool test() {
assert(e1.error().data_ == 10);
assert(oldState.moveAssignCalled);
}
+
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e1(std::unexpect);
+ CheckForInvalidWrites<true> e2;
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false> e1(std::unexpect);
+ CheckForInvalidWrites<false> e2;
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp
index 491de2d..7e37f8b 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp
@@ -80,6 +80,20 @@ constexpr bool test() {
assert(e.has_value());
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e;
+ e.emplace();
+ assert(e.check());
+ }
+ {
+ CheckForInvalidWrites<false> e;
+ e.emplace();
+ assert(e.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
index f443613..d38a46f 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
@@ -9,8 +9,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
// GCC has a issue for `Guaranteed copy elision for potentially-overlapping non-static data members`,
-// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333, but we have a workaround to
-// avoid this issue.
+// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333
+// XFAIL: gcc-13
// <expected>
diff --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
index 84b57ae..ec55f63 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
@@ -9,8 +9,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
// GCC has a issue for `Guaranteed copy elision for potentially-overlapping non-static data members`,
-// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333, but we have a workaround to
-// avoid this issue.
+// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333.
+// XFAIL: gcc-13
// <expected>
diff --git a/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp
index 3478230..f19599d 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp
@@ -227,6 +227,28 @@ constexpr bool test() {
}
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> x(std::unexpect);
+ CheckForInvalidWrites<true> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ {
+ CheckForInvalidWrites<false> x(std::unexpect);
+ CheckForInvalidWrites<false> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp
index b1968bc..a51916f 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp
@@ -99,6 +99,28 @@ constexpr bool test() {
assert(state.copyAssignCalled);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e1;
+ CheckForInvalidWrites<true, true> e2(std::unexpect);
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e1;
+ CheckForInvalidWrites<false, true> e2(std::unexpect);
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp
index e6a29cd..60ae034 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp
@@ -125,6 +125,28 @@ constexpr bool test() {
assert(state.moveAssignCalled);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e1;
+ CheckForInvalidWrites<true, true> e2(std::unexpect);
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e1;
+ CheckForInvalidWrites<false, true> e2(std::unexpect);
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp
index 1ae9653..699597d 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp
@@ -91,6 +91,22 @@ constexpr bool test() {
assert(state1.copyAssignCalled);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e;
+ std::unexpected<int> un(std::in_place, 42);
+ e = un;
+ assert(e.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e;
+ std::unexpected<bool> un(std::in_place, true);
+ e = un;
+ assert(e.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp
index ea94771..641eb492 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp
@@ -172,6 +172,23 @@ constexpr bool test() {
assert(e1.error().data_ == 10);
assert(oldState.moveAssignCalled);
}
+
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e;
+ std::unexpected<int> un(std::in_place, 42);
+ e = std::move(un);
+ assert(e.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e;
+ std::unexpected<bool> un(std::in_place, true);
+ e = std::move(un);
+ assert(e.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp
index f0e19ac..cd6e5a5 100644
--- a/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp
@@ -9,8 +9,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
// GCC has a issue for `Guaranteed copy elision for potentially-overlapping non-static data members`,
-// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333, but we have a workaround to
-// avoid this issue.
+// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333
+// XFAIL: gcc-13
// <expected>
diff --git a/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp
index 07980de..25601af 100644
--- a/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp
@@ -139,6 +139,28 @@ constexpr bool test() {
assert(y.has_value());
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> x(std::unexpect);
+ CheckForInvalidWrites<true, true> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> x(std::unexpect);
+ CheckForInvalidWrites<false, true> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h
index 90768ed..2b6983f 100644
--- a/libcxx/test/std/utilities/expected/types.h
+++ b/libcxx/test/std/utilities/expected/types.h
@@ -199,4 +199,141 @@ static_assert(!std::is_trivially_move_constructible_v<TailClobbererNonTrivialMov
static_assert(std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, true>>);
static_assert(!std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, false>>);
+// The `CheckForInvalidWrites` class recreates situations where other objects
+// may be placed into a `std::expected`'s tail padding (see
+// https://github.com/llvm/llvm-project/issues/70494). With a template
+// parameter `WithPaddedExpected` two cases can be tested:
+//
+// 1. The `std::expected<T, E>` itself has padding, because `T`/`E` _don't_
+// have tail padding. This is modelled by `CheckForInvalidWrites<true>`
+// which has a (potential) data layout like this:
+//
+// +- `expected`'s "has value" flag
+// |
+// | +- `please_dont_overwrite_me`
+// | |
+// /---int---\ | /----------^-------\ //
+// 00 00 00 00 01 01 01 01 01 01 01 01
+// \--v---/
+// |
+// |
+// +- `expected`'s tail padding which
+// gets repurposed by `please_dont_overwrite_me`
+//
+// 2. There is tail padding in the union of `T` and `E` which means the
+// "has value" flag can be put into this tail padding. In this case, the
+// `std::expected` itself _must not_ have any tail padding as it may get
+// overwritten on mutating operations such as `emplace()`. This case is
+// modelled by `CheckForInvalidWrites<false>` with a (potential) data
+// layout like this:
+//
+// +- bool
+// | +- please_dont_overwrite_me
+// | +- "has value" flag |
+// | | /--------^---------\ //
+// 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 00
+// \---padding-----/ |
+// +- `CheckForInvalidWrites`
+// padding
+//
+// Note that other implementation strategies are viable, including one that
+// doesn't make use of `[[no_unique_address]]`. But if an implementation uses
+// the strategy above, it must make sure that those tail padding bytes are not
+// overwritten improperly on operations such as `emplace()`.
+
+struct BoolWithPadding {
+ constexpr explicit BoolWithPadding() noexcept : BoolWithPadding(false) {}
+ constexpr BoolWithPadding(bool val) noexcept {
+ if (!std::is_constant_evaluated()) {
+ std::memset(this, 0, sizeof(*this));
+ }
+ val_ = val;
+ }
+ constexpr BoolWithPadding(const BoolWithPadding& other) noexcept : BoolWithPadding(other.val_) {}
+ constexpr BoolWithPadding& operator=(const BoolWithPadding& other) noexcept {
+ val_ = other.val_;
+ return *this;
+ }
+ // The previous data layout of libc++'s `expected` required `T` to be
+ // trivially move constructible to employ the `[[no_unique_address]]`
+ // optimization. To trigger bugs with the old implementation, make
+ // `BoolWithPadding` trivially move constructible.
+ constexpr BoolWithPadding(BoolWithPadding&&) = default;
+
+private:
+ alignas(8) bool val_;
+};
+
+struct IntWithoutPadding {
+ constexpr explicit IntWithoutPadding() noexcept : IntWithoutPadding(0) {}
+ constexpr IntWithoutPadding(int val) noexcept {
+ if (!std::is_constant_evaluated()) {
+ std::memset(this, 0, sizeof(*this));
+ }
+ val_ = val;
+ }
+ constexpr IntWithoutPadding(const IntWithoutPadding& other) noexcept : IntWithoutPadding(other.val_) {}
+ constexpr IntWithoutPadding& operator=(const IntWithoutPadding& other) noexcept {
+ val_ = other.val_;
+ return *this;
+ }
+ // See comment on `BoolWithPadding`.
+ constexpr IntWithoutPadding(IntWithoutPadding&&) = default;
+
+private:
+ int val_;
+};
+
+template <bool WithPaddedExpected, bool ExpectedVoid>
+struct CheckForInvalidWritesBaseImpl;
+template <>
+struct CheckForInvalidWritesBaseImpl<true, false> {
+ using type = std::expected<IntWithoutPadding, bool>;
+};
+template <>
+struct CheckForInvalidWritesBaseImpl<false, false> {
+ using type = std::expected<BoolWithPadding, bool>;
+};
+template <>
+struct CheckForInvalidWritesBaseImpl<true, true> {
+ using type = std::expected<void, IntWithoutPadding>;
+};
+template <>
+struct CheckForInvalidWritesBaseImpl<false, true> {
+ using type = std::expected<void, BoolWithPadding>;
+};
+
+template <bool WithPaddedExpected, bool ExpectedVoid>
+using CheckForInvalidWritesBase = typename CheckForInvalidWritesBaseImpl<WithPaddedExpected, ExpectedVoid>::type;
+
+template <bool WithPaddedExpected, bool ExpectedVoid = false>
+struct CheckForInvalidWrites : public CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid> {
+ constexpr CheckForInvalidWrites() = default;
+ constexpr CheckForInvalidWrites(std::unexpect_t)
+ : CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>(std::unexpect) {}
+
+ constexpr CheckForInvalidWrites& operator=(const CheckForInvalidWrites& other) {
+ CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=(other);
+ return *this;
+ }
+
+ constexpr CheckForInvalidWrites& operator=(CheckForInvalidWrites&& other) {
+ CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=(std::move(other));
+ return *this;
+ }
+
+ using CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=;
+
+ const bool please_dont_overwrite_me[7] = {true, true, true, true, true, true, true};
+
+ constexpr bool check() {
+ for (bool i : please_dont_overwrite_me) {
+ if (!i) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
#endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H