concept __not_constructing_bool_from_expected
= ! is_same_v<remove_cv_t<_Tp>, bool>
|| ! __is_expected<remove_cvref_t<_Up>>;
+
+ template<typename _Tp, typename _Up = remove_cvref_t<_Tp>>
+ concept __trivially_replaceable
+ = is_trivially_constructible_v<_Up, _Tp>
+ && is_trivially_assignable_v<_Up&, _Tp>
+ && is_trivially_destructible_v<_Up>;
+
+ template<typename _Tp, typename _Up = remove_cvref_t<_Tp>>
+ concept __usable_for_assign
+ = is_constructible_v<_Up, _Tp> && is_assignable_v<_Up&, _Tp>;
+
+ // _GLIBCXX_RESOLVE_LIB_DEFECTS
+ // 4026. Assignment operators of std::expected should propagate triviality
+ template<typename _Tp>
+ concept __usable_for_trivial_assign
+ = __trivially_replaceable<_Tp> && __usable_for_assign<_Tp>;
+
+ // For copy/move assignment to replace T with E (or vice versa)
+ // we require at least one of them to be nothrow move constructible.
+ template<typename _Tp, typename _Er>
+ concept __can_reassign_type
+ = is_nothrow_move_constructible_v<_Tp>
+ || is_nothrow_move_constructible_v<_Er>;
}
/// @endcond
// assignment
+ // Deleted copy assignment, when constraints not met for other overloads
expected& operator=(const expected&) = delete;
+ // Trivial copy assignment
+ expected&
+ operator=(const expected&)
+ noexcept(__and_v<is_nothrow_copy_constructible<_Tp>,
+ is_nothrow_copy_constructible<_Er>,
+ is_nothrow_copy_assignable<_Tp>,
+ is_nothrow_copy_assignable<_Er>>)
+ requires __expected::__usable_for_trivial_assign<const _Tp&>
+ && __expected::__usable_for_trivial_assign<const _Er&>
+ && __expected::__can_reassign_type<_Tp, _Er>
+ = default;
+
+ // Non-trivial copy assignment
constexpr expected&
operator=(const expected& __x)
noexcept(__and_v<is_nothrow_copy_constructible<_Tp>,
is_nothrow_copy_constructible<_Er>,
is_nothrow_copy_assignable<_Tp>,
is_nothrow_copy_assignable<_Er>>)
- requires is_copy_assignable_v<_Tp> && is_copy_constructible_v<_Tp>
- && is_copy_assignable_v<_Er> && is_copy_constructible_v<_Er>
- && (is_nothrow_move_constructible_v<_Tp>
- || is_nothrow_move_constructible_v<_Er>)
+ requires __expected::__usable_for_assign<const _Tp&>
+ && __expected::__usable_for_assign<const _Er&>
+ && __expected::__can_reassign_type<_Tp, _Er>
{
if (__x._M_has_value)
this->_M_assign_val(__x._M_val);
return *this;
}
+ // Trivial move assignment
+ expected&
+ operator=(expected&&)
+ noexcept(__and_v<is_nothrow_move_constructible<_Tp>,
+ is_nothrow_move_constructible<_Er>,
+ is_nothrow_move_assignable<_Tp>,
+ is_nothrow_move_assignable<_Er>>)
+ requires __expected::__usable_for_trivial_assign<_Tp&&>
+ && __expected::__usable_for_trivial_assign<_Er&&>
+ && __expected::__can_reassign_type<_Tp, _Er>
+ = default;
+
+ // Non-trivial move assignment
constexpr expected&
operator=(expected&& __x)
noexcept(__and_v<is_nothrow_move_constructible<_Tp>,
is_nothrow_move_constructible<_Er>,
is_nothrow_move_assignable<_Tp>,
is_nothrow_move_assignable<_Er>>)
- requires is_move_assignable_v<_Tp> && is_move_constructible_v<_Tp>
- && is_move_assignable_v<_Er> && is_move_constructible_v<_Er>
- && (is_nothrow_move_constructible_v<_Tp>
- || is_nothrow_move_constructible_v<_Er>)
+ requires __expected::__usable_for_assign<_Tp&&>
+ && __expected::__usable_for_assign<_Er&&>
+ && __expected::__can_reassign_type<_Tp, _Er>
{
if (__x._M_has_value)
_M_assign_val(std::move(__x._M_val));
// assignment
+ // Deleted copy assignment, when constraints not met for other overloads
expected& operator=(const expected&) = delete;
+ // Trivial copy assignment
+ expected&
+ operator=(const expected&)
+ noexcept(__and_v<is_nothrow_copy_constructible<_Er>,
+ is_nothrow_copy_assignable<_Er>>)
+ requires __expected::__usable_for_trivial_assign<const _Er&>
+ = default;
+
+ // Non-trivial copy assignment
constexpr expected&
operator=(const expected& __x)
noexcept(__and_v<is_nothrow_copy_constructible<_Er>,
is_nothrow_copy_assignable<_Er>>)
- requires is_copy_constructible_v<_Er>
- && is_copy_assignable_v<_Er>
+ requires __expected::__usable_for_assign<const _Er&>
{
if (__x._M_has_value)
emplace();
return *this;
}
+ // Trivial move assignment
+ expected&
+ operator=(expected&&)
+ noexcept(__and_v<is_nothrow_move_constructible<_Er>,
+ is_nothrow_move_assignable<_Er>>)
+ requires __expected::__usable_for_trivial_assign<_Er&&>
+ = default;
+
+ // Non-trivial move assignment
constexpr expected&
operator=(expected&& __x)
noexcept(__and_v<is_nothrow_move_constructible<_Er>,
is_nothrow_move_assignable<_Er>>)
- requires is_move_constructible_v<_Er>
- && is_move_assignable_v<_Er>
+ requires __expected::__usable_for_assign<_Er&&>
{
if (__x._M_has_value)
emplace();
// Copy assignment
template<typename T, typename E>
- constexpr bool copy_assignable
- = std::is_copy_assignable_v<std::expected<T, E>>;
+ constexpr Result copy_assignable
+ = std::is_trivially_copy_assignable_v<std::expected<T, E>> ? Trivial
+ : std::is_nothrow_copy_assignable_v<std::expected<T, E>> ? NoThrow
+ : std::is_copy_assignable_v<std::expected<T, E>> ? Yes
+ : No;
struct F { F(F&&); F& operator=(const F&); }; // not copy-constructible
-struct G { G(const G&); G(G&&); G& operator=(const G&); }; // throwing move
-
-static_assert( copy_assignable< int, int > );
-static_assert( copy_assignable< F, int > == false );
-static_assert( copy_assignable< int, F > == false );
-static_assert( copy_assignable< F, F > == false );
-static_assert( copy_assignable< G, int > );
-static_assert( copy_assignable< int, G > );
-static_assert( copy_assignable< G, G > == false );
-static_assert( copy_assignable< void, int > );
-static_assert( copy_assignable< void, F > == false );
-static_assert( copy_assignable< void, G > );
+
+template<bool CopyCtor, bool MoveCtor, bool CopyAssign, bool MoveAssign>
+struct X {
+ X(const X&) noexcept(CopyCtor);
+ X(X&&) noexcept(MoveCtor);
+ X& operator=(const X&) noexcept(CopyAssign);
+ X& operator=(X&&) noexcept(MoveAssign);
+};
+using G = X<false, false, false, false>;
+using H = X<false, true, true, true>;
+using I = X<true, true, true, false>;
+
+static_assert( copy_assignable< int, int > == Trivial );
+static_assert( copy_assignable< F, int > == No );
+static_assert( copy_assignable< int, F > == No );
+static_assert( copy_assignable< F, F > == No );
+static_assert( copy_assignable< G, int > == Yes );
+static_assert( copy_assignable< int, G > == Yes );
+static_assert( copy_assignable< G, G > == No );
+static_assert( copy_assignable< int, H > == Yes );
+static_assert( copy_assignable< H, H > == Yes );
+static_assert( copy_assignable< int, I > == NoThrow );
+static_assert( copy_assignable< I, I > == NoThrow );
+static_assert( copy_assignable< void, int > == Trivial );
+static_assert( copy_assignable< void, F > == No );
+static_assert( copy_assignable< void, G > == Yes );
+static_assert( copy_assignable< void, H > == Yes );
+static_assert( copy_assignable< void, I > == NoThrow );
// Move assignment
template<typename T, typename E>
- constexpr bool move_assignable
- = std::is_move_assignable_v<std::expected<T, E>>;
-
-static_assert( move_assignable< int, int > );
-static_assert( move_assignable< F, int > );
-static_assert( move_assignable< int, F > );
-static_assert( move_assignable< F, F > == false );
-static_assert( move_assignable< G, int > );
-static_assert( move_assignable< int, G > );
-static_assert( move_assignable< G, G > == false );
-static_assert( move_assignable< void, int > );
-static_assert( move_assignable< void, F > );
-static_assert( move_assignable< void, G > );
+ constexpr Result move_assignable
+ = std::is_trivially_move_assignable_v<std::expected<T, E>> ? Trivial
+ : std::is_nothrow_move_assignable_v<std::expected<T, E>> ? NoThrow
+ : std::is_move_assignable_v<std::expected<T, E>> ? Yes
+ : No;
+
+static_assert( move_assignable< int, int > == Trivial );
+static_assert( move_assignable< F, int > == Yes );
+static_assert( move_assignable< int, F > == Yes );
+static_assert( move_assignable< F, F > == No );
+static_assert( move_assignable< G, int > == Yes );
+static_assert( move_assignable< int, G > == Yes );
+static_assert( move_assignable< G, G > == No );
+static_assert( move_assignable< int, H > == NoThrow );
+static_assert( move_assignable< H, H > == NoThrow );
+static_assert( move_assignable< I, I > == Yes );
+static_assert( move_assignable< void, int > == Trivial );
+static_assert( move_assignable< void, F > == Yes );
+static_assert( move_assignable< void, G > == Yes );
+static_assert( move_assignable< void, H > == NoThrow );
+static_assert( move_assignable< void, I > == Yes );
// QoI properties
static_assert( sizeof(std::expected<char, unsigned char>) == 2 );
static_assert( sizeof(std::expected<void*, char>) == sizeof(void*) + __alignof(void*) );
static_assert( alignof(std::expected<void, char>) == 1 );
static_assert( alignof(std::expected<void*, char>) == alignof(void*) );
+
+// For QoI we propagate noexcept(false) from trivial special members.
+template<bool CopyCtor, bool MoveCtor, bool CopyAssign, bool MoveAssign>
+struct Y {
+ Y(const Y&) noexcept(CopyCtor) = default;
+ Y(Y&&) noexcept(MoveCtor) = default;
+ Y& operator=(const Y&) noexcept(CopyAssign) = default;
+ Y& operator=(Y&&) noexcept(MoveAssign) = default;
+};
+
+template<int I> using Yi = Y<bool(I&8), bool(I&4), bool(I&2), bool(I&1)>;
+
+template<typename> constexpr bool nothrow_copy = false;
+template<typename> constexpr bool nothrow_move = false;
+
+template<bool CC, bool MC, bool CA, bool MA>
+constexpr bool nothrow_copy<Y<CC, MC, CA, MA>> = CC && CA;
+
+template<bool CC, bool MC, bool CA, bool MA>
+constexpr bool nothrow_move<Y<CC, MC, CA, MA>> = MC && MA;
+
+template<> constexpr bool nothrow_copy<void> = true;
+template<> constexpr bool nothrow_move<void> = true;
+
+template<typename A, typename B>
+consteval bool do_checks()
+{
+ if constexpr (std::is_void_v<A> || std::is_nothrow_move_constructible_v<A>
+ || std::is_nothrow_move_constructible_v<B>)
+ {
+ // All assignments should be trivial
+ static_assert( copy_assignable<A, B> == Trivial );
+ static_assert( move_assignable<A, B> == Trivial );
+ // But whether they are nothrow depends on the noexcept-specifiers
+ static_assert( std::is_nothrow_copy_assignable_v<std::expected<A, B>>
+ == (nothrow_copy<A> && nothrow_copy<B>) );
+ static_assert( std::is_nothrow_move_assignable_v<std::expected<A, B>>
+ == (nothrow_move<A> && nothrow_move<B>) );
+ }
+ else
+ {
+ static_assert( copy_assignable<A, B> == No );
+ static_assert( move_assignable<A, B> == No );
+ }
+ return true;
+}
+
+template<typename A, int... I>
+consteval bool check(std::integer_sequence<int, I...>)
+{
+ return (do_checks<A, Yi<I>>() && ...);
+}
+
+template<int... I>
+consteval bool
+check_all(std::integer_sequence<int, I...> i)
+{
+ return (check<Yi<I>>(i) && ...) && check<void>(i);
+}
+
+static_assert(check_all(std::make_integer_sequence<int, 16>{}));