From 7d8e1c0bfd7d0221cc26ded9903c5e0a5d300f4a Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Wed, 13 May 2026 19:50:41 +0200 Subject: [PATCH 01/21] Refactor pfn::expected to move storage to a separate class --- include/pfn/expected.hpp | 1129 ++++++++++++++++++++------------------ 1 file changed, 591 insertions(+), 538 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 3d05221f..0e4c18ec 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -183,66 +183,289 @@ constexpr bool _is_valid_expected = // && not ::std::is_same_v<::std::remove_cv_t, unexpect_t> // && not _is_some_unexpected<::std::remove_cv_t> // && detail::_is_valid_unexpected; -} // namespace detail -// declare void specialization -template - requires ::std::is_void_v -class expected; +static constexpr struct _branch_t { + constexpr explicit _branch_t() noexcept = default; +} _branch{}; -template class expected { - static_assert(detail::_is_valid_expected); +struct _dummy_t final { + constexpr _dummy_t() noexcept = default; +}; - template - using _can_convert_detail = ::std::bool_constant< // - not(::std::is_same_v && ::std::is_same_v) // - && ::std::is_constructible_v // - && ::std::is_constructible_v // - && not ::std::is_constructible_v, expected &> // - && not ::std::is_constructible_v, expected> // - && not ::std::is_constructible_v, expected const &> // - && not ::std::is_constructible_v, expected const> // - && (::std::is_same_v> // - || ( // - not ::std::is_constructible_v &> // - && not ::std::is_constructible_v> // - && not ::std::is_constructible_v const &> // - && not ::std::is_constructible_v const> // - && not ::std::is_convertible_v &, T> // - && not ::std::is_convertible_v &&, T> // - && not ::std::is_convertible_v const &, T> // - && not ::std::is_convertible_v const &&, T>))>; +// Shared storage base for ::pfn::expected. Members are public because the +// inheritance is private; sibling-instantiation access goes through the +// `template friend class expected;` declaration on the derived. +template struct _storage { + using _storage_t = ::std::conditional_t<::std::is_same_v, _dummy_t, T>; + union { + _storage_t v_; + E e_; + }; + bool set_; - template using _can_copy_convert = _can_convert_detail; - template using _can_move_convert = _can_convert_detail; - template friend class expected; + template + constexpr explicit _storage(::std::in_place_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v<_storage_t, Args...>) + requires ::std::is_constructible_v<_storage_t, Args...> + : v_(FWD(a)...), set_(true) + { + } + template + constexpr explicit _storage(::std::in_place_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v<_storage_t, ::std::initializer_list &, Args...>) + requires ::std::is_constructible_v<_storage_t, ::std::initializer_list &, Args...> + : v_(il, FWD(a)...), set_(true) + { + } + template + constexpr explicit _storage(unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) + requires ::std::is_constructible_v + : e_(FWD(a)...), set_(false) + { + } + template + constexpr explicit _storage(unexpect_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) + requires ::std::is_constructible_v &, Args...> + : e_(il, FWD(a)...), set_(false) + { + } - template - using _can_convert = ::std::bool_constant< // - not ::std::is_same_v<::std::remove_cvref_t, ::std::in_place_t> // - && not ::std::is_same_v<::std::remove_cvref_t, unexpect_t> // LWG4222 - && not ::std::is_same_v> // - && not detail::_is_some_unexpected<::std::remove_cvref_t> // - && ::std::is_constructible_v // - && (not ::std::is_same_v> // - || not detail::_is_some_expected<::std::remove_cvref_t>)>; + // Branch tag: initializes only `set_`, leaving the union uninitialized so + // the derived ctor body can placement-new into the active arm. + constexpr explicit _storage(_branch_t, bool s) noexcept : set_(s) {} + + constexpr _storage(_storage const &) = default; + constexpr _storage(_storage &&) = default; + constexpr _storage &operator=(_storage const &) = default; + constexpr _storage &operator=(_storage &&) = default; + + constexpr ~_storage() noexcept + requires(::std::is_trivially_destructible_v<_storage_t> && ::std::is_trivially_destructible_v) + = default; + constexpr ~_storage() // + requires(::std::is_trivially_destructible_v<_storage_t> && not ::std::is_trivially_destructible_v) + { + if (not set_) + ::std::destroy_at(::std::addressof(e_)); + } + constexpr ~_storage() // + requires(not ::std::is_trivially_destructible_v<_storage_t> && ::std::is_trivially_destructible_v) + { + if (set_) + ::std::destroy_at(::std::addressof(v_)); + } + constexpr ~_storage() // + requires(not ::std::is_trivially_destructible_v<_storage_t> && not ::std::is_trivially_destructible_v) + { + if (set_) + ::std::destroy_at(::std::addressof(v_)); + else + ::std::destroy_at(::std::addressof(e_)); + } + + // [expected.object.obs], observers + constexpr _storage_t const *operator->() const noexcept + requires(not ::std::is_void_v) + { + ASSERT(set_); + return ::std::addressof(v_); + } + constexpr _storage_t *operator->() noexcept + requires(not ::std::is_void_v) + { + ASSERT(set_); + return ::std::addressof(v_); + } + constexpr _storage_t const &operator*() const & noexcept + requires(not ::std::is_void_v) + { + return *(this->operator->()); + } + constexpr _storage_t &operator*() & noexcept + requires(not ::std::is_void_v) + { + return *(this->operator->()); + } + constexpr _storage_t const &&operator*() const && noexcept + requires(not ::std::is_void_v) + { + return ::std::move(*(this->operator->())); + } + constexpr _storage_t &&operator*() && noexcept + requires(not ::std::is_void_v) + { + return ::std::move(*(this->operator->())); + } + constexpr void operator*() const & noexcept + requires(::std::is_void_v) + { + ASSERT(set_); + } + constexpr void operator*() & noexcept + requires(::std::is_void_v) + { + ASSERT(set_); + } + constexpr void operator*() const && noexcept + requires(::std::is_void_v) + { + ASSERT(set_); + } + constexpr void operator*() && noexcept + requires(::std::is_void_v) + { + ASSERT(set_); + } + constexpr explicit operator bool() const noexcept { return set_; } + constexpr bool has_value() const noexcept { return set_; } + constexpr bool has_error() const noexcept { return !set_; } // P3798 + constexpr _storage_t const &value() const & + requires(not ::std::is_void_v) + { + static_assert(::std::is_copy_constructible_v); + if (not set_) + throw bad_expected_access(e_); + return v_; + } + constexpr _storage_t &value() & + requires(not ::std::is_void_v) + { + static_assert(::std::is_copy_constructible_v); + if (not set_) + throw bad_expected_access(::std::as_const(e_)); + return v_; + } + constexpr _storage_t const &&value() const && + requires(not ::std::is_void_v) + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_constructible_v); + if (not set_) + throw bad_expected_access(::std::move(e_)); + return ::std::move(v_); + } + constexpr _storage_t &&value() && + requires(not ::std::is_void_v) + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_constructible_v); + if (not set_) + throw bad_expected_access(::std::move(e_)); + return ::std::move(v_); + } + constexpr void value() const & + requires(::std::is_void_v) + { + static_assert(::std::is_copy_constructible_v); + if (not set_) + throw bad_expected_access(e_); + } + constexpr void value() && + requires(::std::is_void_v) + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_move_constructible_v); + if (not set_) + throw bad_expected_access(::std::move(e_)); + } + constexpr E const &error() const & noexcept + { + ASSERT(not set_); + return e_; + } + constexpr E &error() & noexcept + { + ASSERT(not set_); + return e_; + } + constexpr E const &&error() const && noexcept + { + ASSERT(not set_); + return ::std::move(e_); + } + constexpr E &&error() && noexcept + { + ASSERT(not set_); + return ::std::move(e_); + } template - using _can_convert_assign = ::std::bool_constant< // - not ::std::is_same_v> // - && not detail::_is_some_unexpected<::std::remove_cvref_t> // - && ::std::is_constructible_v // - && ::std::is_assignable_v // - && (::std::is_nothrow_constructible_v // - || ::std::is_nothrow_move_constructible_v // - || ::std::is_nothrow_move_constructible_v)>; + constexpr T value_or(U &&v) const & // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension + requires(not ::std::is_void_v) + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_convertible_v); + return set_ ? v_ : static_cast(FWD(v)); + } + template + constexpr T value_or(U &&v) && // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension + requires(not ::std::is_void_v) + { + static_assert(::std::is_move_constructible_v); + static_assert(::std::is_convertible_v); + return set_ ? ::std::move(v_) : static_cast(FWD(v)); + } + + template + constexpr E error_or(G &&e) const & // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_convertible_v); + if (set_) { + return FWD(e); + } + return e_; + } + template + constexpr E error_or(G &&e) && // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_move_constructible_v); + static_assert(::std::is_convertible_v); + if (set_) { + return FWD(e); + } + return ::std::move(e_); + } + + // Internal value accessor used by monadic helpers; `value()` performs the + // access check via exception, this one assumes the precondition. + constexpr _storage_t const &_value() const & noexcept + requires(not ::std::is_void_v) + { + ASSERT(set_); + return v_; + } + constexpr _storage_t &_value() & noexcept + requires(not ::std::is_void_v) + { + ASSERT(set_); + return v_; + } + constexpr _storage_t const &&_value() const && noexcept + requires(not ::std::is_void_v) + { + ASSERT(set_); + return ::std::move(v_); + } + constexpr _storage_t &&_value() && noexcept + requires(not ::std::is_void_v) + { + ASSERT(set_); + return ::std::move(v_); + } // [expected.object.assign] template static constexpr void _reinit(New &newval, Old &oldval, Args &&...args) // noexcept(::std::is_nothrow_constructible_v) { - if constexpr (::std::is_nothrow_constructible_v) { + if constexpr (::std::is_void_v || ::std::is_nothrow_constructible_v) { ::std::destroy_at(::std::addressof(oldval)); ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); } else if constexpr (::std::is_nothrow_move_constructible_v) { @@ -294,8 +517,48 @@ template class expected { } } - static constexpr void _swap(expected &lhs, expected &rhs) // - noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) + // Body helper for the copy/move assignment operators; the public expected + // operator= overloads keep their constraints/noexcept clauses and just + // delegate here, forwarding `s` as lvalue or rvalue. For T=void the value + // side is `_dummy_t` whose destructor and default ctor are trivial; the + // cross-state transitions use direct construct_at, matching the + // [expected.void.assign] and [expected.object.assign] specification (no strong exception guarantee). + constexpr void _assign(auto &&s) + { + if (set_ && s.set_) { + v_ = FWD(s).v_; + } else if (set_) { + _reinit(e_, v_, FWD(s).e_); + set_ = false; + } else if (s.set_) { + _reinit(v_, e_, FWD(s).v_); + set_ = true; + } else { + e_ = FWD(s).e_; + } + } + template constexpr void _assign_value(U &&s) + { + if (set_) { + v_ = FWD(s); + } else { + _reinit(v_, e_, FWD(s)); + set_ = true; + } + } + constexpr void _assign_unexpected(auto &&s) + { + if (not set_) { + e_ = FWD(s).error(); + } else { + _reinit(e_, v_, FWD(s).error()); + set_ = false; + } + } + + // [expected.object.swap] cross-state swap helper; lhs holds value, rhs holds error + static constexpr void _swap(_storage &lhs, _storage &rhs) // + noexcept(::std::is_nothrow_move_constructible_v<_storage_t> && ::std::is_nothrow_move_constructible_v) { if constexpr (::std::is_nothrow_move_constructible_v) { E tmp(::std::move(rhs.e_)); @@ -309,7 +572,7 @@ template class expected { throw; } } else { - T tmp(::std::move(lhs.v_)); + _storage_t tmp(::std::move(lhs.v_)); ::std::destroy_at(::std::addressof(lhs.v_)); try { ::std::construct_at(::std::addressof(lhs.e_), ::std::move(rhs.e_)); @@ -324,26 +587,142 @@ template class expected { rhs.set_ = true; } - constexpr T const &_value() const & noexcept + // [expected.object.swap] body helper; the public expected::swap keeps its + // constraints/noexcept clause and just delegates here. + constexpr void _swap_with(_storage &rhs) { - ASSERT(set_); - return v_; + if (set_ == rhs.set_) { + if (set_) { + if constexpr (not ::std::is_void_v) { + using ::std::swap; + swap(v_, rhs.v_); + } + } else { + using ::std::swap; + swap(e_, rhs.e_); + } + } else if constexpr (::std::is_void_v) { + // Direct cross-state transition: only one E move, matching the + // [expected.void.swap] specification. + if (set_) { + ::std::destroy_at(::std::addressof(v_)); + ::std::construct_at(::std::addressof(e_), ::std::move(rhs.e_)); + ::std::destroy_at(::std::addressof(rhs.e_)); + ::std::construct_at(::std::addressof(rhs.v_)); + set_ = false; + rhs.set_ = true; + } else { + rhs._swap_with(*this); + } + } else { + if (set_) { + _swap(*this, rhs); + } else { + _swap(rhs, *this); + } + } } - constexpr T &_value() & noexcept + + // [expected.object.emplace], emplace + template + constexpr _storage_t &emplace(Args &&...args) noexcept + requires(not ::std::is_void_v && ::std::is_nothrow_constructible_v) { - ASSERT(set_); - return v_; + if (set_) { + ::std::destroy_at(::std::addressof(v_)); + } else { + ::std::destroy_at(::std::addressof(e_)); + set_ = true; + } + return *::std::construct_at(::std::addressof(v_), std::forward(args)...); } - constexpr T const &&_value() const && noexcept + + template + constexpr _storage_t &emplace(::std::initializer_list il, Args &&...args) noexcept + requires(not ::std::is_void_v && ::std::is_nothrow_constructible_v &, Args...>) { - ASSERT(set_); - return ::std::move(v_); + if (set_) { + ::std::destroy_at(::std::addressof(v_)); + } else { + ::std::destroy_at(::std::addressof(e_)); + set_ = true; + } + return *::std::construct_at(::std::addressof(v_), il, std::forward(args)...); } - constexpr T &&_value() && noexcept + + // [expected.void.emplace], emplace + constexpr void emplace() noexcept + requires(::std::is_void_v) { - ASSERT(set_); - return ::std::move(v_); + if (not set_) { + ::std::destroy_at(::std::addressof(e_)); + ::std::construct_at(::std::addressof(v_)); + set_ = true; + } } +}; + +} // namespace detail + +// declare void specialization +template + requires ::std::is_void_v +class expected; + +template class expected : private detail::_storage { + static_assert(detail::_is_valid_expected); + + template + using _can_convert_detail = ::std::bool_constant< // + not(::std::is_same_v && ::std::is_same_v) // + && ::std::is_constructible_v // + && ::std::is_constructible_v // + && not ::std::is_constructible_v, expected &> // + && not ::std::is_constructible_v, expected> // + && not ::std::is_constructible_v, expected const &> // + && not ::std::is_constructible_v, expected const> // + && (::std::is_same_v> // + || ( // + not ::std::is_constructible_v &> // + && not ::std::is_constructible_v> // + && not ::std::is_constructible_v const &> // + && not ::std::is_constructible_v const> // + && not ::std::is_convertible_v &, T> // + && not ::std::is_convertible_v &&, T> // + && not ::std::is_convertible_v const &, T> // + && not ::std::is_convertible_v const &&, T>))>; + + template using _can_copy_convert = _can_convert_detail; + template using _can_move_convert = _can_convert_detail; + template friend class expected; + + // Bring the storage members into scope so unqualified lookup resolves them + // through the dependent base, and to document the inherited surface. + using _base = detail::_storage; + using _base::_value; + using _base::e_; + using _base::set_; + using _base::v_; + + template + using _can_convert = ::std::bool_constant< // + not ::std::is_same_v<::std::remove_cvref_t, ::std::in_place_t> // + && not ::std::is_same_v<::std::remove_cvref_t, unexpect_t> // LWG4222 + && not ::std::is_same_v> // + && not detail::_is_some_unexpected<::std::remove_cvref_t> // + && ::std::is_constructible_v // + && (not ::std::is_same_v> // + || not detail::_is_some_expected<::std::remove_cvref_t>)>; + + template + using _can_convert_assign = ::std::bool_constant< // + not ::std::is_same_v> // + && not detail::_is_some_unexpected<::std::remove_cvref_t> // + && ::std::is_constructible_v // + && ::std::is_assignable_v // + && (::std::is_nothrow_constructible_v // + || ::std::is_nothrow_move_constructible_v // + || ::std::is_nothrow_move_constructible_v)>; template static constexpr auto _and_then(Self &&self, Fn &&fn) // @@ -434,7 +813,7 @@ template class expected { constexpr expected() // noexcept(::std::is_nothrow_default_constructible_v) // extension requires ::std::is_default_constructible_v - : v_(), set_(true) + : _base(::std::in_place) { } @@ -448,7 +827,7 @@ template class expected { noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_copy_constructible_v) // extension requires(::std::is_copy_constructible_v && ::std::is_copy_constructible_v && (not ::std::is_trivially_copy_constructible_v || not ::std::is_trivially_copy_constructible_v)) - : set_(s.set_) + : _base(detail::_branch, s.set_) { if (set_) ::std::construct_at(::std::addressof(v_), s.v_); @@ -464,7 +843,7 @@ template class expected { noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && ::std::is_move_constructible_v && (not ::std::is_trivially_move_constructible_v || not ::std::is_trivially_move_constructible_v)) - : set_(s.set_) + : _base(detail::_branch, s.set_) { if (set_) ::std::construct_at(::std::addressof(v_), ::std::move(s.v_)); @@ -478,7 +857,7 @@ template class expected { noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) // extension requires(_can_copy_convert::value) - : set_(s.set_) + : _base(detail::_branch, s.set_) { if (set_) ::std::construct_at(::std::addressof(v_), s.v_); @@ -491,7 +870,7 @@ template class expected { expected(expected &&s) // noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) // extension requires(_can_move_convert::value) - : set_(s.set_) + : _base(detail::_branch, s.set_) { if (set_) ::std::construct_at(::std::addressof(v_), ::std::move(s.v_)); @@ -503,7 +882,7 @@ template class expected { constexpr explicit(not ::std::is_convertible_v) expected(U &&v) // noexcept(::std::is_nothrow_constructible_v) // extension requires(_can_convert::value) - : v_(FWD(v)), set_(true) + : _base(::std::in_place, FWD(v)) { } @@ -511,7 +890,7 @@ template class expected { constexpr explicit(!::std::is_convertible_v) expected(unexpected const &g) // noexcept(::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v) - : e_(::std::forward(g.error())), set_(false) + : _base(unexpect, ::std::forward(g.error())) { } @@ -519,325 +898,128 @@ template class expected { constexpr explicit(!::std::is_convertible_v) expected(unexpected &&g) // noexcept(::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v) - : e_(::std::forward(g.error())), set_(false) + : _base(unexpect, ::std::forward(g.error())) { } template constexpr explicit expected(::std::in_place_t, Args &&...a) // noexcept(::std::is_nothrow_constructible_v) // extension - requires ::std::is_constructible_v - : v_(FWD(a)...), set_(true) - { - } - template - constexpr explicit expected(::std::in_place_t, ::std::initializer_list il, Args &&...a) // - noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension - requires ::std::is_constructible_v &, Args...> - : v_(il, FWD(a)...), set_(true) - { - } - template - constexpr explicit expected(unexpect_t, Args &&...a) // - noexcept(::std::is_nothrow_constructible_v) // extension - requires ::std::is_constructible_v - : e_(FWD(a)...), set_(false) - { - } - template - constexpr explicit expected(unexpect_t, ::std::initializer_list il, Args &&...a) // - noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension - requires ::std::is_constructible_v &, Args...> - : e_(il, FWD(a)...), set_(false) - { - } - - // [expected.object.dtor], destructor - constexpr ~expected() noexcept - requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) - = default; - constexpr ~expected() // - requires(::std::is_trivially_destructible_v && not ::std::is_trivially_destructible_v) - { - if (not set_) - ::std::destroy_at(::std::addressof(e_)); - // else T is trivially destructible, no need to do anything - } - constexpr ~expected() // - requires(not ::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) - { - if (set_) - ::std::destroy_at(::std::addressof(v_)); - // else E is trivially destructible, no need to do anything - } - constexpr ~expected() // - requires(not ::std::is_trivially_destructible_v && not ::std::is_trivially_destructible_v) - { - if (set_) - ::std::destroy_at(::std::addressof(v_)); - else - ::std::destroy_at(::std::addressof(e_)); - } - - // [expected.object.assign], assignment - constexpr expected &operator=(expected const &) = delete; - constexpr expected &operator=(expected const &s) // - noexcept(::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v - && ::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v) // extension - requires(::std::is_copy_assignable_v && ::std::is_copy_constructible_v && ::std::is_copy_assignable_v - && ::std::is_copy_constructible_v - && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) - { - if (set_ && s.set_) { - v_ = s.v_; - } else if (set_) { - _reinit(e_, v_, s.e_); - set_ = false; - } else if (s.set_) { - _reinit(v_, e_, s.v_); - set_ = true; - } else { - e_ = s.e_; - } - return *this; - } - - constexpr expected &operator=(expected &&s) // - noexcept(::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v - && ::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) // required - requires(::std::is_move_constructible_v && ::std::is_move_assignable_v && ::std::is_move_constructible_v - && ::std::is_move_assignable_v - && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) - { - if (set_ && s.set_) { - v_ = ::std::move(s.v_); - } else if (set_) { - _reinit(e_, v_, ::std::move(s.e_)); - set_ = false; - } else if (s.set_) { - _reinit(v_, e_, ::std::move(s.v_)); - set_ = true; - } else { - e_ = ::std::move(s.e_); - } - return *this; - } - - template - constexpr expected &operator=(U &&s) // - noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension - requires(_can_convert_assign::value) - { - if (set_) { - v_ = FWD(s); - } else { - _reinit(v_, e_, FWD(s)); - set_ = true; - } - return *this; - } - - template - constexpr expected &operator=(unexpected const &s) // - noexcept(::std::is_nothrow_assignable_v - && ::std::is_nothrow_constructible_v) // extension - requires(::std::is_constructible_v && ::std::is_assignable_v - && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v - || ::std::is_nothrow_move_constructible_v)) - { - if (not set_) { - e_ = ::std::forward(s.error()); - } else { - _reinit(e_, v_, ::std::forward(s.error())); - set_ = false; - } - return *this; - } - - template - constexpr expected &operator=(unexpected &&s) // - noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension - requires(::std::is_constructible_v && ::std::is_assignable_v - && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v - || ::std::is_nothrow_move_constructible_v)) - { - if (not set_) { - e_ = ::std::forward(s.error()); - } else { - _reinit(e_, v_, ::std::forward(s.error())); - set_ = false; - } - return *this; - } - - template - constexpr T &emplace(Args &&...args) noexcept - requires(::std::is_nothrow_constructible_v) - { - if (set_) { - ::std::destroy_at(::std::addressof(v_)); - } else { - ::std::destroy_at(::std::addressof(e_)); - set_ = true; - } - return *::std::construct_at(::std::addressof(v_), std::forward(args)...); - } - - template - constexpr T &emplace(::std::initializer_list il, Args &&...args) noexcept - requires(::std::is_nothrow_constructible_v &, Args...>) - { - if (set_) { - ::std::destroy_at(::std::addressof(v_)); - } else { - ::std::destroy_at(::std::addressof(e_)); - set_ = true; - } - return *::std::construct_at(::std::addressof(v_), il, std::forward(args)...); - } - - // [expected.object.swap], swap - constexpr void - swap(expected &rhs) noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v - && ::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) - requires(::std::is_swappable_v && ::std::is_swappable_v && ::std::is_move_constructible_v - && ::std::is_move_constructible_v - && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) - { - bool const lhset = has_value(); - bool const rhset = rhs.has_value(); - if (lhset == rhset) { - if (lhset) { - using ::std::swap; - swap(v_, rhs.v_); - } else { - using ::std::swap; - swap(e_, rhs.e_); - } - } else { - if (lhset) { - _swap(*this, rhs); - } else { - _swap(rhs, *this); - } - } - } - - constexpr friend void swap(expected &x, expected &y) noexcept(noexcept(x.swap(y))) - requires requires { x.swap(y); } - { - x.swap(y); - } - - // [expected.object.obs], observers - constexpr T const *operator->() const noexcept - { - ASSERT(set_); - return ::std::addressof(v_); - } - constexpr T *operator->() noexcept - { - ASSERT(set_); - return ::std::addressof(v_); - } - constexpr T const &operator*() const & noexcept { return *(this->operator->()); } - constexpr T &operator*() & noexcept { return *(this->operator->()); } - constexpr T const &&operator*() const && noexcept { return ::std::move(*(this->operator->())); } - constexpr T &&operator*() && noexcept { return ::std::move(*(this->operator->())); } - constexpr explicit operator bool() const noexcept { return set_; } - constexpr bool has_value() const noexcept { return set_; } - constexpr bool has_error() const noexcept { return !set_; } // P3798 - constexpr T const &value() const & - { - static_assert(::std::is_copy_constructible_v); - if (not set_) - throw bad_expected_access(e_); - return v_; - } - constexpr T &value() & - { - static_assert(::std::is_copy_constructible_v); - if (not set_) - throw bad_expected_access(::std::as_const(e_)); - return v_; + requires ::std::is_constructible_v + : _base(::std::in_place, FWD(a)...) + { } - constexpr T const &&value() const && + template + constexpr explicit expected(::std::in_place_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension + requires ::std::is_constructible_v &, Args...> + : _base(::std::in_place, il, FWD(a)...) { - static_assert(::std::is_copy_constructible_v); - static_assert(::std::is_constructible_v); - if (not set_) - throw bad_expected_access(::std::move(e_)); - return ::std::move(v_); } - constexpr T &&value() && + template + constexpr explicit expected(unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires ::std::is_constructible_v + : _base(unexpect, FWD(a)...) { - static_assert(::std::is_copy_constructible_v); - static_assert(::std::is_constructible_v); - if (not set_) - throw bad_expected_access(::std::move(e_)); - return ::std::move(v_); } - constexpr E const &error() const & noexcept + template + constexpr explicit expected(unexpect_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension + requires ::std::is_constructible_v &, Args...> + : _base(unexpect, il, FWD(a)...) { - ASSERT(not set_); - return e_; } - constexpr E &error() & noexcept + + // [expected.object.dtor], destructor inherited from _storage + + // [expected.object.assign], assignment; bodies delegate to _storage helpers + constexpr expected &operator=(expected const &) = delete; + constexpr expected &operator=(expected const &s) // + noexcept(::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v + && ::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v) // extension + requires(::std::is_copy_assignable_v && ::std::is_copy_constructible_v && ::std::is_copy_assignable_v + && ::std::is_copy_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) { - ASSERT(not set_); - return e_; + this->_assign(static_cast<_base const &>(s)); + return *this; } - constexpr E const &&error() const && noexcept + + constexpr expected &operator=(expected &&s) // + noexcept(::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v + && ::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) // required + requires(::std::is_move_constructible_v && ::std::is_move_assignable_v && ::std::is_move_constructible_v + && ::std::is_move_assignable_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) { - ASSERT(not set_); - return ::std::move(e_); + this->_assign(static_cast<_base &&>(s)); + return *this; } - constexpr E &&error() && noexcept + + template + constexpr expected &operator=(U &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension + requires(_can_convert_assign::value) { - ASSERT(not set_); - return ::std::move(e_); + this->_assign_value(FWD(s)); + return *this; } - template - constexpr T value_or(U &&v) const & // - noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension + template + constexpr expected &operator=(unexpected const &s) // + noexcept(::std::is_nothrow_assignable_v + && ::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v && ::std::is_assignable_v + && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v + || ::std::is_nothrow_move_constructible_v)) { - static_assert(::std::is_copy_constructible_v); - static_assert(::std::is_convertible_v); - return set_ ? v_ : static_cast(FWD(v)); + this->_assign_unexpected(s); + return *this; } - template - constexpr T value_or(U &&v) && // - noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension + + template + constexpr expected &operator=(unexpected &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v && ::std::is_assignable_v + && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v + || ::std::is_nothrow_move_constructible_v)) { - static_assert(::std::is_move_constructible_v); - static_assert(::std::is_convertible_v); - return set_ ? ::std::move(v_) : static_cast(FWD(v)); + this->_assign_unexpected(::std::move(s)); + return *this; } - template - constexpr E error_or(G &&e) const & // - noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension + // [expected.object.emplace], emplace inherited from _storage + using _base::emplace; + + // [expected.object.swap], swap; body delegates to _storage helper + constexpr void + swap(expected &rhs) noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v + && ::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) + requires(::std::is_swappable_v && ::std::is_swappable_v && ::std::is_move_constructible_v + && ::std::is_move_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) { - static_assert(::std::is_copy_constructible_v); - static_assert(::std::is_convertible_v); - if (set_) { - return FWD(e); - } - return e_; + this->_swap_with(rhs); } - template - constexpr E error_or(G &&e) && // - noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension + + constexpr friend void swap(expected &x, expected &y) noexcept(noexcept(x.swap(y))) + requires requires { x.swap(y); } { - static_assert(::std::is_move_constructible_v); - static_assert(::std::is_convertible_v); - if (set_) { - return FWD(e); - } - return ::std::move(e_); + x.swap(y); } + // [expected.object.obs], observers inherited from _storage + using _base::operator*; + using _base::operator->; + using _base::operator bool; + using _base::error; + using _base::error_or; + using _base::has_error; + using _base::has_value; + using _base::value; + using _base::value_or; + // [expected.object.monadic], monadic operations template constexpr auto and_then(F &&f) & // @@ -1001,18 +1183,11 @@ requires { } return x.error() == e.error(); } - -private: - union { - T v_; - E e_; - }; - bool set_; }; template requires ::std::is_void_v -class expected { +class expected : private detail::_storage { static_assert(detail::_is_valid_unexpected); template @@ -1027,6 +1202,13 @@ class expected { template using _can_move_convert = _can_convert_detail; template friend class expected; + // Bring the storage members into scope so unqualified lookup resolves them + // through the dependent base, and to document the inherited surface. + using _base = detail::_storage; + using _base::e_; + using _base::set_; + using _base::v_; + template static constexpr auto _and_then(Self &&self, Fn &&fn) // noexcept(::std::is_nothrow_invocable_v && ::std::is_nothrow_constructible_v) @@ -1104,19 +1286,23 @@ class expected { template using rebind = expected; // [expected.void.cons], constructors - constexpr expected() noexcept : d_(), set_(true) {} + constexpr expected() noexcept : _base(::std::in_place) {} constexpr expected(expected const &) = delete; constexpr expected(expected const &) // requires(::std::is_copy_constructible_v && ::std::is_trivially_copy_constructible_v) // = default; + // Delegate to the in_place ctor of `_storage`, which default-initialises the + // value arm (`_dummy_t`) and sets `set_` to true; the body then switches to + // the error arm when the source holds an error. constexpr expected(expected const &s) // noexcept(::std::is_nothrow_copy_constructible_v) // extension requires(::std::is_copy_constructible_v && not ::std::is_trivially_copy_constructible_v) // - : d_(), set_(s.set_) + : _base(::std::in_place) { - if (not set_) { - ::std::destroy_at(::std::addressof(d_)); + if (not s.set_) { + ::std::destroy_at(::std::addressof(v_)); ::std::construct_at(::std::addressof(e_), s.e_); + set_ = false; } } @@ -1126,11 +1312,12 @@ class expected { constexpr expected(expected &&s) // noexcept(::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && not ::std::is_trivially_move_constructible_v) - : d_(), set_(s.set_) + : _base(::std::in_place) { - if (not set_) { - ::std::destroy_at(::std::addressof(d_)); + if (not s.set_) { + ::std::destroy_at(::std::addressof(v_)); ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + set_ = false; } } @@ -1138,22 +1325,24 @@ class expected { constexpr explicit(not ::std::is_convertible_v) expected(expected const &s) // noexcept(::std::is_nothrow_constructible_v) // extension requires(_can_copy_convert::value) - : d_(), set_(s.set_) + : _base(::std::in_place) { - if (not set_) { - ::std::destroy_at(::std::addressof(d_)); + if (not s.set_) { + ::std::destroy_at(::std::addressof(v_)); ::std::construct_at(::std::addressof(e_), s.e_); + set_ = false; } } template constexpr explicit(not ::std::is_convertible_v) expected(expected &&s) // noexcept(::std::is_nothrow_constructible_v) // extension requires(_can_move_convert::value) - : d_(), set_(s.set_) + : _base(::std::in_place) { - if (not set_) { - ::std::destroy_at(::std::addressof(d_)); + if (not s.set_) { + ::std::destroy_at(::std::addressof(v_)); ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + set_ = false; } } @@ -1161,66 +1350,43 @@ class expected { constexpr explicit(!::std::is_convertible_v) expected(unexpected const &g) // noexcept(::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v) - : e_(::std::forward(g.error())), set_(false) + : _base(unexpect, ::std::forward(g.error())) { } template constexpr explicit(!::std::is_convertible_v) expected(unexpected &&g) // noexcept(::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v) - : e_(::std::forward(g.error())), set_(false) + : _base(unexpect, ::std::forward(g.error())) { } - constexpr explicit expected(::std::in_place_t) noexcept : d_(), set_(true) {} + constexpr explicit expected(::std::in_place_t) noexcept : _base(::std::in_place) {} template constexpr explicit expected(unexpect_t, Args &&...a) // noexcept(::std::is_nothrow_constructible_v) // extension requires ::std::is_constructible_v - : e_(FWD(a)...), set_(false) + : _base(unexpect, FWD(a)...) { } template constexpr explicit expected(unexpect_t, ::std::initializer_list il, Args &&...a) // noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension requires ::std::is_constructible_v &, Args...> - : e_(il, FWD(a)...), set_(false) + : _base(unexpect, il, FWD(a)...) { } - // [expected.void.dtor], destructor - constexpr ~expected() noexcept - requires(::std::is_trivially_destructible_v) - = default; - constexpr ~expected() // - requires(not ::std::is_trivially_destructible_v) - { - if (not set_) - ::std::destroy_at(::std::addressof(e_)); - else - ::std::destroy_at(::std::addressof(d_)); - } + // [expected.void.dtor], destructor inherited from _storage - // [expected.void.assign], assignment + // [expected.void.assign], assignment; bodies delegate to _storage helpers constexpr expected &operator=(expected const &) = delete; constexpr expected &operator=(expected const &s) // noexcept(::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v) // extension requires(::std::is_copy_assignable_v && ::std::is_copy_constructible_v) { - if (set_ && s.set_) { - ; - } else if (set_) { - ::std::destroy_at(::std::addressof(d_)); - ::std::construct_at(::std::addressof(e_), s.e_); - set_ = false; - } else if (s.set_) { - ::std::destroy_at(::std::addressof(e_)); - ::std::construct_at(::std::addressof(d_)); - set_ = true; - } else { - e_ = s.e_; - } + this->_assign(static_cast<_base const &>(s)); return *this; } @@ -1228,19 +1394,7 @@ class expected { noexcept(::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && ::std::is_move_assignable_v) { - if (set_ && s.set_) { - ; - } else if (set_) { - ::std::destroy_at(::std::addressof(d_)); - ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); - set_ = false; - } else if (s.set_) { - ::std::destroy_at(::std::addressof(e_)); - ::std::construct_at(::std::addressof(d_)); - set_ = true; - } else { - e_ = ::std::move(s.e_); - } + this->_assign(static_cast<_base &&>(s)); return *this; } @@ -1250,13 +1404,7 @@ class expected { && ::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v && ::std::is_assignable_v) { - if (set_) { - ::std::destroy_at(::std::addressof(d_)); - ::std::construct_at(::std::addressof(e_), ::std::forward(s.error())); - set_ = false; - } else { - e_ = ::std::forward(s.error()); - } + this->_assign_unexpected(s); return *this; } @@ -1265,49 +1413,19 @@ class expected { noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v && ::std::is_assignable_v) { - if (set_) { - ::std::destroy_at(::std::addressof(d_)); - ::std::construct_at(::std::addressof(e_), ::std::forward(s.error())); - set_ = false; - } else { - e_ = ::std::forward(s.error()); - } + this->_assign_unexpected(::std::move(s)); return *this; } - constexpr void emplace() noexcept - { - if (not set_) { - ::std::destroy_at(::std::addressof(e_)); - ::std::construct_at(::std::addressof(d_)); - set_ = true; - } - } + // [expected.void.emplace], emplace inherited from _storage + using _base::emplace; - // [expected.void.swap], swap + // [expected.void.swap], swap; body delegates to _storage helper constexpr void swap(expected &rhs) // noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) requires(::std::is_swappable_v && ::std::is_move_constructible_v) { - bool const lhset = has_value(); - bool const rhset = rhs.has_value(); - if (lhset == rhset) { - if (not lhset) { - using ::std::swap; - swap(e_, rhs.e_); - } - } else { - if (lhset) { - ::std::destroy_at(::std::addressof(d_)); - ::std::construct_at(::std::addressof(e_), ::std::move(rhs.e_)); - ::std::destroy_at(::std::addressof(rhs.e_)); - ::std::construct_at(::std::addressof(rhs.d_)); - set_ = false; - rhs.set_ = true; - } else { - rhs.swap(*this); - } - } + this->_swap_with(rhs); } constexpr friend void swap(expected &x, expected &y) noexcept(noexcept(x.swap(y))) @@ -1316,67 +1434,14 @@ class expected { x.swap(y); } - // [expected.void.obs], observers - constexpr explicit operator bool() const noexcept { return set_; } - constexpr bool has_value() const noexcept { return set_; } - constexpr bool has_error() const noexcept { return !set_; } // P3798 - constexpr void operator*() const noexcept { ASSERT(set_); } - constexpr void value() const & - { - static_assert(::std::is_copy_constructible_v); - if (not set_) - throw bad_expected_access(e_); - } - constexpr void value() && - { - static_assert(::std::is_copy_constructible_v); - static_assert(::std::is_move_constructible_v); - if (not set_) - throw bad_expected_access(::std::move(e_)); - } - constexpr E const &error() const & noexcept - { - ASSERT(not set_); - return e_; - } - constexpr E &error() & noexcept - { - ASSERT(not set_); - return e_; - } - constexpr E const &&error() const && noexcept - { - ASSERT(not set_); - return ::std::move(e_); - } - constexpr E &&error() && noexcept - { - ASSERT(not set_); - return ::std::move(e_); - } - - template - constexpr E error_or(G &&e) const & // - noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension - { - static_assert(::std::is_copy_constructible_v); - static_assert(::std::is_convertible_v); - if (set_) { - return FWD(e); - } - return e_; - } - template - constexpr E error_or(G &&e) && // - noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension - { - static_assert(::std::is_move_constructible_v); - static_assert(::std::is_convertible_v); - if (set_) { - return FWD(e); - } - return ::std::move(e_); - } + // [expected.void.obs], observers inherited from _storage + using _base::operator*; + using _base::operator bool; + using _base::error; + using _base::error_or; + using _base::has_error; + using _base::has_value; + using _base::value; // [expected.void.monadic], monadic operations template @@ -1524,18 +1589,6 @@ class expected { } return x.error() == e.error(); } - -private: - struct dummy final { - constexpr dummy() noexcept = default; - constexpr ~dummy() noexcept = default; - }; - - union { - [[no_unique_address]] dummy d_; - E e_; - }; - bool set_; }; } // namespace pfn From 0205f89f2ae78968ad8583dea513e12590d99390 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 14 May 2026 14:21:37 +0200 Subject: [PATCH 02/21] Fixes and further refactoring --- include/pfn/expected.hpp | 544 ++++++++++++++++++++++----------------- 1 file changed, 305 insertions(+), 239 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 0e4c18ec..a84c8853 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -184,116 +184,255 @@ constexpr bool _is_valid_expected = // && not _is_some_unexpected<::std::remove_cv_t> // && detail::_is_valid_unexpected; -static constexpr struct _branch_t { - constexpr explicit _branch_t() noexcept = default; -} _branch{}; +// Internal union wrapper for the value/error storage. Copy/move ctors +// are defaulted iff both `T` and `E` are trivially copy/move-constructible; +// otherwise deleted (callers placement-new the appropriate arm). +// The in_place / unexpect ctors activate `v_` / `e_` respectively. +template union _storage_union_t { + using _value_t = T; + T v_; + E e_; + + constexpr _storage_union_t(_storage_union_t const &) = delete; + constexpr _storage_union_t(_storage_union_t const &) // + requires(::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v) + = default; + constexpr _storage_union_t(_storage_union_t &&) = delete; + constexpr _storage_union_t(_storage_union_t &&) // + requires(::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v) + = default; + constexpr _storage_union_t &operator=(_storage_union_t const &) = delete; + constexpr _storage_union_t &operator=(_storage_union_t &&) = delete; + + constexpr explicit _storage_union_t() noexcept {} + + template + constexpr explicit _storage_union_t(::std::in_place_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) + requires ::std::is_constructible_v + : v_(FWD(a)...) + { + } + template + constexpr explicit _storage_union_t(unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) + requires ::std::is_constructible_v + : e_(FWD(a)...) + { + } + + constexpr ~_storage_union_t() noexcept + requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) + = default; + constexpr ~_storage_union_t() noexcept {} + + // Implements the reinit-expected helper from [expected.object.assign]. + template + static constexpr void _reinit(New &newval, Old &oldval, Args &&...args) // + noexcept(::std::is_nothrow_constructible_v) + { + if constexpr (::std::is_nothrow_constructible_v) { + ::std::destroy_at(::std::addressof(oldval)); + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } else if constexpr (::std::is_nothrow_move_constructible_v) { + New tmp(::std::forward(args)...); + ::std::destroy_at(::std::addressof(oldval)); + ::std::construct_at(::std::addressof(newval), std::move(tmp)); + } else if constexpr (::std::is_trivially_copyable_v) { + // Workaround for https://github.com/llvm/llvm-project/issues/196520: + // clang on aarch64 sinks the snapshot load past the store-through-newval + // when Old's TBAA tag differs from New's, corrupting the strong-EG + // restoration on catch. A byte-buffer snapshot via std::memcpy is opaque + // to TBAA, and preserving *oldval in place across the try (no destroy_at) + // makes the catch a plain byte restore -- which for trivially-copyable + // (and therefore trivially-destructible per [class.prop]/1). Old is + // observationally identical to the destroy-and-recreate branch below. + if (not ::std::is_constant_evaluated()) { + alignas(Old) unsigned char _bytes[sizeof(Old)]; + ::std::memcpy(_bytes, ::std::addressof(oldval), sizeof(Old)); + try { + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } catch (...) { + ::std::memcpy(::std::addressof(oldval), _bytes, sizeof(Old)); + throw; + } + } else { + // LCOV_EXCL_START constant-evaluated only; runtime branches are above and below + Old tmp(std::move(oldval)); + ::std::destroy_at(::std::addressof(oldval)); + try { + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } catch (...) { + ::std::construct_at(::std::addressof(oldval), std::move(tmp)); + throw; + } + // LCOV_EXCL_STOP + } + } else { + Old tmp(std::move(oldval)); + ::std::destroy_at(::std::addressof(oldval)); + try { + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } catch (...) { + ::std::construct_at(::std::addressof(oldval), std::move(tmp)); + throw; + } + } + } +}; -struct _dummy_t final { - constexpr _dummy_t() noexcept = default; +// Void specialization: value state activates a trivial `_dummy_t v_` +// placeholder so the union always has an active member (required by +// constant evaluation). All `v_` operations are no-ops at runtime. +template union _storage_union_t { + struct _dummy_t final { + constexpr _dummy_t() noexcept = default; + }; + + using _value_t = _dummy_t; + _dummy_t v_; + E e_; + + constexpr _storage_union_t(_storage_union_t const &) = delete; + constexpr _storage_union_t(_storage_union_t const &) // + requires(::std::is_trivially_copy_constructible_v) + = default; + constexpr _storage_union_t(_storage_union_t &&) = delete; + constexpr _storage_union_t(_storage_union_t &&) // + requires(::std::is_trivially_move_constructible_v) + = default; + constexpr _storage_union_t &operator=(_storage_union_t const &) = delete; + constexpr _storage_union_t &operator=(_storage_union_t &&) = delete; + + constexpr explicit _storage_union_t() noexcept {} + constexpr explicit _storage_union_t(::std::in_place_t) noexcept : v_{} {} + + template + constexpr explicit _storage_union_t(unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) + requires ::std::is_constructible_v + : e_(FWD(a)...) + { + } + + constexpr ~_storage_union_t() noexcept + requires(::std::is_trivially_destructible_v) + = default; + constexpr ~_storage_union_t() noexcept {} + + // [expected.void.assign] mandates direct construction (no temporary). + template + static constexpr void _reinit(New &newval, Old &oldval, Args &&...args) // + noexcept(::std::is_nothrow_constructible_v) + { + if constexpr (::std::is_nothrow_constructible_v) { + ::std::destroy_at(::std::addressof(oldval)); + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } else { + // On exception the trivial `_dummy_t` oldval is reconstructed so the + // union always has an active member (required for constant evaluation). + // If New is not nothrow-constructible then it's not _dummy_t, hence + // oldval must be _dummy_t. + static_assert(::std::is_same_v, _dummy_t>); + ::std::destroy_at(::std::addressof(oldval)); + try { + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } catch (...) { + ::std::construct_at(::std::addressof(oldval)); + throw; + } + } + } }; // Shared storage base for ::pfn::expected. Members are public because the // inheritance is private; sibling-instantiation access goes through the // `template friend class expected;` declaration on the derived. template struct _storage { - using _storage_t = ::std::conditional_t<::std::is_same_v, _dummy_t, T>; - union { - _storage_t v_; - E e_; - }; + using _storage_t = _storage_union_t; + // `T` for non-void, trivial `_dummy_t` for void; used as observer return + // type (void value-returning overloads are requires-disabled, so the + // placeholder is never observable) and in destructor constraints. + using _value_t = _storage_t::_value_t; + _storage_t storage_; bool set_; template constexpr explicit _storage(::std::in_place_t, Args &&...a) // - noexcept(::std::is_nothrow_constructible_v<_storage_t, Args...>) - requires ::std::is_constructible_v<_storage_t, Args...> - : v_(FWD(a)...), set_(true) - { - } - template - constexpr explicit _storage(::std::in_place_t, ::std::initializer_list il, Args &&...a) // - noexcept(::std::is_nothrow_constructible_v<_storage_t, ::std::initializer_list &, Args...>) - requires ::std::is_constructible_v<_storage_t, ::std::initializer_list &, Args...> - : v_(il, FWD(a)...), set_(true) + noexcept(::std::is_nothrow_constructible_v<_storage_t, ::std::in_place_t, Args...>) + requires ::std::is_constructible_v<_storage_t, ::std::in_place_t, Args...> + : storage_(::std::in_place, FWD(a)...), set_(true) { } template constexpr explicit _storage(unexpect_t, Args &&...a) // noexcept(::std::is_nothrow_constructible_v) requires ::std::is_constructible_v - : e_(FWD(a)...), set_(false) - { - } - template - constexpr explicit _storage(unexpect_t, ::std::initializer_list il, Args &&...a) // - noexcept(::std::is_nothrow_constructible_v &, Args...>) - requires ::std::is_constructible_v &, Args...> - : e_(il, FWD(a)...), set_(false) + : storage_(unexpect, FWD(a)...), set_(false) { } - // Branch tag: initializes only `set_`, leaving the union uninitialized so - // the derived ctor body can placement-new into the active arm. - constexpr explicit _storage(_branch_t, bool s) noexcept : set_(s) {} + // Leaves `storage_` with no active union member so the derived ctor + // body can placement-new the appropriate arm. + constexpr explicit _storage(bool s) noexcept : storage_(), set_(s) {} constexpr _storage(_storage const &) = default; constexpr _storage(_storage &&) = default; - constexpr _storage &operator=(_storage const &) = default; - constexpr _storage &operator=(_storage &&) = default; + constexpr _storage &operator=(_storage const &) = delete; + constexpr _storage &operator=(_storage &&) = delete; constexpr ~_storage() noexcept - requires(::std::is_trivially_destructible_v<_storage_t> && ::std::is_trivially_destructible_v) + requires(::std::is_trivially_destructible_v<_value_t> && ::std::is_trivially_destructible_v) = default; constexpr ~_storage() // - requires(::std::is_trivially_destructible_v<_storage_t> && not ::std::is_trivially_destructible_v) + requires(::std::is_trivially_destructible_v<_value_t> && not ::std::is_trivially_destructible_v) { if (not set_) - ::std::destroy_at(::std::addressof(e_)); + ::std::destroy_at(::std::addressof(storage_.e_)); } constexpr ~_storage() // - requires(not ::std::is_trivially_destructible_v<_storage_t> && ::std::is_trivially_destructible_v) + requires(not ::std::is_trivially_destructible_v<_value_t> && ::std::is_trivially_destructible_v) { if (set_) - ::std::destroy_at(::std::addressof(v_)); + ::std::destroy_at(::std::addressof(storage_.v_)); } constexpr ~_storage() // - requires(not ::std::is_trivially_destructible_v<_storage_t> && not ::std::is_trivially_destructible_v) + requires(not ::std::is_trivially_destructible_v<_value_t> && not ::std::is_trivially_destructible_v) { if (set_) - ::std::destroy_at(::std::addressof(v_)); + ::std::destroy_at(::std::addressof(storage_.v_)); else - ::std::destroy_at(::std::addressof(e_)); + ::std::destroy_at(::std::addressof(storage_.e_)); } - // [expected.object.obs], observers - constexpr _storage_t const *operator->() const noexcept + constexpr _value_t const *operator->() const noexcept requires(not ::std::is_void_v) { ASSERT(set_); - return ::std::addressof(v_); + return ::std::addressof(storage_.v_); } - constexpr _storage_t *operator->() noexcept + constexpr _value_t *operator->() noexcept requires(not ::std::is_void_v) { ASSERT(set_); - return ::std::addressof(v_); + return ::std::addressof(storage_.v_); } - constexpr _storage_t const &operator*() const & noexcept + constexpr _value_t const &operator*() const & noexcept requires(not ::std::is_void_v) { return *(this->operator->()); } - constexpr _storage_t &operator*() & noexcept + constexpr _value_t &operator*() & noexcept requires(not ::std::is_void_v) { return *(this->operator->()); } - constexpr _storage_t const &&operator*() const && noexcept + constexpr _value_t const &&operator*() const && noexcept requires(not ::std::is_void_v) { return ::std::move(*(this->operator->())); } - constexpr _storage_t &&operator*() && noexcept + constexpr _value_t &&operator*() && noexcept requires(not ::std::is_void_v) { return ::std::move(*(this->operator->())); @@ -321,46 +460,46 @@ template struct _storage { constexpr explicit operator bool() const noexcept { return set_; } constexpr bool has_value() const noexcept { return set_; } constexpr bool has_error() const noexcept { return !set_; } // P3798 - constexpr _storage_t const &value() const & + constexpr _value_t const &value() const & requires(not ::std::is_void_v) { static_assert(::std::is_copy_constructible_v); if (not set_) - throw bad_expected_access(e_); - return v_; + throw bad_expected_access(storage_.e_); + return storage_.v_; } - constexpr _storage_t &value() & + constexpr _value_t &value() & requires(not ::std::is_void_v) { static_assert(::std::is_copy_constructible_v); if (not set_) - throw bad_expected_access(::std::as_const(e_)); - return v_; + throw bad_expected_access(::std::as_const(storage_.e_)); + return storage_.v_; } - constexpr _storage_t const &&value() const && + constexpr _value_t const &&value() const && requires(not ::std::is_void_v) { static_assert(::std::is_copy_constructible_v); static_assert(::std::is_constructible_v); if (not set_) - throw bad_expected_access(::std::move(e_)); - return ::std::move(v_); + throw bad_expected_access(::std::move(storage_.e_)); + return ::std::move(storage_.v_); } - constexpr _storage_t &&value() && + constexpr _value_t &&value() && requires(not ::std::is_void_v) { static_assert(::std::is_copy_constructible_v); static_assert(::std::is_constructible_v); if (not set_) - throw bad_expected_access(::std::move(e_)); - return ::std::move(v_); + throw bad_expected_access(::std::move(storage_.e_)); + return ::std::move(storage_.v_); } constexpr void value() const & requires(::std::is_void_v) { static_assert(::std::is_copy_constructible_v); if (not set_) - throw bad_expected_access(e_); + throw bad_expected_access(storage_.e_); } constexpr void value() && requires(::std::is_void_v) @@ -368,27 +507,27 @@ template struct _storage { static_assert(::std::is_copy_constructible_v); static_assert(::std::is_move_constructible_v); if (not set_) - throw bad_expected_access(::std::move(e_)); + throw bad_expected_access(::std::move(storage_.e_)); } constexpr E const &error() const & noexcept { ASSERT(not set_); - return e_; + return storage_.e_; } constexpr E &error() & noexcept { ASSERT(not set_); - return e_; + return storage_.e_; } constexpr E const &&error() const && noexcept { ASSERT(not set_); - return ::std::move(e_); + return ::std::move(storage_.e_); } constexpr E &&error() && noexcept { ASSERT(not set_); - return ::std::move(e_); + return ::std::move(storage_.e_); } template @@ -398,7 +537,7 @@ template struct _storage { { static_assert(::std::is_copy_constructible_v); static_assert(::std::is_convertible_v); - return set_ ? v_ : static_cast(FWD(v)); + return set_ ? storage_.v_ : static_cast(FWD(v)); } template constexpr T value_or(U &&v) && // @@ -407,7 +546,7 @@ template struct _storage { { static_assert(::std::is_move_constructible_v); static_assert(::std::is_convertible_v); - return set_ ? ::std::move(v_) : static_cast(FWD(v)); + return set_ ? ::std::move(storage_.v_) : static_cast(FWD(v)); } template @@ -419,7 +558,7 @@ template struct _storage { if (set_) { return FWD(e); } - return e_; + return storage_.e_; } template constexpr E error_or(G &&e) && // @@ -430,156 +569,97 @@ template struct _storage { if (set_) { return FWD(e); } - return ::std::move(e_); + return ::std::move(storage_.e_); } - // Internal value accessor used by monadic helpers; `value()` performs the - // access check via exception, this one assumes the precondition. - constexpr _storage_t const &_value() const & noexcept + // Unchecked value accessor used by monadic helpers; `value()` throws, + // this asserts the precondition. + constexpr _value_t const &_value() const & noexcept requires(not ::std::is_void_v) { ASSERT(set_); - return v_; + return storage_.v_; } - constexpr _storage_t &_value() & noexcept + constexpr _value_t &_value() & noexcept requires(not ::std::is_void_v) { ASSERT(set_); - return v_; + return storage_.v_; } - constexpr _storage_t const &&_value() const && noexcept + constexpr _value_t const &&_value() const && noexcept requires(not ::std::is_void_v) { ASSERT(set_); - return ::std::move(v_); + return ::std::move(storage_.v_); } - constexpr _storage_t &&_value() && noexcept + constexpr _value_t &&_value() && noexcept requires(not ::std::is_void_v) { ASSERT(set_); - return ::std::move(v_); - } - - // [expected.object.assign] - template - static constexpr void _reinit(New &newval, Old &oldval, Args &&...args) // - noexcept(::std::is_nothrow_constructible_v) - { - if constexpr (::std::is_void_v || ::std::is_nothrow_constructible_v) { - ::std::destroy_at(::std::addressof(oldval)); - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); - } else if constexpr (::std::is_nothrow_move_constructible_v) { - New tmp(::std::forward(args)...); - ::std::destroy_at(::std::addressof(oldval)); - ::std::construct_at(::std::addressof(newval), std::move(tmp)); - } else if constexpr (::std::is_trivially_copyable_v) { - // Workaround for https://github.com/llvm/llvm-project/issues/196520: - // clang on aarch64 sinks the snapshot load past the store-through-newval - // when Old's TBAA tag differs from New's, corrupting the strong-EG - // restoration on catch. A byte-buffer snapshot via std::memcpy is opaque - // to TBAA, and preserving *oldval in place across the try (no destroy_at) - // makes the catch a plain byte restore -- which for trivially-copyable - // (and therefore trivially-destructible) Old is observationally identical - // to the destroy-and-recreate branch below. - // (Old is also trivially-destructible -- implied by is_trivially_copyable_v - // per [class.prop]/1 -- so skipping destroy_at is observationally identical - // to the destroy-and-recreate branch below.) - if (not ::std::is_constant_evaluated()) { - alignas(Old) unsigned char _bytes[sizeof(Old)]; - ::std::memcpy(_bytes, ::std::addressof(oldval), sizeof(Old)); - try { - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); - } catch (...) { - ::std::memcpy(::std::addressof(oldval), _bytes, sizeof(Old)); - throw; - } - } else { - // LCOV_EXCL_START constant-evaluated only; runtime branches are above and below - Old tmp(std::move(oldval)); - ::std::destroy_at(::std::addressof(oldval)); - try { - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); - } catch (...) { - ::std::construct_at(::std::addressof(oldval), std::move(tmp)); - throw; - } - // LCOV_EXCL_STOP - } - } else { - Old tmp(std::move(oldval)); - ::std::destroy_at(::std::addressof(oldval)); - try { - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); - } catch (...) { - ::std::construct_at(::std::addressof(oldval), std::move(tmp)); - throw; - } - } + return ::std::move(storage_.v_); } - // Body helper for the copy/move assignment operators; the public expected - // operator= overloads keep their constraints/noexcept clauses and just - // delegate here, forwarding `s` as lvalue or rvalue. For T=void the value - // side is `_dummy_t` whose destructor and default ctor are trivial; the - // cross-state transitions use direct construct_at, matching the - // [expected.void.assign] and [expected.object.assign] specification (no strong exception guarantee). + // Assignment body shared by the public expected operator= overloads, + // which keep their constraints/noexcept clauses and forward `s` here as + // lvalue or rvalue. For T=void all `storage_.v_` operations are no-ops + // at runtime (trivial `_dummy_t`) but still track the active union member. constexpr void _assign(auto &&s) { if (set_ && s.set_) { - v_ = FWD(s).v_; + storage_.v_ = FWD(s).storage_.v_; } else if (set_) { - _reinit(e_, v_, FWD(s).e_); + _storage_t::_reinit(storage_.e_, storage_.v_, FWD(s).storage_.e_); set_ = false; } else if (s.set_) { - _reinit(v_, e_, FWD(s).v_); + _storage_t::_reinit(storage_.v_, storage_.e_, FWD(s).storage_.v_); set_ = true; } else { - e_ = FWD(s).e_; + storage_.e_ = FWD(s).storage_.e_; } } template constexpr void _assign_value(U &&s) { if (set_) { - v_ = FWD(s); + storage_.v_ = FWD(s); } else { - _reinit(v_, e_, FWD(s)); + _storage_t::_reinit(storage_.v_, storage_.e_, FWD(s)); set_ = true; } } constexpr void _assign_unexpected(auto &&s) { if (not set_) { - e_ = FWD(s).error(); + storage_.e_ = FWD(s).error(); } else { - _reinit(e_, v_, FWD(s).error()); + _storage_t::_reinit(storage_.e_, storage_.v_, FWD(s).error()); set_ = false; } } - // [expected.object.swap] cross-state swap helper; lhs holds value, rhs holds error - static constexpr void _swap(_storage &lhs, _storage &rhs) // - noexcept(::std::is_nothrow_move_constructible_v<_storage_t> && ::std::is_nothrow_move_constructible_v) + // Cross-state swap; lhs holds value, rhs holds error. + static constexpr void _swap_helper(_storage &lhs, _storage &rhs) // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) { if constexpr (::std::is_nothrow_move_constructible_v) { - E tmp(::std::move(rhs.e_)); - ::std::destroy_at(::std::addressof(rhs.e_)); + E tmp(::std::move(rhs.storage_.e_)); + ::std::destroy_at(::std::addressof(rhs.storage_.e_)); try { - ::std::construct_at(::std::addressof(rhs.v_), ::std::move(lhs.v_)); - ::std::destroy_at(::std::addressof(lhs.v_)); - ::std::construct_at(::std::addressof(lhs.e_), ::std::move(tmp)); + ::std::construct_at(::std::addressof(rhs.storage_.v_), ::std::move(lhs.storage_.v_)); + ::std::destroy_at(::std::addressof(lhs.storage_.v_)); + ::std::construct_at(::std::addressof(lhs.storage_.e_), ::std::move(tmp)); } catch (...) { - ::std::construct_at(::std::addressof(rhs.e_), ::std::move(tmp)); + ::std::construct_at(::std::addressof(rhs.storage_.e_), ::std::move(tmp)); throw; } } else { - _storage_t tmp(::std::move(lhs.v_)); - ::std::destroy_at(::std::addressof(lhs.v_)); + auto tmp(::std::move(lhs.storage_.v_)); + ::std::destroy_at(::std::addressof(lhs.storage_.v_)); try { - ::std::construct_at(::std::addressof(lhs.e_), ::std::move(rhs.e_)); - ::std::destroy_at(::std::addressof(rhs.e_)); - ::std::construct_at(::std::addressof(rhs.v_), ::std::move(tmp)); + ::std::construct_at(::std::addressof(lhs.storage_.e_), ::std::move(rhs.storage_.e_)); + ::std::destroy_at(::std::addressof(rhs.storage_.e_)); + ::std::construct_at(::std::addressof(rhs.storage_.v_), ::std::move(tmp)); } catch (...) { - ::std::construct_at(::std::addressof(lhs.v_), ::std::move(tmp)); + ::std::construct_at(::std::addressof(lhs.storage_.v_), ::std::move(tmp)); throw; } } @@ -587,76 +667,69 @@ template struct _storage { rhs.set_ = true; } - // [expected.object.swap] body helper; the public expected::swap keeps its - // constraints/noexcept clause and just delegates here. constexpr void _swap_with(_storage &rhs) { if (set_ == rhs.set_) { if (set_) { if constexpr (not ::std::is_void_v) { using ::std::swap; - swap(v_, rhs.v_); + swap(storage_.v_, rhs.storage_.v_); } } else { using ::std::swap; - swap(e_, rhs.e_); + swap(storage_.e_, rhs.storage_.e_); } } else if constexpr (::std::is_void_v) { - // Direct cross-state transition: only one E move, matching the - // [expected.void.swap] specification. + // [expected.void.swap] mandates a single E move on cross-state. if (set_) { - ::std::destroy_at(::std::addressof(v_)); - ::std::construct_at(::std::addressof(e_), ::std::move(rhs.e_)); - ::std::destroy_at(::std::addressof(rhs.e_)); - ::std::construct_at(::std::addressof(rhs.v_)); + ::std::destroy_at(::std::addressof(storage_.v_)); + ::std::construct_at(::std::addressof(storage_.e_), ::std::move(rhs.storage_.e_)); + ::std::destroy_at(::std::addressof(rhs.storage_.e_)); + ::std::construct_at(::std::addressof(rhs.storage_.v_)); set_ = false; rhs.set_ = true; } else { rhs._swap_with(*this); } + } else if (set_) { + _swap_helper(*this, rhs); } else { - if (set_) { - _swap(*this, rhs); - } else { - _swap(rhs, *this); - } + _swap_helper(rhs, *this); } } - // [expected.object.emplace], emplace template - constexpr _storage_t &emplace(Args &&...args) noexcept + constexpr _value_t &emplace(Args &&...args) noexcept requires(not ::std::is_void_v && ::std::is_nothrow_constructible_v) { if (set_) { - ::std::destroy_at(::std::addressof(v_)); + ::std::destroy_at(::std::addressof(storage_.v_)); } else { - ::std::destroy_at(::std::addressof(e_)); + ::std::destroy_at(::std::addressof(storage_.e_)); set_ = true; } - return *::std::construct_at(::std::addressof(v_), std::forward(args)...); + return *::std::construct_at(::std::addressof(storage_.v_), std::forward(args)...); } template - constexpr _storage_t &emplace(::std::initializer_list il, Args &&...args) noexcept + constexpr _value_t &emplace(::std::initializer_list il, Args &&...args) noexcept requires(not ::std::is_void_v && ::std::is_nothrow_constructible_v &, Args...>) { if (set_) { - ::std::destroy_at(::std::addressof(v_)); + ::std::destroy_at(::std::addressof(storage_.v_)); } else { - ::std::destroy_at(::std::addressof(e_)); + ::std::destroy_at(::std::addressof(storage_.e_)); set_ = true; } - return *::std::construct_at(::std::addressof(v_), il, std::forward(args)...); + return *::std::construct_at(::std::addressof(storage_.v_), il, std::forward(args)...); } - // [expected.void.emplace], emplace constexpr void emplace() noexcept requires(::std::is_void_v) { if (not set_) { - ::std::destroy_at(::std::addressof(e_)); - ::std::construct_at(::std::addressof(v_)); + ::std::destroy_at(::std::addressof(storage_.e_)); + ::std::construct_at(::std::addressof(storage_.v_)); set_ = true; } } @@ -700,9 +773,8 @@ template class expected : private detail::_storage { // through the dependent base, and to document the inherited surface. using _base = detail::_storage; using _base::_value; - using _base::e_; using _base::set_; - using _base::v_; + using _base::storage_; template using _can_convert = ::std::bool_constant< // @@ -827,12 +899,12 @@ template class expected : private detail::_storage { noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_copy_constructible_v) // extension requires(::std::is_copy_constructible_v && ::std::is_copy_constructible_v && (not ::std::is_trivially_copy_constructible_v || not ::std::is_trivially_copy_constructible_v)) - : _base(detail::_branch, s.set_) + : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(v_), s.v_); + ::std::construct_at(::std::addressof(storage_.v_), s.storage_.v_); else - ::std::construct_at(::std::addressof(e_), s.e_); + ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } constexpr expected(expected &&s) @@ -843,12 +915,12 @@ template class expected : private detail::_storage { noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && ::std::is_move_constructible_v && (not ::std::is_trivially_move_constructible_v || not ::std::is_trivially_move_constructible_v)) - : _base(detail::_branch, s.set_) + : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(v_), ::std::move(s.v_)); + ::std::construct_at(::std::addressof(storage_.v_), ::std::move(s.storage_.v_)); else - ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template @@ -857,12 +929,12 @@ template class expected : private detail::_storage { noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) // extension requires(_can_copy_convert::value) - : _base(detail::_branch, s.set_) + : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(v_), s.v_); + ::std::construct_at(::std::addressof(storage_.v_), s.storage_.v_); else - ::std::construct_at(::std::addressof(e_), s.e_); + ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } template @@ -870,12 +942,12 @@ template class expected : private detail::_storage { expected(expected &&s) // noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) // extension requires(_can_move_convert::value) - : _base(detail::_branch, s.set_) + : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(v_), ::std::move(s.v_)); + ::std::construct_at(::std::addressof(storage_.v_), ::std::move(s.storage_.v_)); else - ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template > @@ -1203,11 +1275,12 @@ class expected : private detail::_storage { template friend class expected; // Bring the storage members into scope so unqualified lookup resolves them - // through the dependent base, and to document the inherited surface. + // through the dependent base, and to document the inherited surface. For + // T=void the storage union has only `e_`; the value state has no active + // member. using _base = detail::_storage; - using _base::e_; using _base::set_; - using _base::v_; + using _base::storage_; template static constexpr auto _and_then(Self &&self, Fn &&fn) // @@ -1291,19 +1364,15 @@ class expected : private detail::_storage { constexpr expected(expected const &) // requires(::std::is_copy_constructible_v && ::std::is_trivially_copy_constructible_v) // = default; - // Delegate to the in_place ctor of `_storage`, which default-initialises the - // value arm (`_dummy_t`) and sets `set_` to true; the body then switches to - // the error arm when the source holds an error. constexpr expected(expected const &s) // noexcept(::std::is_nothrow_copy_constructible_v) // extension requires(::std::is_copy_constructible_v && not ::std::is_trivially_copy_constructible_v) // - : _base(::std::in_place) + : _base(s.set_) { - if (not s.set_) { - ::std::destroy_at(::std::addressof(v_)); - ::std::construct_at(::std::addressof(e_), s.e_); - set_ = false; - } + if (set_) + ::std::construct_at(::std::addressof(storage_.v_)); + else + ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } constexpr expected(expected &&s) // @@ -1312,38 +1381,35 @@ class expected : private detail::_storage { constexpr expected(expected &&s) // noexcept(::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && not ::std::is_trivially_move_constructible_v) - : _base(::std::in_place) + : _base(s.set_) { - if (not s.set_) { - ::std::destroy_at(::std::addressof(v_)); - ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); - set_ = false; - } + if (set_) + ::std::construct_at(::std::addressof(storage_.v_)); + else + ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template constexpr explicit(not ::std::is_convertible_v) expected(expected const &s) // noexcept(::std::is_nothrow_constructible_v) // extension requires(_can_copy_convert::value) - : _base(::std::in_place) + : _base(s.set_) { - if (not s.set_) { - ::std::destroy_at(::std::addressof(v_)); - ::std::construct_at(::std::addressof(e_), s.e_); - set_ = false; - } + if (set_) + ::std::construct_at(::std::addressof(storage_.v_)); + else + ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } template constexpr explicit(not ::std::is_convertible_v) expected(expected &&s) // noexcept(::std::is_nothrow_constructible_v) // extension requires(_can_move_convert::value) - : _base(::std::in_place) + : _base(s.set_) { - if (not s.set_) { - ::std::destroy_at(::std::addressof(v_)); - ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); - set_ = false; - } + if (set_) + ::std::construct_at(::std::addressof(storage_.v_)); + else + ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template From 11d197e47dbda14b05578ad09e48a82c9d2ebaef Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 14 May 2026 14:46:00 +0200 Subject: [PATCH 03/21] MSVC workaround --- include/pfn/expected.hpp | 62 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index a84c8853..455ec0fe 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -228,53 +228,53 @@ template union _storage_union_t { // Implements the reinit-expected helper from [expected.object.assign]. template - static constexpr void _reinit(New &newval, Old &oldval, Args &&...args) // + static constexpr void _reinit(New *newp, Old *oldp, Args &&...args) // noexcept(::std::is_nothrow_constructible_v) { if constexpr (::std::is_nothrow_constructible_v) { - ::std::destroy_at(::std::addressof(oldval)); - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + ::std::destroy_at(oldp); + ::std::construct_at(newp, ::std::forward(args)...); } else if constexpr (::std::is_nothrow_move_constructible_v) { New tmp(::std::forward(args)...); - ::std::destroy_at(::std::addressof(oldval)); - ::std::construct_at(::std::addressof(newval), std::move(tmp)); + ::std::destroy_at(oldp); + ::std::construct_at(newp, std::move(tmp)); } else if constexpr (::std::is_trivially_copyable_v) { // Workaround for https://github.com/llvm/llvm-project/issues/196520: - // clang on aarch64 sinks the snapshot load past the store-through-newval + // clang on aarch64 sinks the snapshot load past the store-through-newp // when Old's TBAA tag differs from New's, corrupting the strong-EG // restoration on catch. A byte-buffer snapshot via std::memcpy is opaque - // to TBAA, and preserving *oldval in place across the try (no destroy_at) + // to TBAA, and preserving *oldp in place across the try (no destroy_at) // makes the catch a plain byte restore -- which for trivially-copyable // (and therefore trivially-destructible per [class.prop]/1). Old is // observationally identical to the destroy-and-recreate branch below. if (not ::std::is_constant_evaluated()) { alignas(Old) unsigned char _bytes[sizeof(Old)]; - ::std::memcpy(_bytes, ::std::addressof(oldval), sizeof(Old)); + ::std::memcpy(_bytes, oldp, sizeof(Old)); try { - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + ::std::construct_at(newp, ::std::forward(args)...); } catch (...) { - ::std::memcpy(::std::addressof(oldval), _bytes, sizeof(Old)); + ::std::memcpy(oldp, _bytes, sizeof(Old)); throw; } } else { // LCOV_EXCL_START constant-evaluated only; runtime branches are above and below - Old tmp(std::move(oldval)); - ::std::destroy_at(::std::addressof(oldval)); + Old tmp(std::move(*oldp)); + ::std::destroy_at(oldp); try { - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + ::std::construct_at(newp, ::std::forward(args)...); } catch (...) { - ::std::construct_at(::std::addressof(oldval), std::move(tmp)); + ::std::construct_at(oldp, std::move(tmp)); throw; } // LCOV_EXCL_STOP } } else { - Old tmp(std::move(oldval)); - ::std::destroy_at(::std::addressof(oldval)); + Old tmp(std::move(*oldp)); + ::std::destroy_at(oldp); try { - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + ::std::construct_at(newp, ::std::forward(args)...); } catch (...) { - ::std::construct_at(::std::addressof(oldval), std::move(tmp)); + ::std::construct_at(oldp, std::move(tmp)); throw; } } @@ -322,23 +322,23 @@ template union _storage_union_t { // [expected.void.assign] mandates direct construction (no temporary). template - static constexpr void _reinit(New &newval, Old &oldval, Args &&...args) // + static constexpr void _reinit(New *newp, Old *oldp, Args &&...args) // noexcept(::std::is_nothrow_constructible_v) { if constexpr (::std::is_nothrow_constructible_v) { - ::std::destroy_at(::std::addressof(oldval)); - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + ::std::destroy_at(oldp); + ::std::construct_at(newp, ::std::forward(args)...); } else { - // On exception the trivial `_dummy_t` oldval is reconstructed so the + // On exception the trivial `_dummy_t` *oldp is reconstructed so the // union always has an active member (required for constant evaluation). // If New is not nothrow-constructible then it's not _dummy_t, hence - // oldval must be _dummy_t. - static_assert(::std::is_same_v, _dummy_t>); - ::std::destroy_at(::std::addressof(oldval)); + // *oldp must be _dummy_t. + static_assert(::std::is_same_v, _dummy_t>); + ::std::destroy_at(oldp); try { - ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + ::std::construct_at(newp, ::std::forward(args)...); } catch (...) { - ::std::construct_at(::std::addressof(oldval)); + ::std::construct_at(oldp); throw; } } @@ -608,10 +608,10 @@ template struct _storage { if (set_ && s.set_) { storage_.v_ = FWD(s).storage_.v_; } else if (set_) { - _storage_t::_reinit(storage_.e_, storage_.v_, FWD(s).storage_.e_); + _storage_t::_reinit(::std::addressof(storage_.e_), ::std::addressof(storage_.v_), FWD(s).storage_.e_); set_ = false; } else if (s.set_) { - _storage_t::_reinit(storage_.v_, storage_.e_, FWD(s).storage_.v_); + _storage_t::_reinit(::std::addressof(storage_.v_), ::std::addressof(storage_.e_), FWD(s).storage_.v_); set_ = true; } else { storage_.e_ = FWD(s).storage_.e_; @@ -622,7 +622,7 @@ template struct _storage { if (set_) { storage_.v_ = FWD(s); } else { - _storage_t::_reinit(storage_.v_, storage_.e_, FWD(s)); + _storage_t::_reinit(::std::addressof(storage_.v_), ::std::addressof(storage_.e_), FWD(s)); set_ = true; } } @@ -631,7 +631,7 @@ template struct _storage { if (not set_) { storage_.e_ = FWD(s).error(); } else { - _storage_t::_reinit(storage_.e_, storage_.v_, FWD(s).error()); + _storage_t::_reinit(::std::addressof(storage_.e_), ::std::addressof(storage_.v_), FWD(s).error()); set_ = false; } } From e045baf489b972e73729de617179bd4b86712848 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 14 May 2026 14:49:00 +0200 Subject: [PATCH 04/21] Add LCOV_EXCL for construct_at of a trivial type --- include/pfn/expected.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 455ec0fe..7eb7c893 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -1370,7 +1370,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL else ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } @@ -1384,7 +1384,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL else ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } @@ -1396,7 +1396,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL else ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } @@ -1407,7 +1407,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL else ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } From fec147b5f08830a2187d483421bd526134512eb3 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 14 May 2026 15:00:41 +0200 Subject: [PATCH 05/21] Another MSVC workaround --- include/pfn/expected.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 7eb7c893..38fe2857 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -325,15 +325,15 @@ template union _storage_union_t { static constexpr void _reinit(New *newp, Old *oldp, Args &&...args) // noexcept(::std::is_nothrow_constructible_v) { - if constexpr (::std::is_nothrow_constructible_v) { + if constexpr (::std::is_same_v) { + ::std::destroy_at(oldp); + ::std::construct_at(newp); + } else if constexpr (::std::is_nothrow_constructible_v) { ::std::destroy_at(oldp); ::std::construct_at(newp, ::std::forward(args)...); } else { // On exception the trivial `_dummy_t` *oldp is reconstructed so the // union always has an active member (required for constant evaluation). - // If New is not nothrow-constructible then it's not _dummy_t, hence - // *oldp must be _dummy_t. - static_assert(::std::is_same_v, _dummy_t>); ::std::destroy_at(oldp); try { ::std::construct_at(newp, ::std::forward(args)...); From 25cbace1eaf87f79598d314d4c2c02553bdba94c Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 14 May 2026 15:39:13 +0200 Subject: [PATCH 06/21] Address review comment, fix coverage excludes --- include/pfn/expected.hpp | 17 ++++++++++++----- tests/pfn/expected.cpp | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 38fe2857..77018a80 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -681,9 +681,16 @@ template struct _storage { } } else if constexpr (::std::is_void_v) { // [expected.void.swap] mandates a single E move on cross-state. + // On exception from E's move, restore the `_dummy_t` value so the union + // always has an active member (required for constant evaluation). if (set_) { ::std::destroy_at(::std::addressof(storage_.v_)); - ::std::construct_at(::std::addressof(storage_.e_), ::std::move(rhs.storage_.e_)); + try { + ::std::construct_at(::std::addressof(storage_.e_), ::std::move(rhs.storage_.e_)); + } catch (...) { + ::std::construct_at(::std::addressof(storage_.v_)); + throw; + } ::std::destroy_at(::std::addressof(rhs.storage_.e_)); ::std::construct_at(::std::addressof(rhs.storage_.v_)); set_ = false; @@ -1370,7 +1377,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible else ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } @@ -1384,7 +1391,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible else ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } @@ -1396,7 +1403,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible else ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } @@ -1407,7 +1414,7 @@ class expected : private detail::_storage { : _base(s.set_) { if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible else ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } diff --git a/tests/pfn/expected.cpp b/tests/pfn/expected.cpp index 1a59ac9b..9c801078 100644 --- a/tests/pfn/expected.cpp +++ b/tests/pfn/expected.cpp @@ -2672,6 +2672,11 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK(std::as_const(a).value().v == 11); CHECK(std::move(std::as_const(a)).value().v == 11); CHECK(std::move(a).value().v == 11); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); } { @@ -4858,6 +4863,19 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a; static_assert(std::is_same_v); + { + T a; + a.value(); + std::as_const(a).value(); + std::move(std::as_const(a)).value(); + std::move(a).value(); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + SECTION("operators") { static_assert(std::is_same_v); From c400427b764994bb0b0287f27a74c95865b1baa1 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 14 May 2026 17:09:09 +0200 Subject: [PATCH 07/21] Fix exception safety of copy/move constructors --- include/pfn/expected.hpp | 66 ++++++++++++---------------------------- 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 77018a80..f573e316 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -372,9 +372,16 @@ template struct _storage { { } - // Leaves `storage_` with no active union member so the derived ctor - // body can placement-new the appropriate arm. - constexpr explicit _storage(bool s) noexcept : storage_(), set_(s) {} + constexpr explicit _storage(bool s, auto &&src) : storage_(), set_(s) + { + if (set_) { + if constexpr (::std::is_void_v) + ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE trivially constructible + else + ::std::construct_at(::std::addressof(storage_.v_), FWD(src).v_); + } else + ::std::construct_at(::std::addressof(storage_.e_), FWD(src).e_); + } constexpr _storage(_storage const &) = default; constexpr _storage(_storage &&) = default; @@ -894,7 +901,7 @@ template class expected : private detail::_storage { requires ::std::is_default_constructible_v : _base(::std::in_place) { - } + } // LCOV_EXCL_LINE constexpr expected(expected const &) = delete; constexpr expected(expected const &s) // @@ -906,14 +913,9 @@ template class expected : private detail::_storage { noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_copy_constructible_v) // extension requires(::std::is_copy_constructible_v && ::std::is_copy_constructible_v && (not ::std::is_trivially_copy_constructible_v || not ::std::is_trivially_copy_constructible_v)) - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_), s.storage_.v_); - else - ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } - constexpr expected(expected &&s) requires(::std::is_move_constructible_v && ::std::is_move_constructible_v && ::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v) @@ -922,12 +924,8 @@ template class expected : private detail::_storage { noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && ::std::is_move_constructible_v && (not ::std::is_trivially_move_constructible_v || not ::std::is_trivially_move_constructible_v)) - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_), ::std::move(s.storage_.v_)); - else - ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template @@ -936,25 +934,16 @@ template class expected : private detail::_storage { noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) // extension requires(_can_copy_convert::value) - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_), s.storage_.v_); - else - ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } - template constexpr explicit(not ::std::is_convertible_v || not ::std::is_convertible_v) expected(expected &&s) // noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) // extension requires(_can_move_convert::value) - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_), ::std::move(s.storage_.v_)); - else - ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template > @@ -1374,49 +1363,32 @@ class expected : private detail::_storage { constexpr expected(expected const &s) // noexcept(::std::is_nothrow_copy_constructible_v) // extension requires(::std::is_copy_constructible_v && not ::std::is_trivially_copy_constructible_v) // - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible - else - ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } - constexpr expected(expected &&s) // requires(::std::is_move_constructible_v && ::std::is_trivially_move_constructible_v) // = default; constexpr expected(expected &&s) // noexcept(::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && not ::std::is_trivially_move_constructible_v) - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible - else - ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template constexpr explicit(not ::std::is_convertible_v) expected(expected const &s) // noexcept(::std::is_nothrow_constructible_v) // extension requires(_can_copy_convert::value) - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible - else - ::std::construct_at(::std::addressof(storage_.e_), s.storage_.e_); } template constexpr explicit(not ::std::is_convertible_v) expected(expected &&s) // noexcept(::std::is_nothrow_constructible_v) // extension requires(_can_move_convert::value) - : _base(s.set_) + : _base(s.set_, FWD(s).storage_) { - if (set_) - ::std::construct_at(::std::addressof(storage_.v_)); // LCOV_EXCL_LINE v_ is trivially constructible - else - ::std::construct_at(::std::addressof(storage_.e_), ::std::move(s.storage_.e_)); } template From 73b85894867339a7de8a211737cd778f9f0b71fc Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 15 May 2026 17:16:51 +0200 Subject: [PATCH 08/21] Final cleanup --- include/pfn/expected.hpp | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index f573e316..317e87dd 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -170,11 +170,7 @@ template class unexpected { template unexpected(E) -> unexpected; -template class expected; namespace detail { -template constexpr bool _is_some_expected = false; -template constexpr bool _is_some_expected<::pfn::expected> = true; - template constexpr bool _is_valid_expected = // not ::std::is_reference_v // @@ -751,13 +747,17 @@ template struct _storage { } // namespace detail -// declare void specialization -template - requires ::std::is_void_v -class expected; +template class expected; +template class expected; + +namespace detail { +template constexpr bool _is_some_expected = false; +template constexpr bool _is_some_expected<::pfn::expected> = true; +} // namespace detail template class expected : private detail::_storage { static_assert(detail::_is_valid_expected); + using _base = detail::_storage; template using _can_convert_detail = ::std::bool_constant< // @@ -783,13 +783,6 @@ template class expected : private detail::_storage { template using _can_move_convert = _can_convert_detail; template friend class expected; - // Bring the storage members into scope so unqualified lookup resolves them - // through the dependent base, and to document the inherited surface. - using _base = detail::_storage; - using _base::_value; - using _base::set_; - using _base::storage_; - template using _can_convert = ::std::bool_constant< // not ::std::is_same_v<::std::remove_cvref_t, ::std::in_place_t> // @@ -1253,10 +1246,9 @@ requires { } }; -template - requires ::std::is_void_v -class expected : private detail::_storage { +template class expected : private detail::_storage { static_assert(detail::_is_valid_unexpected); + using _base = detail::_storage; template using _can_convert_detail = ::std::bool_constant< // @@ -1270,14 +1262,6 @@ class expected : private detail::_storage { template using _can_move_convert = _can_convert_detail; template friend class expected; - // Bring the storage members into scope so unqualified lookup resolves them - // through the dependent base, and to document the inherited surface. For - // T=void the storage union has only `e_`; the value state has no active - // member. - using _base = detail::_storage; - using _base::set_; - using _base::storage_; - template static constexpr auto _and_then(Self &&self, Fn &&fn) // noexcept(::std::is_nothrow_invocable_v && ::std::is_nothrow_constructible_v) @@ -1340,7 +1324,7 @@ class expected : private detail::_storage { using error_t = ::std::remove_cv_t<::std::invoke_result_t>; static_assert(detail::_is_valid_unexpected); static_assert(::std::is_constructible_v>); - using result_t = expected; + using result_t = expected; if (not self.has_value()) { return result_t(unexpect, ::std::invoke(FWD(fn), FWD(self).error())); } @@ -1348,7 +1332,7 @@ class expected : private detail::_storage { } public: - using value_type = T; + using value_type = void; using error_type = E; using unexpected_type = unexpected; From cc7b3a216033e41e1add9b7766e0984031ef8bb8 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 15 May 2026 17:17:14 +0200 Subject: [PATCH 09/21] Experimental removal of include --- examples/polygon/polygon.hpp | 14 +- examples/simple/main.cpp | 28 +- include/CMakeLists.txt | 3 +- include/fn/concepts.hpp | 2 +- include/fn/expected.hpp | 145 +++++----- include/fn/fail.hpp | 8 +- include/fn/filter.hpp | 4 +- tests/fn/and_then.cpp | 34 +-- tests/fn/discard.cpp | 12 +- tests/fn/expected.cpp | 498 +++++++++++++++++------------------ tests/fn/fail.cpp | 16 +- tests/fn/filter.cpp | 20 +- tests/fn/inspect.cpp | 18 +- tests/fn/inspect_error.cpp | 18 +- tests/fn/or_else.cpp | 74 +++--- tests/fn/recover.cpp | 22 +- tests/fn/transform.cpp | 22 +- tests/fn/transform_error.cpp | 28 +- tests/fn/value_or.cpp | 8 +- 19 files changed, 487 insertions(+), 487 deletions(-) diff --git a/examples/polygon/polygon.hpp b/examples/polygon/polygon.hpp index 41483f2d..83c7fd22 100644 --- a/examples/polygon/polygon.hpp +++ b/examples/polygon/polygon.hpp @@ -66,19 +66,19 @@ struct parameters { std::string_view const program_name = (args.size() >= 1 && !args[0].empty()) // ? args[0] : std::string_view{""}; - return std::unexpected(too_few_parameters{program_name}); + return ::pfn::unexpected(too_few_parameters{program_name}); } parameters params; params.characters = args[1]; if (params.characters.size() < 3) { - return std::unexpected(too_few_characters{}); + return ::pfn::unexpected(too_few_characters{}); } params.required = static_cast(params.characters[0]); for (char c : params.characters) { if (static_cast(c) > 0x7F) { - return std::unexpected(non_ascii_characters{}); + return ::pfn::unexpected(non_ascii_characters{}); } } @@ -185,12 +185,12 @@ struct inputs { ec == std::errc::no_such_file_or_directory // || ec == std::errc::not_a_directory // || type == std::filesystem::file_type::not_found) { - return std::unexpected(file_not_found{{.path = std::move(path), .ec = ec}}); + return ::pfn::unexpected(file_not_found{{.path = std::move(path), .ec = ec}}); } if (ec == std::errc::permission_denied) { - return std::unexpected(permission_denied{{.path = std::move(path), .ec = ec}}); + return ::pfn::unexpected(permission_denied{{.path = std::move(path), .ec = ec}}); } - return std::unexpected( + return ::pfn::unexpected( io_error{{.path = std::move(path), .ec = (ec ? ec : std::make_error_code(std::io_errc::stream))}}); }; @@ -254,7 +254,7 @@ constexpr inline struct algorithm_t { if (source.in->eof() && !source.in->bad()) { continue; } - return std::unexpected(read_error{{.path = source.path, .ec = std::make_error_code(std::io_errc::stream)}}); + return ::pfn::unexpected(read_error{{.path = source.path, .ec = std::make_error_code(std::io_errc::stream)}}); } return 0; diff --git a/examples/simple/main.cpp b/examples/simple/main.cpp index 1b3806bc..57554b40 100644 --- a/examples/simple/main.cpp +++ b/examples/simple/main.cpp @@ -83,7 +83,7 @@ TEST_CASE("Minimal expected", "[expected][and_then]") } { // example-expected-and_then-error - fn::expected ex = std::unexpected{"Not good"}; + fn::expected ex = ::pfn::unexpected{"Not good"}; auto oops = ex | fn::and_then([](auto&& v) -> fn::expected { @@ -139,14 +139,14 @@ TEST_CASE("Demo expected", "[expected][pack][and_then][discard][transform_error] if (std::from_chars(str.begin(), end, tmp).ptr == end) { return {tmp}; } - return std::unexpected{"Failed to parse " + std::string(str)}; + return ::pfn::unexpected{"Failed to parse " + std::string(str)}; }; // Immovable operations must be captured as lvalues, and functor will store // reference to them rather than make a copy constexpr auto fn1 = [j = ImmovableValue{-1}](int i) noexcept -> fn::expected { if (i < j.value) { - return std::unexpected{"Too small"}; + return ::pfn::unexpected{"Too small"}; } return {i + 0.5}; }; @@ -199,12 +199,12 @@ TEST_CASE("Demo expected", "[expected][pack][and_then][discard][transform_error] if (std::from_chars(str.begin(), end, tmp).ptr == end) { return {tmp}; } - return std::unexpected{"Failed to parse " + std::string(str)}; + return ::pfn::unexpected{"Failed to parse " + std::string(str)}; }; constexpr auto parse_twelve = [](std::string str) noexcept -> fn::expected { if (str != "12") - return std::unexpected{"Not 12"}; + return ::pfn::unexpected{"Not 12"}; return {12.}; }; @@ -235,7 +235,7 @@ TEST_CASE("Demo expected", "[expected][pack][and_then][discard][transform_error] | fn::inspect_error([](Error) noexcept { CHECK(false); }) // | fn::discard(); - fn::expected{std::unexpected{"discarded"}} // + fn::expected{::pfn::unexpected{"discarded"}} // | fn::inspect([](int) noexcept { CHECK(false); }) // | fn::inspect_error([](Error e) noexcept { REQUIRE(e.what == "discarded"); }) // | fn::discard(); @@ -506,7 +506,7 @@ TEST_CASE("Demo choice and graded monad", "[choice][and_then][inspect][transform if constexpr (std::is_same_v, T>) { return {FWD(v)}; } else - return std::unexpected{InvalidType}; + return ::pfn::unexpected{InvalidType}; }); }; @@ -523,7 +523,7 @@ TEST_CASE("Demo choice and graded monad", "[choice][and_then][inspect][transform else if (configuration == "test") return type{std::in_place, fn::sum{fn::pack{std::type_identity{}, std::move(test_name)}}}; else - return type{std::unexpect, InvalidConfiguration}; + return type{::pfn::unexpect, InvalidConfiguration}; })(configuration, test_name) // & convert(std::in_place_type, hostname) // & convert(std::in_place_type, port) // @@ -550,24 +550,24 @@ TEST_CASE("Demo choice and graded monad", "[choice][and_then][inspect][transform || config.hostname.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789.") != std::string_view::npos || config.hostname.find("..") != std::string_view::npos) - return std::unexpected(InvalidHostname); + return ::pfn::unexpected(InvalidHostname); else if (config.port < 1 || config.port > 0xffff) - return std::unexpected(InvalidPort); + return ::pfn::unexpected(InvalidPort); else if (config.filename.size() < 1 || config.filename.size() > 254) - return std::unexpected(InvalidFilename); + return ::pfn::unexpected(InvalidFilename); else if (config.threshold < 0 || config.threshold > 1) - return std::unexpected(InvalidThreshold); + return ::pfn::unexpected(InvalidThreshold); if constexpr (std::is_same_v, ConfigTest>) { if (config.test_name != "foo") - return std::unexpected(InvalidTest); + return ::pfn::unexpected(InvalidTest); } return FWD(config); }) | fn::and_then([](auto const &config) -> fn::expected { if (config.port < 1024) - return std::unexpected(ConnectError); + return ::pfn::unexpected(ConnectError); if constexpr (std::is_same_v) return {0x50eda7a}; // dummy result else diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 1a0571d6..b1cea557 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -93,8 +93,7 @@ target_sources(include_fn INTERFACE FILES ${INCLUDE_FN_HEADERS}) append_compilation_options(include_fn INTERFACE) target_include_directories(include_fn SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -# TODO uncomment when include_pfn is ready -# target_link_libraries(include_fn INTERFACE include_pfn) +target_link_libraries(include_fn INTERFACE include_pfn) install(TARGETS include_fn FILE_SET include_fn_headers diff --git a/include/fn/concepts.hpp b/include/fn/concepts.hpp index 6ca38035..8e1febfb 100644 --- a/include/fn/concepts.hpp +++ b/include/fn/concepts.hpp @@ -76,7 +76,7 @@ concept same_monadic_type_as = same_kind && same_value_kind; */ template concept convertible_to_unexpected - = requires { static_cast>>(std::declval()); }; + = requires { static_cast<::pfn::unexpected>>(std::declval()); }; /** * @brief TODO diff --git a/include/fn/expected.hpp b/include/fn/expected.hpp index f14a2ba3..479e69ad 100644 --- a/include/fn/expected.hpp +++ b/include/fn/expected.hpp @@ -6,11 +6,12 @@ #ifndef INCLUDE_FN_EXPECTED #define INCLUDE_FN_EXPECTED +#include "pfn/expected.hpp" + #include #include #include -#include #include #include @@ -29,13 +30,13 @@ concept some_expected_void = // some_expected // && std::is_same_v::value_type>; -template struct expected final : std::expected { - using value_type = std::expected::value_type; - using error_type = std::expected::error_type; - using unexpected_type = std::unexpected; +template struct expected final : ::pfn::expected { + using value_type = ::pfn::expected::value_type; + using error_type = ::pfn::expected::error_type; + using unexpected_type = ::pfn::unexpected; static_assert(not std::is_same_v>); - using std::expected::expected; + using ::pfn::expected::expected; // convert to graded monad auto sum_error() const & -> expected> @@ -45,7 +46,7 @@ template struct expected final : std::expectedhas_value()) return type{std::in_place, this->value()}; else - return type{std::unexpect, sum(this->error())}; + return type{::pfn::unexpect, sum(this->error())}; } auto sum_error() && -> expected> @@ -55,7 +56,7 @@ template struct expected final : std::expectedhas_value()) return type{std::in_place, std::move(*this).value()}; else - return type{std::unexpect, sum(std::move(*this).error())}; + return type{::pfn::unexpect, sum(std::move(*this).error())}; } auto sum_error() & -> decltype(auto) @@ -89,7 +90,7 @@ template struct expected final : std::expectedhas_value()) return type{std::in_place, sum(this->value())}; else - return type{std::unexpect, this->error()}; + return type{::pfn::unexpect, this->error()}; } auto sum_value() && -> expected, error_type> @@ -99,7 +100,7 @@ template struct expected final : std::expectedhas_value()) return type{std::in_place, sum(std::move(*this).value())}; else - return type{std::unexpect, std::move(*this).error()}; + return type{::pfn::unexpect, std::move(*this).error()}; } auto sum_value() & -> decltype(auto) @@ -138,7 +139,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn), this->value()); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -150,10 +151,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, this->error()); + return new_type(::pfn::unexpect, this->error()); else std::unreachable(); } @@ -171,7 +172,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn), this->value()); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -183,10 +184,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, this->error()); + return new_type(::pfn::unexpect, this->error()); else std::unreachable(); } @@ -204,7 +205,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); else - return type(std::unexpect, std::move(*this).error()); + return type(::pfn::unexpect, std::move(*this).error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -216,10 +217,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, std::move(*this).error()); + return new_type(::pfn::unexpect, std::move(*this).error()); else std::unreachable(); } @@ -237,7 +238,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); else - return type(std::unexpect, std::move(*this).error()); + return type(::pfn::unexpect, std::move(*this).error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -249,10 +250,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, std::move(*this).error()); + return new_type(::pfn::unexpect, std::move(*this).error()); else std::unreachable(); } @@ -271,7 +272,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn)); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -283,10 +284,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, this->error()); + return new_type(::pfn::unexpect, this->error()); else std::unreachable(); } @@ -304,7 +305,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn)); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -316,10 +317,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, this->error()); + return new_type(::pfn::unexpect, this->error()); else std::unreachable(); } @@ -337,7 +338,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn)); else - return type(std::unexpect, std::move(*this).error()); + return type(::pfn::unexpect, std::move(*this).error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -349,10 +350,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, std::move(*this).error()); + return new_type(::pfn::unexpect, std::move(*this).error()); else std::unreachable(); } @@ -370,7 +371,7 @@ template struct expected final : std::expectedhas_value()) return ::fn::detail::_invoke(FWD(fn)); else - return type(std::unexpect, std::move(*this).error()); + return type(::pfn::unexpect, std::move(*this).error()); } else { using new_error_type = sum_for; using new_type = ::fn::expected; @@ -382,10 +383,10 @@ template struct expected final : std::expected>) - return new_type(std::unexpect, std::move(*this).error()); + return new_type(::pfn::unexpect, std::move(*this).error()); else std::unreachable(); } @@ -419,7 +420,7 @@ template struct expected final : std::expected struct expected final : std::expected struct expected final : std::expected struct expected final : std::expected struct expected final : std::expectedvalue())); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } template @@ -547,7 +548,7 @@ template struct expected final : std::expectedvalue())); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } template @@ -563,7 +564,7 @@ template struct expected final : std::expected @@ -579,7 +580,7 @@ template struct expected final : std::expected struct expected final : std::expectedvalue().transform(FWD(fn))); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } template @@ -612,7 +613,7 @@ template struct expected final : std::expectedvalue().transform(FWD(fn))); else - return type(std::unexpect, this->error()); + return type(::pfn::unexpect, this->error()); } template @@ -628,7 +629,7 @@ template struct expected final : std::expected @@ -644,7 +645,7 @@ template struct expected final : std::expected struct expected final : std::expectederror()); + return type(::pfn::unexpect, this->error()); } template @@ -677,7 +678,7 @@ template struct expected final : std::expectederror()); + return type(::pfn::unexpect, this->error()); } template @@ -693,7 +694,7 @@ template struct expected final : std::expected @@ -709,7 +710,7 @@ template struct expected final : std::expected struct expected final : std::expectederror())); + return type(::pfn::unexpect, ::fn::detail::_invoke(FWD(fn), this->error())); } template @@ -740,7 +741,7 @@ template struct expected final : std::expectederror())); + return type(::pfn::unexpect, ::fn::detail::_invoke(FWD(fn), this->error())); } template @@ -755,7 +756,7 @@ template struct expected final : std::expected @@ -770,7 +771,7 @@ template struct expected final : std::expected struct expected final : std::expectederror().transform(FWD(fn))); + return type(::pfn::unexpect, this->error().transform(FWD(fn))); } template @@ -801,7 +802,7 @@ template struct expected final : std::expectederror().transform(FWD(fn))); + return type(::pfn::unexpect, this->error().transform(FWD(fn))); } template @@ -816,7 +817,7 @@ template struct expected final : std::expected @@ -831,7 +832,7 @@ template struct expected final : std::expected if (lh.has_value() && rh.has_value()) return type{std::in_place, FWD(rh).value()}; else if (not lh.has_value()) - return type{std::unexpect, FWD(lh).error()}; + return type{::pfn::unexpect, FWD(lh).error()}; else - return type{std::unexpect, FWD(rh).error()}; + return type{::pfn::unexpect, FWD(rh).error()}; } template @@ -873,12 +874,12 @@ template return type{std::in_place, FWD(rh).value()}; else if (not lh.has_value()) { if constexpr (not std::is_same_v::error_type, sum<>>) - return type{std::unexpect, new_error_type{FWD(lh).error()}}; + return type{::pfn::unexpect, new_error_type{FWD(lh).error()}}; else std::unreachable(); } else { if constexpr (not std::is_same_v::error_type, sum<>>) - return type{std::unexpect, new_error_type{FWD(rh).error()}}; + return type{::pfn::unexpect, new_error_type{FWD(rh).error()}}; else std::unreachable(); } @@ -895,9 +896,9 @@ template if (lh.has_value() && rh.has_value()) return type{std::in_place, FWD(lh).value()}; else if (not lh.has_value()) - return type{std::unexpect, FWD(lh).error()}; + return type{::pfn::unexpect, FWD(lh).error()}; else - return type{std::unexpect, FWD(rh).error()}; + return type{::pfn::unexpect, FWD(rh).error()}; } template @@ -916,12 +917,12 @@ template return type{std::in_place, FWD(lh).value()}; else if (not lh.has_value()) { if constexpr (not std::is_same_v::error_type, sum<>>) - return type{std::unexpect, new_error_type{FWD(lh).error()}}; + return type{::pfn::unexpect, new_error_type{FWD(lh).error()}}; else std::unreachable(); } else { if constexpr (not std::is_same_v::error_type, sum<>>) - return type{std::unexpect, new_error_type{FWD(rh).error()}}; + return type{::pfn::unexpect, new_error_type{FWD(rh).error()}}; else std::unreachable(); } @@ -937,9 +938,9 @@ template if (lh.has_value() && rh.has_value()) return type{std::in_place}; else if (not lh.has_value()) - return type{std::unexpect, FWD(lh).error()}; + return type{::pfn::unexpect, FWD(lh).error()}; else - return type{std::unexpect, FWD(rh).error()}; + return type{::pfn::unexpect, FWD(rh).error()}; } template @@ -957,12 +958,12 @@ template return type{std::in_place}; else if (not lh.has_value()) { if constexpr (not std::is_same_v::error_type, sum<>>) - return type{std::unexpect, new_error_type{FWD(lh).error()}}; + return type{::pfn::unexpect, new_error_type{FWD(lh).error()}}; else std::unreachable(); } else { if constexpr (not std::is_same_v::error_type, sum<>>) - return type{std::unexpect, new_error_type{FWD(rh).error()}}; + return type{::pfn::unexpect, new_error_type{FWD(rh).error()}}; else std::unreachable(); } @@ -982,7 +983,7 @@ template [[nodiscard]] constexpr auto operator&(Lh &&lh, Rh &&rh) noexcept { using error_type = std::remove_cvref_t::error_type; - static constexpr auto efn = [](auto &&v) { return std::unexpected(FWD(v).error()); }; + static constexpr auto efn = [](auto &&v) { return ::pfn::unexpected(FWD(v).error()); }; return ::fn::detail::_join::template type>(FWD(lh), FWD(rh), efn); } @@ -998,7 +999,7 @@ template = sum_for::error_type, typename std::remove_cvref_t::error_type>; static constexpr auto efn = [](auto &&v) { if constexpr (not std::is_same_v::error_type, sum<>>) { - return std::unexpected(FWD(v).error()); + return ::pfn::unexpected(FWD(v).error()); } else { std::unreachable(); } diff --git a/include/fn/fail.hpp b/include/fn/fail.hpp index 60bed02f..8d60c228 100644 --- a/include/fn/fail.hpp +++ b/include/fn/fail.hpp @@ -60,9 +60,9 @@ struct fail_t::apply final { { using type = std::remove_cvref_t; if (v.has_value()) { - return type{std::unexpect, ::fn::invoke(FWD(fn), FWD(v).value())}; + return type{::pfn::unexpect, ::fn::invoke(FWD(fn), FWD(v).value())}; } - return type{std::unexpect, FWD(v).error()}; + return type{::pfn::unexpect, FWD(v).error()}; } /** @@ -78,9 +78,9 @@ struct fail_t::apply final { { using type = std::remove_cvref_t; if (v.has_value()) { - return type{std::unexpect, ::fn::invoke(FWD(fn))}; + return type{::pfn::unexpect, ::fn::invoke(FWD(fn))}; } - return type{std::unexpect, FWD(v).error()}; + return type{::pfn::unexpect, FWD(v).error()}; } /** diff --git a/include/fn/filter.hpp b/include/fn/filter.hpp index c26b4cf0..7720e1fa 100644 --- a/include/fn/filter.hpp +++ b/include/fn/filter.hpp @@ -89,7 +89,7 @@ struct filter_t::apply final { if (std::as_const(v).has_value()) { bool const keep = ::fn::invoke(FWD(pred), std::as_const(v).value()); return (keep ? type{std::in_place, FWD(v).value()} - : type{std::unexpect, ::fn::invoke(FWD(on_err), FWD(v).value())}); + : type{::pfn::unexpect, ::fn::invoke(FWD(on_err), FWD(v).value())}); } return FWD(v); } @@ -110,7 +110,7 @@ struct filter_t::apply final { if (std::as_const(v).has_value()) { bool const keep = ::fn::invoke(FWD(pred)); return (keep ? type{std::in_place} // - : type{std::unexpect, ::fn::invoke(FWD(on_err))}); + : type{::pfn::unexpect, ::fn::invoke(FWD(on_err))}); } return FWD(v); } diff --git a/tests/fn/and_then.cpp b/tests/fn/and_then.cpp index 2f6d1a97..d20fecae 100644 --- a/tests/fn/and_then.cpp +++ b/tests/fn/and_then.cpp @@ -339,7 +339,7 @@ TEST_CASE("and_then", "[and_then][expected][expected_value][pack]") constexpr auto fnValue = [](int i) -> operand_t { return {i + 1}; }; constexpr auto wrong = [](int) -> operand_t { throw 0; }; - constexpr auto fnFail = [](int i) -> operand_t { return std::unexpected("Got " + std::to_string(i)); }; + constexpr auto fnFail = [](int i) -> operand_t { return ::pfn::unexpected("Got " + std::to_string(i)); }; constexpr auto fnXabs = [](int i) -> fn::expected { return {{std::abs(8 - i)}}; }; static_assert(is::invocable_with_any(fnValue)); @@ -387,7 +387,7 @@ TEST_CASE("and_then", "[and_then][expected][expected_value][pack]") WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | and_then(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -411,7 +411,7 @@ TEST_CASE("and_then", "[and_then][expected][expected_value][pack]") WHEN("fail") { constexpr auto fnFail = [](int i, double d) constexpr -> fn::expected { - return std::unexpected("Got " + std::to_string(i) + " and " + std::to_string(d)); + return ::pfn::unexpected("Got " + std::to_string(i) + " and " + std::to_string(d)); }; using T = decltype(a | and_then(fnFail)); static_assert(std::is_same_v>); @@ -422,7 +422,7 @@ TEST_CASE("and_then", "[and_then][expected][expected_value][pack]") WHEN("operand is error") { constexpr auto wrong = [](auto...) -> operand_t { throw 0; }; - REQUIRE((operand_t{std::unexpect, Error{"Not good"}} | and_then(wrong)).error().what == "Not good"); + REQUIRE((operand_t{::pfn::unexpect, Error{"Not good"}} | and_then(wrong)).error().what == "Not good"); } } } @@ -451,9 +451,9 @@ TEST_CASE("and_then", "[and_then][expected][expected_value][pack]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | and_then(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | and_then(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | and_then(wrong)) .error() .what @@ -478,7 +478,7 @@ TEST_CASE("and_then", "[and_then][expected][expected_void]") }; constexpr auto wrong = []() -> operand_t { throw 0; }; - auto fnFail = [&count]() -> operand_t { return std::unexpected("Got " + std::to_string(++count)); }; + auto fnFail = [&count]() -> operand_t { return ::pfn::unexpected("Got " + std::to_string(++count)); }; auto fnXabs = [&count]() -> fn::expected { return {{++count}}; }; static_assert(is::invocable_with_any(fnValue)); @@ -514,7 +514,7 @@ TEST_CASE("and_then", "[and_then][expected][expected_void]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | and_then(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -550,9 +550,9 @@ TEST_CASE("and_then", "[and_then][expected][expected_void]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | and_then(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | and_then(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | and_then(wrong)) .error() .what @@ -765,7 +765,7 @@ TEST_CASE("constexpr and_then expected", "[and_then][constexpr][expected]") constexpr auto fn = [](int i) constexpr noexcept -> T { if (i < 3) return {i + 1}; - return std::unexpected{Error::ThresholdExceeded}; + return ::pfn::unexpected{Error::ThresholdExceeded}; }; constexpr auto r1 = T{0} | fn::and_then(fn); static_assert(r1.value() == 1); @@ -781,7 +781,7 @@ TEST_CASE("constexpr and_then expected", "[and_then][constexpr][expected]") return {true}; if (i == 0) return {false}; - return std::unexpected{Error::SomethingElse}; + return ::pfn::unexpected{Error::SomethingElse}; }; constexpr auto r1 = T{1} | fn::and_then(fn); static_assert(std::is_same_v const>); @@ -805,7 +805,7 @@ TEST_CASE("constexpr and_then expected with sum", "[and_then][constexpr][expecte constexpr auto fn = fn::overload{[](int i) constexpr noexcept -> T { if (i < 3) return {i + 1}; - return std::unexpected{Error::ThresholdExceeded}; + return ::pfn::unexpected{Error::ThresholdExceeded}; }, [](Xint v) constexpr noexcept -> T { return v; }}; constexpr auto r1 = T{0} | fn::and_then(fn); @@ -823,9 +823,9 @@ TEST_CASE("constexpr and_then expected with sum", "[and_then][constexpr][expecte return {true}; if (i == 0) return {false}; - return std::unexpected{Error::SomethingElse}; + return ::pfn::unexpected{Error::SomethingElse}; }, - [](Xint) constexpr noexcept -> T1 { return std::unexpected{Error::UnexpectedType}; }}; + [](Xint) constexpr noexcept -> T1 { return ::pfn::unexpected{Error::UnexpectedType}; }}; constexpr auto r1 = T{1} | fn::and_then(fn); static_assert(std::is_same_v const>); static_assert(r1.value() == true); @@ -848,7 +848,7 @@ TEST_CASE("constexpr and_then graded monad", "[and_then][constexpr][expected][gr constexpr auto fn1 = [](int i) -> fn::expected { if (i < 2) return {i + 1}; - return std::unexpected{i}; + return ::pfn::unexpected{i}; }; constexpr auto r1 = T{0} | fn::and_then(fn1); @@ -866,7 +866,7 @@ TEST_CASE("constexpr and_then graded monad", "[and_then][constexpr][expected][gr { constexpr auto fn2 = [](int i) -> fn::expected { if (i < 0 || i > 1) - return std::unexpected{Error::InvalidValue}; + return ::pfn::unexpected{Error::InvalidValue}; return {i == 1}; }; diff --git a/tests/fn/discard.cpp b/tests/fn/discard.cpp index a11b3e8a..89dbc5e7 100644 --- a/tests/fn/discard.cpp +++ b/tests/fn/discard.cpp @@ -50,7 +50,7 @@ TEST_CASE("discard", "[discard][expected][expected_value]") WHEN("operand is error") { - operand_t a{std::unexpect, Error{"Not good"}}; + operand_t a{::pfn::unexpect, Error{"Not good"}}; a | discard(); REQUIRE(a.error().what == "Not good"); @@ -67,7 +67,7 @@ TEST_CASE("discard", "[discard][expected][expected_value]") WHEN("operand is error") { - operand_t{std::unexpect, Error{"Not good"}} | discard(); + operand_t{::pfn::unexpect, Error{"Not good"}} | discard(); SUCCEED(); } } @@ -91,7 +91,7 @@ TEST_CASE("discard with pack", "[discard][expected][expected_value][pack]") WHEN("operand is error") { - operand_t b{std::unexpect, Error{"Pack error"}}; + operand_t b{::pfn::unexpect, Error{"Pack error"}}; b | discard(); REQUIRE(b.error().what == "Pack error"); @@ -121,7 +121,7 @@ TEST_CASE("discard", "[discard][expected][expected_void]") WHEN("operand is error") { - operand_t a{std::unexpect, Error{"Not good"}}; + operand_t a{::pfn::unexpect, Error{"Not good"}}; a | discard(); REQUIRE(a.error().what == "Not good"); @@ -138,7 +138,7 @@ TEST_CASE("discard", "[discard][expected][expected_void]") WHEN("operand is error") { - operand_t{std::unexpect, Error{"Not good"}} | discard(); + operand_t{::pfn::unexpect, Error{"Not good"}} | discard(); SUCCEED(); } } @@ -198,7 +198,7 @@ TEST_CASE("constexpr discard expected", "[discard][constexpr][expected]") using T = fn::expected; constexpr auto a = T{42}; - constexpr auto b = T{std::unexpect, Error::ThresholdExceeded}; + constexpr auto b = T{::pfn::unexpect, Error::ThresholdExceeded}; constexpr auto test = [](T v) { v | discard(); diff --git a/tests/fn/expected.cpp b/tests/fn/expected.cpp index 59622f0f..aac94b6f 100644 --- a/tests/fn/expected.cpp +++ b/tests/fn/expected.cpp @@ -46,7 +46,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("and_then to error") { - constexpr auto fn = []() -> fn::expected { return std::unexpected(FileNotFound); }; + constexpr auto fn = []() -> fn::expected { return ::pfn::unexpected(FileNotFound); }; constexpr auto a = unit.and_then(fn); static_assert(std::is_same_v> const>); static_assert(a.error() == fn::sum{FileNotFound}); @@ -81,7 +81,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("and_then to error") { - constexpr auto fn = []() -> fn::expected { return std::unexpected(FileNotFound); }; + constexpr auto fn = []() -> fn::expected { return ::pfn::unexpected(FileNotFound); }; auto a = unit.and_then(fn); static_assert(std::is_same_v>>); CHECK(a.error() == fn::sum{FileNotFound}); @@ -114,7 +114,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] } WHEN("error") { - T s{std::unexpect, Unknown}; + T s{::pfn::unexpect, Unknown}; CHECK(s.sum_error().error() == fn::sum{Unknown}); CHECK(std::as_const(s).sum_error().error() == fn::sum{Unknown}); CHECK(std::move(std::as_const(s)).sum_error().error() == fn::sum{Unknown}); @@ -141,7 +141,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] } WHEN("error") { - T s{std::unexpect, Unknown}; + T s{::pfn::unexpect, Unknown}; CHECK(s.sum_error().error() == fn::sum{Unknown}); CHECK(std::as_const(s).sum_error().error() == fn::sum{Unknown}); CHECK(std::move(std::as_const(s)).sum_error().error() == fn::sum{Unknown}); @@ -168,7 +168,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] } WHEN("error") { - T s{std::unexpect, Unknown}; + T s{::pfn::unexpect, Unknown}; CHECK(s.sum_value().error() == Unknown); CHECK(std::as_const(s).sum_value().error() == Unknown); CHECK(std::move(std::as_const(s)).sum_value().error() == Unknown); @@ -195,7 +195,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] } WHEN("error") { - T s{std::unexpect, Unknown}; + T s{::pfn::unexpect, Unknown}; CHECK(s.sum_value().error() == Unknown); CHECK(std::as_const(s).sum_value().error() == Unknown); CHECK(std::move(std::as_const(s)).sum_value().error() == Unknown); @@ -240,7 +240,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("value to error") { - constexpr auto fn = [](int i) -> fn::expected { return std::unexpected(i >= 1); }; + constexpr auto fn = [](int i) -> fn::expected { return ::pfn::unexpected(i >= 1); }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{true}); CHECK(std::as_const(s).and_then(fn).error() == fn::sum{true}); @@ -250,7 +250,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("error") { - fn::expected> s{std::unexpect, fn::sum{FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::sum{FileNotFound}}; constexpr auto fn = [](int) -> fn::expected { throw 0; }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{FileNotFound}); @@ -293,7 +293,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("value to error") { - constexpr auto fn = []() -> fn::expected { return std::unexpected(true); }; + constexpr auto fn = []() -> fn::expected { return ::pfn::unexpected(true); }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{true}); CHECK(std::as_const(s).and_then(fn).error() == fn::sum{true}); @@ -303,7 +303,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("error") { - fn::expected> s{std::unexpect, fn::sum{FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::sum{FileNotFound}}; constexpr auto fn = []() -> fn::expected { throw 0; }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{FileNotFound}); @@ -346,7 +346,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("value to error") { - constexpr auto fn = [](int i) -> fn::expected { return std::unexpected(i >= 1); }; + constexpr auto fn = [](int i) -> fn::expected { return ::pfn::unexpected(i >= 1); }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{true}); CHECK(std::as_const(s).and_then(fn).error() == fn::sum{true}); @@ -356,7 +356,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("error") { - fn::expected> s{std::unexpect, fn::sum{FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::sum{FileNotFound}}; constexpr auto fn = [](int) -> fn::expected { throw 0; }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{FileNotFound}); @@ -399,7 +399,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("value to error") { - constexpr auto fn = []() -> fn::expected { return std::unexpected(true); }; + constexpr auto fn = []() -> fn::expected { return ::pfn::unexpected(true); }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{true}); CHECK(std::as_const(s).and_then(fn).error() == fn::sum{true}); @@ -409,7 +409,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("error") { - fn::expected> s{std::unexpect, fn::sum{FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::sum{FileNotFound}}; constexpr auto fn = []() -> fn::expected { throw 0; }; static_assert(std::is_same_v>>); CHECK(s.and_then(fn).error() == fn::sum{FileNotFound}); @@ -424,7 +424,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("or_else") { - fn::expected, Error> s{std::unexpect, FileNotFound}; + fn::expected, Error> s{::pfn::unexpect, FileNotFound}; constexpr auto fn1 = [](int) -> fn::expected { throw 0; }; static_assert(std::is_same_v, Error>>); @@ -455,7 +455,7 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("error to error") { - constexpr auto fn = [](Error) -> fn::expected { return std::unexpected("Boo"); }; + constexpr auto fn = [](Error) -> fn::expected { return ::pfn::unexpected("Boo"); }; static_assert(std::is_same_v, std::string>>); CHECK(s.or_else(fn).error() == "Boo"); CHECK(std::as_const(s).or_else(fn).error() == "Boo"); @@ -488,7 +488,7 @@ TEST_CASE("graded monad constexpr and runtime", "[constexpr][and_then][or_else][ constexpr auto fn1 = [](int i) -> fn::expected { if (i < 2) return {i + 1}; - return std::unexpected{i}; + return ::pfn::unexpected{i}; }; constexpr auto r1 = T{0}.and_then(fn1); @@ -508,7 +508,7 @@ TEST_CASE("graded monad constexpr and runtime", "[constexpr][and_then][or_else][ { constexpr auto fn2 = [](int i) -> fn::expected { if (i < 0 || i > 1) - return std::unexpected{Error::InvalidValue}; + return ::pfn::unexpected{Error::InvalidValue}; return {i == 1}; }; @@ -540,7 +540,7 @@ TEST_CASE("graded monad constexpr and runtime", "[constexpr][and_then][or_else][ constexpr auto fn1 = [](int i) -> fn::expected { if (i < 2) return {i + 1}; - return std::unexpected{i}; + return ::pfn::unexpected{i}; }; auto const r1 = T{0}.and_then(fn1); @@ -558,7 +558,7 @@ TEST_CASE("graded monad constexpr and runtime", "[constexpr][and_then][or_else][ { constexpr auto fn2 = [](int i) -> fn::expected { if (i < 0 || i > 1) - return std::unexpected{Error::InvalidValue}; + return ::pfn::unexpected{Error::InvalidValue}; return {i == 1}; }; @@ -584,15 +584,15 @@ TEST_CASE("graded monad constexpr and runtime", "[constexpr][and_then][or_else][ constexpr auto fn1 = [](Error i) -> fn::expected { if (i == Error::Unknown) return {0}; - return std::unexpected{(int)i}; + return ::pfn::unexpected{(int)i}; }; constexpr auto r1 = T{14}.or_else(fn1); static_assert(std::is_same_v, int> const>); static_assert(r1.value() == fn::sum{14}); - constexpr auto r2 = T{std::unexpect, Error::InvalidValue}.or_else(fn1); + constexpr auto r2 = T{::pfn::unexpect, Error::InvalidValue}.or_else(fn1); static_assert(r2.error() == 1); - constexpr auto r3 = T{std::unexpect, Error::Unknown}.or_else(fn1); + constexpr auto r3 = T{::pfn::unexpect, Error::Unknown}.or_else(fn1); static_assert(r3.value() == fn::sum{0}); SUCCEED(); @@ -605,15 +605,15 @@ TEST_CASE("graded monad constexpr and runtime", "[constexpr][and_then][or_else][ constexpr auto fn1 = [](Error i) -> fn::expected { if (i == Error::Unknown) return {0}; - return std::unexpected{(int)i}; + return ::pfn::unexpected{(int)i}; }; auto const r1 = T{14}.or_else(fn1); static_assert(std::is_same_v, int> const>); CHECK(r1.value() == fn::sum{14}); - auto const r2 = T{std::unexpect, Error::InvalidValue}.or_else(fn1); + auto const r2 = T{::pfn::unexpect, Error::InvalidValue}.or_else(fn1); CHECK(r2.error() == 1); - auto const r3 = T{std::unexpect, Error::Unknown}.or_else(fn1); + auto const r3 = T{::pfn::unexpect, Error::Unknown}.or_else(fn1); CHECK(r3.value() == fn::sum{0}); } } @@ -658,7 +658,7 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat WHEN("error") { - fn::expected, Error> s{std::unexpect, FileNotFound}; + fn::expected, Error> s{::pfn::unexpect, FileNotFound}; CHECK(s.and_then( // [](auto...) -> fn::expected { throw 0; }) .error() @@ -749,10 +749,10 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat WHEN("error") { - fn::expected, Error> s{std::unexpect, FileNotFound}; + fn::expected, Error> s{::pfn::unexpect, FileNotFound}; CHECK(s.transform([](auto...) -> bool { throw 0; }).error() == FileNotFound); CHECK(std::as_const(s).transform([](auto...) -> bool { throw 0; }).error() == FileNotFound); - CHECK(fn::expected, Error>{std::unexpect, FileNotFound} + CHECK(fn::expected, Error>{::pfn::unexpect, FileNotFound} .transform([](auto...) -> bool { throw 0; }) .error() == FileNotFound); @@ -774,16 +774,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{}) .value() == 42); - CHECK((fn::expected{std::unexpect, FileNotFound} // + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // & fn::expected{}) .error() == FileNotFound); CHECK((fn::expected{42} // - & fn::expected{std::unexpect, Unknown}) + & fn::expected{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, Unknown}) + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -798,16 +798,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{12}) .value() == 12); - CHECK((fn::expected{std::unexpect, FileNotFound} // + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // & fn::expected{12}) .error() == FileNotFound); CHECK((fn::expected{} // - & fn::expected{std::unexpect, Unknown}) + & fn::expected{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, Unknown}) + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -821,16 +821,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat CHECK((fn::expected{} // & fn::expected{}) .has_value()); - CHECK((fn::expected{std::unexpect, FileNotFound} // + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // & fn::expected{}) .error() == FileNotFound); CHECK((fn::expected{} // - & fn::expected{std::unexpect, Unknown}) + & fn::expected{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, Unknown}) + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -845,16 +845,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{12}) .transform([](double d, int i) constexpr -> bool { return d == 0.5 && i == 12; }) .value()); - CHECK((fn::expected{std::unexpect, FileNotFound} // + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // & fn::expected{12}) .error() == FileNotFound); CHECK((fn::expected{} // - & fn::expected{std::unexpect, Unknown}) + & fn::expected{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, Unknown}) + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -869,16 +869,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected, Error>{std::in_place, fn::pack{true, 12}}) .transform([](double d, bool b, int i) constexpr -> bool { return d == 0.5 && b && i == 12; }) .value()); - CHECK((fn::expected{std::unexpect, FileNotFound} // + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // & fn::expected, Error>{std::in_place, fn::pack{true, 12}}) .error() == FileNotFound); CHECK((fn::expected{} // - & fn::expected, Error>{std::unexpect, Unknown}) + & fn::expected, Error>{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected{std::unexpect, FileNotFound} // - & fn::expected, Error>{std::unexpect, Unknown}) + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // + & fn::expected, Error>{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -893,16 +893,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{12}) .transform([](double d, bool b, int i) constexpr -> bool { return d == 0.5 && b && i == 12; }) .value()); - CHECK((fn::expected, Error>{std::unexpect, FileNotFound} // + CHECK((fn::expected, Error>{::pfn::unexpect, FileNotFound} // & fn::expected{12}) .error() == FileNotFound); CHECK((fn::expected, Error>{std::in_place, fn::pack{0.5, true}} // - & fn::expected{std::unexpect, Unknown}) + & fn::expected{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected, Error>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, Unknown}) + CHECK((fn::expected, Error>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -919,16 +919,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat return d == 0.5 && b1 && b2 && i == 12; }) .value()); - CHECK((fn::expected, Error>{std::unexpect, FileNotFound} // + CHECK((fn::expected, Error>{::pfn::unexpect, FileNotFound} // & fn::expected, Error>{std::in_place, fn::pack{true, 12}}) .error() == FileNotFound); CHECK((fn::expected, Error>{std::in_place, fn::pack{0.5, true}} // - & fn::expected, Error>{std::unexpect, Unknown}) + & fn::expected, Error>{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected, Error>{std::unexpect, FileNotFound} // - & fn::expected, Error>{std::unexpect, Unknown}) + CHECK((fn::expected, Error>{::pfn::unexpect, FileNotFound} // + & fn::expected, Error>{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -943,16 +943,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{}) .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); - CHECK((fn::expected, Error>{std::unexpect, FileNotFound} // + CHECK((fn::expected, Error>{::pfn::unexpect, FileNotFound} // & fn::expected{}) .error() == FileNotFound); CHECK((fn::expected, Error>{std::in_place, fn::pack{0.5, true}} // - & fn::expected{std::unexpect, Unknown}) + & fn::expected{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected, Error>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, Unknown}) + CHECK((fn::expected, Error>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -967,16 +967,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected, Error>{std::in_place, fn::pack{0.5, true}}) .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); - CHECK((fn::expected{std::unexpect, FileNotFound} // + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // & fn::expected, Error>{std::in_place, fn::pack{0.5, true}}) .error() == FileNotFound); CHECK((fn::expected{} // - & fn::expected, Error>{std::unexpect, Unknown}) + & fn::expected, Error>{::pfn::unexpect, Unknown}) .error() == Unknown); - CHECK((fn::expected{std::unexpect, FileNotFound} // - & fn::expected, Error>{std::unexpect, Unknown}) + CHECK((fn::expected{::pfn::unexpect, FileNotFound} // + & fn::expected, Error>{::pfn::unexpect, Unknown}) .error() == FileNotFound); } @@ -997,9 +997,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); WHEN("sum of packs on left") { @@ -1016,9 +1016,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); } WHEN("sum of packs on right") @@ -1036,9 +1036,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{fn::pack{0.5, 3}}}).error() == FileNotFound); - CHECK((Lh{fn::sum{12}} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{fn::pack{0.5, 3}}}).error() == FileNotFound); + CHECK((Lh{fn::sum{12}} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); } } @@ -1057,9 +1057,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{12}).error() == FileNotFound); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{12}).error() == FileNotFound); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); WHEN("sum of packs on left") { @@ -1075,9 +1075,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{12}).error() == FileNotFound); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{12}).error() == FileNotFound); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); } WHEN("pack on right") @@ -1094,9 +1094,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::in_place, fn::pack{0.5, true}}).error() == FileNotFound); - CHECK((Lh{fn::sum{1.5}} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{std::in_place, fn::pack{0.5, true}}).error() == FileNotFound); + CHECK((Lh{fn::sum{1.5}} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); } } @@ -1115,9 +1115,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); - CHECK((Lh{0.5} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); + CHECK((Lh{0.5} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); WHEN("pack on left") { @@ -1133,9 +1133,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); - CHECK((Lh{fn::pack{0.5, 3}} & Rh{std::unexpect, Unknown}).error() == Unknown); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, Unknown}).error() == FileNotFound); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == FileNotFound); + CHECK((Lh{fn::pack{0.5, 3}} & Rh{::pfn::unexpect, Unknown}).error() == Unknown); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, Unknown}).error() == FileNotFound); } } } @@ -1172,16 +1172,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{}) .value() == 42); - CHECK((fn::expected>{std::unexpect, fn::sum{FileNotFound}} // + CHECK((fn::expected>{::pfn::unexpect, fn::sum{FileNotFound}} // & fn::expected{}) .error() == fn::sum{FileNotFound}); CHECK((fn::expected>{42} // - & fn::expected{std::unexpect, 13}) + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{13}); - CHECK((fn::expected>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, 13}) + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{FileNotFound}); } @@ -1196,16 +1196,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{12}) .value() == 12); - CHECK((fn::expected>{std::unexpect, FileNotFound} // + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // & fn::expected{12}) .error() == fn::sum{FileNotFound}); CHECK((fn::expected>{} // - & fn::expected{std::unexpect, 13}) + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{13}); - CHECK((fn::expected>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, 13}) + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{FileNotFound}); } @@ -1219,16 +1219,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat CHECK((fn::expected>{} // & fn::expected{}) .has_value()); - CHECK((fn::expected>{std::unexpect, FileNotFound} // + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // & fn::expected{}) .error() == fn::sum{FileNotFound}); CHECK((fn::expected>{} // - & fn::expected{std::unexpect, 13}) + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{13}); - CHECK((fn::expected>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, 13}) + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{FileNotFound}); } @@ -1243,16 +1243,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{12}) .transform([](double d, int i) constexpr -> bool { return d == 0.5 && i == 12; }) .value()); - CHECK((fn::expected>{std::unexpect, FileNotFound} // + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // & fn::expected{12}) .error() == fn::sum{FileNotFound}); CHECK((fn::expected>{} // - & fn::expected{std::unexpect, 13}) + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{13}); - CHECK((fn::expected>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, 13}) + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{FileNotFound}); } @@ -1267,16 +1267,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{12}) .transform([](double d, bool b, int i) constexpr -> bool { return d == 0.5 && b && i == 12; }) .value()); - CHECK((fn::expected, fn::sum>{std::unexpect, FileNotFound} // + CHECK((fn::expected, fn::sum>{::pfn::unexpect, FileNotFound} // & fn::expected{12}) .error() == fn::sum{FileNotFound}); CHECK((fn::expected, fn::sum>{std::in_place, fn::pack{0.5, true}} // - & fn::expected{std::unexpect, 13}) + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{13}); - CHECK((fn::expected, fn::sum>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, 13}) + CHECK((fn::expected, fn::sum>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{FileNotFound}); } @@ -1291,16 +1291,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected{}) .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); - CHECK((fn::expected, fn::sum>{std::unexpect, FileNotFound} // + CHECK((fn::expected, fn::sum>{::pfn::unexpect, FileNotFound} // & fn::expected{}) .error() == fn::sum{FileNotFound}); CHECK((fn::expected, fn::sum>{std::in_place, fn::pack{0.5, true}} // - & fn::expected{std::unexpect, 13}) + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{13}); - CHECK((fn::expected, fn::sum>{std::unexpect, FileNotFound} // - & fn::expected{std::unexpect, 13}) + CHECK((fn::expected, fn::sum>{::pfn::unexpect, FileNotFound} // + & fn::expected{::pfn::unexpect, 13}) .error() == fn::sum{FileNotFound}); } @@ -1315,16 +1315,16 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected, int>{std::in_place, fn::pack{0.5, true}}) .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); - CHECK((fn::expected>{std::unexpect, FileNotFound} // + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // & fn::expected, int>{std::in_place, fn::pack{0.5, true}}) .error() == fn::sum{FileNotFound}); CHECK((fn::expected>{} // - & fn::expected, int>{std::unexpect, 13}) + & fn::expected, int>{::pfn::unexpect, 13}) .error() == fn::sum{13}); - CHECK((fn::expected>{std::unexpect, FileNotFound} // - & fn::expected, int>{std::unexpect, 13}) + CHECK((fn::expected>{::pfn::unexpect, FileNotFound} // + & fn::expected, int>{::pfn::unexpect, 13}) .error() == fn::sum{FileNotFound}); } @@ -1345,9 +1345,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); WHEN("sum of packs on left") { @@ -1364,9 +1364,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); } } @@ -1385,9 +1385,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); WHEN("sum of packs on left") { @@ -1403,9 +1403,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); } } @@ -1424,9 +1424,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{0.5} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{0.5} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); WHEN("pack on left") { @@ -1442,9 +1442,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::pack{0.5, 3}} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::pack{0.5, 3}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); } } } @@ -1482,15 +1482,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat .value() == 12); CHECK((fn::expected{12} // - & fn::expected>{std::unexpect, FileNotFound}) + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{FileNotFound}); - CHECK((fn::expected{std::unexpect, 13} // + CHECK((fn::expected{::pfn::unexpect, 13} // & fn::expected>{}) .error() == fn::sum{13}); - CHECK((fn::expected{std::unexpect, 13} // - & fn::expected>{std::unexpect, FileNotFound}) + CHECK((fn::expected{::pfn::unexpect, 13} // + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{13}); } @@ -1506,15 +1506,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat .value() == 42); CHECK((fn::expected{} // - & fn::expected>{std::unexpect, fn::sum{FileNotFound}}) + & fn::expected>{::pfn::unexpect, fn::sum{FileNotFound}}) .error() == fn::sum{FileNotFound}); - CHECK((fn::expected{std::unexpect, 13} // + CHECK((fn::expected{::pfn::unexpect, 13} // & fn::expected>{42}) .error() == fn::sum{13}); - CHECK((fn::expected{std::unexpect, 13} // - & fn::expected>{std::unexpect, FileNotFound}) + CHECK((fn::expected{::pfn::unexpect, 13} // + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{13}); } @@ -1529,15 +1529,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat & fn::expected>{}) .has_value()); CHECK((fn::expected{} // - & fn::expected>{std::unexpect, FileNotFound}) + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{FileNotFound}); - CHECK((fn::expected{std::unexpect, 13} // + CHECK((fn::expected{::pfn::unexpect, 13} // & fn::expected>{}) .error() == fn::sum{13}); - CHECK((fn::expected{std::unexpect, 13} // - & fn::expected>{std::unexpect, FileNotFound}) + CHECK((fn::expected{::pfn::unexpect, 13} // + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{13}); } @@ -1553,15 +1553,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat .transform([](double d, int i) constexpr -> bool { return d == 0.5 && i == 12; }) .value()); CHECK((fn::expected{0.5} // - & fn::expected>{std::unexpect, FileNotFound}) + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{FileNotFound}); - CHECK((fn::expected{std::unexpect, 13} // + CHECK((fn::expected{::pfn::unexpect, 13} // & fn::expected>{12}) .error() == fn::sum{13}); - CHECK((fn::expected{std::unexpect, 13} // - & fn::expected>{std::unexpect, FileNotFound}) + CHECK((fn::expected{::pfn::unexpect, 13} // + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{13}); } @@ -1577,15 +1577,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat .transform([](double d, bool b, int i) constexpr -> bool { return d == 0.5 && b && i == 12; }) .value()); CHECK((fn::expected, int>{std::in_place, fn::pack{0.5, true}} // - & fn::expected>{std::unexpect, FileNotFound}) + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{FileNotFound}); - CHECK((fn::expected, int>{std::unexpect, 13} // + CHECK((fn::expected, int>{::pfn::unexpect, 13} // & fn::expected>{12}) .error() == fn::sum{13}); - CHECK((fn::expected, int>{std::unexpect, 13} // - & fn::expected>{std::unexpect, FileNotFound}) + CHECK((fn::expected, int>{::pfn::unexpect, 13} // + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{13}); } @@ -1601,15 +1601,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); CHECK((fn::expected, int>{std::in_place, fn::pack{0.5, true}} // - & fn::expected>{std::unexpect, FileNotFound}) + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{FileNotFound}); - CHECK((fn::expected, int>{std::unexpect, 13} // + CHECK((fn::expected, int>{::pfn::unexpect, 13} // & fn::expected>{}) .error() == fn::sum{13}); - CHECK((fn::expected, int>{std::unexpect, 13} // - & fn::expected>{std::unexpect, FileNotFound}) + CHECK((fn::expected, int>{::pfn::unexpect, 13} // + & fn::expected>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{13}); } @@ -1625,15 +1625,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); CHECK((fn::expected{} // - & fn::expected, fn::sum>{std::unexpect, FileNotFound}) + & fn::expected, fn::sum>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{FileNotFound}); - CHECK((fn::expected{std::unexpect, 13} // + CHECK((fn::expected{::pfn::unexpect, 13} // & fn::expected, fn::sum>{std::in_place, fn::pack{0.5, true}}) .error() == fn::sum{13}); - CHECK((fn::expected{std::unexpect, 13} // - & fn::expected, fn::sum>{std::unexpect, FileNotFound}) + CHECK((fn::expected{::pfn::unexpect, 13} // + & fn::expected, fn::sum>{::pfn::unexpect, FileNotFound}) .error() == fn::sum{13}); } @@ -1654,9 +1654,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); WHEN("sum of packs on left") { @@ -1673,9 +1673,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); } } @@ -1694,9 +1694,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{12}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{12}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); WHEN("sum of packs on left") { @@ -1712,9 +1712,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{12}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{12}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); } } @@ -1733,9 +1733,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{0.5} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{0.5} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); WHEN("pack on left") { @@ -1751,9 +1751,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::pack{0.5, 3}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::pack{0.5, 3}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); } } } @@ -1772,9 +1772,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat fn::expected>>); CHECK((Lh{12} & Rh{}).value() == 12); - CHECK((Lh{12} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); + CHECK((Lh{12} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); } WHEN("void & value yield value") @@ -1785,9 +1785,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat fn::expected>>); CHECK((Lh{} & Rh{42}).value() == 42); - CHECK((Lh{} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{42}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); + CHECK((Lh{} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{42}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); } WHEN("void & void yield void") @@ -1798,9 +1798,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat fn::expected>>); CHECK((Lh{} & Rh{}).has_value()); - CHECK((Lh{} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); + CHECK((Lh{} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); } WHEN("value & value yield pack") @@ -1813,9 +1813,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat CHECK((Lh{0.5} & Rh{12}) .transform([](double d, int i) constexpr -> bool { return d == 0.5 && i == 12; }) .value()); - CHECK((Lh{0.5} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{12}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); + CHECK((Lh{0.5} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{12}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); } WHEN("pack & value yield pack") @@ -1830,15 +1830,15 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat .transform([](double d, bool b, int i) constexpr -> bool { return d == 0.5 && b && i == 12; }) .value()); CHECK((Lh{std::in_place, fn::pack{0.5, true}} // - & Rh{std::unexpect, fn::sum{FileNotFound}}) + & Rh{::pfn::unexpect, fn::sum{FileNotFound}}) .error() == fn::sum{FileNotFound}); - CHECK((Lh{std::unexpect, fn::sum{13}} // + CHECK((Lh{::pfn::unexpect, fn::sum{13}} // & Rh{12}) .error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{13}} // - & Rh{std::unexpect, fn::sum{FileNotFound}}) + CHECK((Lh{::pfn::unexpect, fn::sum{13}} // + & Rh{::pfn::unexpect, fn::sum{FileNotFound}}) .error() == fn::sum{13}); } @@ -1853,10 +1853,10 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat CHECK((Lh{std::in_place, fn::pack{0.5, true}} & Rh{}) .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); - CHECK((Lh{std::in_place, fn::pack{0.5, true}} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() + CHECK((Lh{std::in_place, fn::pack{0.5, true}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); } WHEN("void & pack yield pack") @@ -1869,10 +1869,10 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat CHECK((Lh{} & Rh{std::in_place, fn::pack{0.5, true}}) .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); - CHECK((Lh{} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{std::in_place, fn::pack{0.5, true}}).error() + CHECK((Lh{} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{std::in_place, fn::pack{0.5, true}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{13}} & Rh{std::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); } WHEN("sum on both sides") @@ -1891,9 +1891,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, fn::sum{13}}).error() + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); WHEN("sum of packs on left") @@ -1917,9 +1917,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, fn::sum{13}}).error() + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); } } @@ -1939,9 +1939,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{0.5}} & Rh{std::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{0.5}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); WHEN("sum of packs on left") { @@ -1963,9 +1963,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, fn::sum{13}}).error() + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); } } @@ -1985,9 +1985,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{0.5} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, FileNotFound} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{0.5} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); WHEN("pack on left") { @@ -2009,9 +2009,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat }) .value() == fn::sum{true}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); - CHECK((Lh{fn::pack{0.5, 3}} & Rh{std::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{std::unexpect, fn::sum{FileNotFound}} & Rh{std::unexpect, fn::sum{13}}).error() + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{fn::pack{0.5, 3}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); } } @@ -2075,7 +2075,7 @@ TEST_CASE("expected sum support and_then", "[expected][sum][and_then]") WHEN("error") { - fn::expected, Error> s{std::unexpect, FileNotFound}; + fn::expected, Error> s{::pfn::unexpect, FileNotFound}; CHECK(s.and_then( // [](auto...) -> fn::expected { return {true}; }) .error() @@ -2085,7 +2085,7 @@ TEST_CASE("expected sum support and_then", "[expected][sum][and_then]") [](auto...) -> fn::expected { return {true}; }) .error() == FileNotFound); - CHECK(fn::expected, Error>{std::unexpect, FileNotFound} + CHECK(fn::expected, Error>{::pfn::unexpect, FileNotFound} .and_then( // [](auto...) -> fn::expected { return {true}; }) .error() @@ -2117,7 +2117,7 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") { WHEN("value") { - fn::expected> s{std::unexpect, fn::sum{12}}; + fn::expected> s{::pfn::unexpect, fn::sum{12}}; CHECK(s.or_else( // fn::overload([](int &i) -> fn::expected { return {i}; }, @@ -2204,7 +2204,7 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") [](std::string_view const &) -> fn::expected { throw 0; }, [](std::string_view &&) -> fn::expected { throw 0; }, [](std::string_view const &&) -> fn::expected { throw 0; }); - constexpr fn::expected> a{std::unexpect, fn::sum{42}}; + constexpr fn::expected> a{::pfn::unexpect, fn::sum{42}}; static_assert(std::is_same_v>); static_assert(a.or_else(fn).value() == 42); } @@ -2212,10 +2212,10 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") WHEN("void") { - fn::expected> s{std::unexpect, fn::sum{12}}; + fn::expected> s{::pfn::unexpect, fn::sum{12}}; CHECK(s.or_else( // - fn::overload([](int &) -> fn::expected { return std::unexpected{FileNotFound}; }, + fn::overload([](int &) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, [](int const &) -> fn::expected { throw 0; }, [](int &&) -> fn::expected { throw 0; }, [](int const &&) -> fn::expected { throw 0; }, @@ -2230,7 +2230,7 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") .or_else( // fn::overload( [](int &) -> fn::expected { throw 0; }, - [](int const &) -> fn::expected { return std::unexpected{FileNotFound}; }, + [](int const &) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, [](int &&) -> fn::expected { throw 0; }, [](int const &&) -> fn::expected { throw 0; }, [](std::string_view &) -> fn::expected { throw 0; }, @@ -2246,7 +2246,7 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") [](int &) -> fn::expected { throw 0; }, [](int const &) -> fn::expected { throw 0; }, [](int &&) -> fn::expected { throw 0; }, - [](int const &&) -> fn::expected { return std::unexpected{FileNotFound}; }, + [](int const &&) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, [](std::string_view &) -> fn::expected { throw 0; }, [](std::string_view const &) -> fn::expected { throw 0; }, [](std::string_view &&) -> fn::expected { throw 0; }, @@ -2258,7 +2258,7 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") .or_else( // fn::overload([](int &) -> fn::expected { throw 0; }, [](int const &) -> fn::expected { throw 0; }, - [](int &&) -> fn::expected { return std::unexpected{FileNotFound}; }, + [](int &&) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, [](int const &&) -> fn::expected { throw 0; }, [](std::string_view &) -> fn::expected { throw 0; }, [](std::string_view const &) -> fn::expected { throw 0; }, @@ -2291,14 +2291,14 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") { constexpr auto fn = fn::overload([](int &) -> fn::expected { throw 0; }, - [](int const &) -> fn::expected { return std::unexpected{FileNotFound}; }, + [](int const &) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, [](int &&) -> fn::expected { throw 0; }, [](int const &&) -> fn::expected { throw 0; }, [](std::string_view &) -> fn::expected { throw 0; }, [](std::string_view const &) -> fn::expected { throw 0; }, [](std::string_view &&) -> fn::expected { throw 0; }, [](std::string_view const &&) -> fn::expected { throw 0; }); - constexpr fn::expected> a{std::unexpect, fn::sum{42}}; + constexpr fn::expected> a{::pfn::unexpect, fn::sum{42}}; static_assert(std::is_same_v>); static_assert(a.or_else(fn).error() == FileNotFound); } @@ -2353,7 +2353,7 @@ TEST_CASE("expected sum support transform", "[expected][sum][transform]") WHEN("error") { - fn::expected, Error> s{std::unexpect, FileNotFound}; + fn::expected, Error> s{::pfn::unexpect, FileNotFound}; CHECK(s.transform( // [](auto...) -> std::monostate { throw 0; }) .error() @@ -2393,7 +2393,7 @@ TEST_CASE("expected sum support transform_error", "[expected][sum][transform_err { WHEN("value") { - fn::expected> s{std::unexpect, fn::sum{12}}; + fn::expected> s{::pfn::unexpect, fn::sum{12}}; CHECK(s.transform_error( // fn::overload( @@ -2465,7 +2465,7 @@ TEST_CASE("expected sum support transform_error", "[expected][sum][transform_err [](int const &&) -> bool { throw 0; }, [](std::string_view &) -> int { throw 0; }, [](std::string_view const &) -> int { throw 0; }, [](std::string_view &&) -> int { throw 0; }, [](std::string_view const &&) -> int { throw 0; }); - constexpr fn::expected> a{std::unexpect, fn::sum{42}}; + constexpr fn::expected> a{::pfn::unexpect, fn::sum{42}}; static_assert(std::is_same_v>>); static_assert(a.transform_error(fn).error() == fn::sum{true}); } @@ -2473,7 +2473,7 @@ TEST_CASE("expected sum support transform_error", "[expected][sum][transform_err WHEN("void") { - fn::expected> s{std::unexpect, fn::sum{12}}; + fn::expected> s{::pfn::unexpect, fn::sum{12}}; CHECK(s.transform_error( // fn::overload( @@ -2541,7 +2541,7 @@ TEST_CASE("expected sum support transform_error", "[expected][sum][transform_err [](int const &&) -> int { throw 0; }, [](std::string_view &) -> int { throw 0; }, [](std::string_view const &) -> int { throw 0; }, [](std::string_view &&) -> int { throw 0; }, [](std::string_view const &&) -> int { throw 0; }); - constexpr fn::expected> a{std::unexpect, fn::sum{42}}; + constexpr fn::expected> a{::pfn::unexpect, fn::sum{42}}; static_assert(std::is_same_v>>); static_assert(a.transform_error(fn).error() == fn::sum{42}); } @@ -2583,7 +2583,7 @@ TEST_CASE("expected polyfills and_then", "[expected][polyfill][and_then]") WHEN("error") { - fn::expected s{std::unexpect, Unknown}; + fn::expected s{::pfn::unexpect, Unknown}; CHECK(s.and_then( // [](auto) -> fn::expected { throw 0; }) .error() @@ -2616,7 +2616,7 @@ TEST_CASE("expected polyfills and_then", "[expected][polyfill][and_then]") WHEN("error") { - fn::expected s{std::unexpect, Unknown}; + fn::expected s{::pfn::unexpect, Unknown}; CHECK(s.and_then([]() -> fn::expected { throw 0; }).error() == Unknown); CHECK(std::as_const(s).and_then([]() -> fn::expected { throw 0; }).error() == Unknown); CHECK(std::move(std::as_const(s)).and_then([]() -> fn::expected { throw 0; }).error() == Unknown); @@ -2637,7 +2637,7 @@ TEST_CASE("expected polyfills or_else", "[expected][polyfill][or_else][pack]") WHEN("error") { - fn::expected s{std::unexpect, FileNotFound}; + fn::expected s{::pfn::unexpect, FileNotFound}; CHECK(s.or_else( // fn::overload([](Error &e) -> fn::expected { return e == FileNotFound; }, [](Error const &) -> fn::expected { throw 0; }, @@ -2669,7 +2669,7 @@ TEST_CASE("expected polyfills or_else", "[expected][polyfill][or_else][pack]") WHEN("pack error") { - fn::expected> s{std::unexpect, fn::pack{12, FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; CHECK(s.or_else( // fn::overload([](int, Error &e) -> fn::expected { return e == FileNotFound; }, [](int, Error const &) -> fn::expected { throw 0; }, @@ -2710,7 +2710,7 @@ TEST_CASE("expected polyfills or_else", "[expected][polyfill][or_else][pack]") WHEN("error") { - fn::expected s{std::unexpect, FileNotFound}; + fn::expected s{::pfn::unexpect, FileNotFound}; CHECK(s.or_else( // fn::overload([](Error &) -> fn::expected { return {}; }, [](Error const &) -> fn::expected { throw 0; }, @@ -2742,7 +2742,7 @@ TEST_CASE("expected polyfills or_else", "[expected][polyfill][or_else][pack]") WHEN("pack error") { - fn::expected> s{std::unexpect, fn::pack{12, FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; CHECK(s.or_else( // fn::overload([](int, Error &) -> fn::expected { return {}; }, [](int, Error const &) -> fn::expected { throw 0; }, @@ -2825,7 +2825,7 @@ TEST_CASE("expected polyfills transform", "[expected][polyfill][transform]") WHEN("error") { - fn::expected s{std::unexpect, Unknown}; + fn::expected s{::pfn::unexpect, Unknown}; CHECK(s.transform([](auto) -> bool { throw 0; }).error() == Unknown); CHECK(std::as_const(s).transform([](auto) -> bool { throw 0; }).error() == Unknown); CHECK(std::move(std::as_const(s)).transform([](auto) -> bool { throw 0; }).error() == Unknown); @@ -2852,7 +2852,7 @@ TEST_CASE("expected polyfills transform", "[expected][polyfill][transform]") WHEN("error") { - fn::expected s{std::unexpect, Unknown}; + fn::expected s{::pfn::unexpect, Unknown}; CHECK(s.transform([]() -> bool { throw 0; }).error() == Unknown); CHECK(std::as_const(s).transform([]() -> bool { throw 0; }).error() == Unknown); CHECK(std::move(std::as_const(s)).transform([]() -> bool { throw 0; }).error() == Unknown); @@ -2873,7 +2873,7 @@ TEST_CASE("expected polyfills transform_error", "[expected][polyfill][transform_ WHEN("error") { - fn::expected s{std::unexpect, FileNotFound}; + fn::expected s{::pfn::unexpect, FileNotFound}; CHECK( s.transform_error( // fn::overload([](Error &e) -> bool { return e == FileNotFound; }, [](Error const &) -> bool { throw 0; }, @@ -2901,7 +2901,7 @@ TEST_CASE("expected polyfills transform_error", "[expected][polyfill][transform_ WHEN("pack error") { - fn::expected> s{std::unexpect, fn::pack{12, FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; CHECK(s.transform_error( // fn::overload([](int, Error &e) -> bool { return e == FileNotFound; }, [](int, Error const &) -> bool { throw 0; }, [](int, Error &&) -> bool { throw 0; }, @@ -2939,7 +2939,7 @@ TEST_CASE("expected polyfills transform_error", "[expected][polyfill][transform_ WHEN("error") { - fn::expected s{std::unexpect, FileNotFound}; + fn::expected s{::pfn::unexpect, FileNotFound}; CHECK(s.transform_error( // fn::overload([](Error &) -> bool { return true; }, [](Error const &) -> bool { throw 0; }, [](Error &&) -> bool { throw 0; }, [](Error const &&) -> bool { throw 0; })) @@ -2963,7 +2963,7 @@ TEST_CASE("expected polyfills transform_error", "[expected][polyfill][transform_ WHEN("pack error") { - fn::expected> s{std::unexpect, fn::pack{12, FileNotFound}}; + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; CHECK(s.transform_error( // fn::overload([](int, Error &) -> bool { return true; }, [](int, Error const &) -> bool { throw 0; }, [](int, Error &&) -> bool { throw 0; }, [](int, Error const &&) -> bool { throw 0; })) diff --git a/tests/fn/fail.cpp b/tests/fn/fail.cpp index e5f7c783..f02be3ad 100644 --- a/tests/fn/fail.cpp +++ b/tests/fn/fail.cpp @@ -77,7 +77,7 @@ TEST_CASE("fail", "[fail][expected][expected_value][pack]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | fail(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -113,7 +113,7 @@ TEST_CASE("fail", "[fail][expected][expected_value][pack]") WHEN("operand is error") { constexpr auto wrong = [](auto...) -> Error { throw 0; }; - REQUIRE((operand_t{std::unexpect, Error{"Not good"}} | fail(wrong)).error().what == "Not good"); + REQUIRE((operand_t{::pfn::unexpect, Error{"Not good"}} | fail(wrong)).error().what == "Not good"); } } @@ -127,9 +127,9 @@ TEST_CASE("fail", "[fail][expected][expected_value][pack]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | fail(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | fail(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | fail(wrong)) .error() .what @@ -181,7 +181,7 @@ TEST_CASE("fail", "[fail][expected][expected_void]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | fail(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -202,9 +202,9 @@ TEST_CASE("fail", "[fail][expected][expected_void]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | fail(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | fail(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | fail(wrong)) .error() .what @@ -351,7 +351,7 @@ TEST_CASE("constexpr fail expected with sum", "[fail][constexpr][expected][sum]" static_assert(r2.error() == Error::SomethingElse); constexpr auto r3 = T{3} | fn::fail(fn); static_assert(r3.error() == Error::ThresholdExceeded); - constexpr auto r4 = T{std::unexpect, Error::Reserved} | fn::fail(fn); + constexpr auto r4 = T{::pfn::unexpect, Error::Reserved} | fn::fail(fn); static_assert(r4.error() == Error::Reserved); SUCCEED(); diff --git a/tests/fn/filter.cpp b/tests/fn/filter.cpp index ca0a4c54..84366379 100644 --- a/tests/fn/filter.cpp +++ b/tests/fn/filter.cpp @@ -86,7 +86,7 @@ TEST_CASE("filter", "[filter][expected][expected_value]") WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | filter(truePred, wrong)); static_assert(std::is_same_v); @@ -121,10 +121,10 @@ TEST_CASE("filter", "[filter][expected][expected_value]") WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | filter(truePred, wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | filter(truePred, wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | filter(truePred, wrong)) .error() .what @@ -230,13 +230,13 @@ TEST_CASE("filter member function", "[filter][expected][expected_value][member_f WHEN("operand is error") { - REQUIRE((operand_t{std::unexpect, Error{"Not good"}} | filter(predPack, errPack)).error().what == "Not good"); + REQUIRE((operand_t{::pfn::unexpect, Error{"Not good"}} | filter(predPack, errPack)).error().what == "Not good"); } } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | filter(predicate, wrong)); static_assert(std::is_same_v); @@ -273,10 +273,10 @@ TEST_CASE("filter member function", "[filter][expected][expected_value][member_f WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | filter(predicate, wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | filter(predicate, wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | filter(predicate, wrong)) .error() .what @@ -333,7 +333,7 @@ TEST_CASE("filter", "[filter][expected][expected_void]") WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | filter(truePred, wrong)); static_assert(std::is_same_v); @@ -368,10 +368,10 @@ TEST_CASE("filter", "[filter][expected][expected_void]") WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | filter(truePred, wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | filter(truePred, wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | filter(truePred, wrong)) .error() .what diff --git a/tests/fn/inspect.cpp b/tests/fn/inspect.cpp index 57dc6d09..d37e87a2 100644 --- a/tests/fn/inspect.cpp +++ b/tests/fn/inspect.cpp @@ -70,7 +70,7 @@ TEST_CASE("inspect expected", "[inspect][expected][expected_value][pack]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | inspect(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -108,7 +108,7 @@ TEST_CASE("inspect expected", "[inspect][expected][expected_value][pack]") WHEN("operand is error") { using operand_t = fn::expected, Error>; - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; constexpr auto wrong = [](auto...) { throw 0; }; using T = decltype(a | inspect(wrong)); static_assert(std::is_same_v); @@ -131,9 +131,9 @@ TEST_CASE("inspect expected", "[inspect][expected][expected_value][pack]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | inspect(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | inspect(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | inspect(wrong)) .error() .what @@ -180,7 +180,7 @@ TEST_CASE("inspect void expected", "[inspect][expected][expected_void]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | inspect(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -202,9 +202,9 @@ TEST_CASE("inspect void expected", "[inspect][expected][expected_void]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | inspect(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | inspect(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | inspect(wrong)) .error() .what @@ -383,7 +383,7 @@ TEST_CASE("constexpr inspect expected", "[inspect][constexpr][expected]") constexpr auto fn = [](int) constexpr noexcept -> void {}; constexpr auto r1 = T{0} | fn::inspect(fn); static_assert(r1.value() == 0); - constexpr auto r2 = T{std::unexpect, Error::SomethingElse} | fn::inspect(fn); + constexpr auto r2 = T{::pfn::unexpect, Error::SomethingElse} | fn::inspect(fn); static_assert(r2.error() == Error::SomethingElse); SUCCEED(); @@ -401,7 +401,7 @@ TEST_CASE("constexpr inspect expected with sum", "[inspect][constexpr][expected] }; constexpr auto r12 = T{0} | fn::inspect(fn2); static_assert(r12.value() == fn::sum{0}); - constexpr auto r2 = T{std::unexpect, Error::SomethingElse} | fn::inspect(fn1); + constexpr auto r2 = T{::pfn::unexpect, Error::SomethingElse} | fn::inspect(fn1); static_assert(r2.error() == Error::SomethingElse); SUCCEED(); diff --git a/tests/fn/inspect_error.cpp b/tests/fn/inspect_error.cpp index 82f19e7b..3efe78a5 100644 --- a/tests/fn/inspect_error.cpp +++ b/tests/fn/inspect_error.cpp @@ -62,7 +62,7 @@ TEST_CASE("inspect_error expected", "[inspect_error][expected]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | inspect_error(fnError)); static_assert(std::is_same_v); REQUIRE((a // @@ -74,7 +74,7 @@ TEST_CASE("inspect_error expected", "[inspect_error][expected]") } WHEN("calling member function") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | inspect_error(&Error::finalize)); static_assert(std::is_same_v); auto const before = Error::count; @@ -97,9 +97,9 @@ TEST_CASE("inspect_error expected", "[inspect_error][expected]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | inspect_error(fnError)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | inspect_error(fnError)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | inspect_error(fnError)) .error() .what @@ -108,10 +108,10 @@ TEST_CASE("inspect_error expected", "[inspect_error][expected]") } WHEN("calling member function") { - using T = decltype(operand_t{std::unexpect, "Not good"} | inspect_error(&Error::finalize)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | inspect_error(&Error::finalize)); static_assert(std::is_same_v); auto const before = Error::count; - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | inspect_error(&Error::finalize)) .error() .what @@ -183,7 +183,7 @@ TEST_CASE("constexpr inspect_error expected", "[inspect_error][constexpr][expect constexpr auto fn = [](Error) constexpr noexcept -> void {}; constexpr auto r1 = T{0} | fn::inspect_error(fn); static_assert(r1.value() == 0); - constexpr auto r2 = T{std::unexpect, Error::SomethingElse} | fn::inspect_error(fn); + constexpr auto r2 = T{::pfn::unexpect, Error::SomethingElse} | fn::inspect_error(fn); static_assert(r2.error() == Error::SomethingElse); SUCCEED(); @@ -196,9 +196,9 @@ TEST_CASE("constexpr inspect_error expected with sum", "[inspect_error][constexp constexpr auto fn = fn::overload{[](Error) constexpr noexcept -> void {}, [](bool) constexpr noexcept -> void {}}; constexpr auto r1 = T{0} | fn::inspect_error(fn); static_assert(r1.value() == 0); - constexpr auto r2 = T{std::unexpect, fn::sum{Error::SomethingElse}} | fn::inspect_error(fn); + constexpr auto r2 = T{::pfn::unexpect, fn::sum{Error::SomethingElse}} | fn::inspect_error(fn); static_assert(r2.error() == fn::sum{Error::SomethingElse}); - constexpr auto r3 = T{std::unexpect, fn::sum{false}} | fn::inspect_error(fn); + constexpr auto r3 = T{::pfn::unexpect, fn::sum{false}} | fn::inspect_error(fn); static_assert(r3.error() == fn::sum{false}); SUCCEED(); diff --git a/tests/fn/or_else.cpp b/tests/fn/or_else.cpp index 27798c40..8df8b555 100644 --- a/tests/fn/or_else.cpp +++ b/tests/fn/or_else.cpp @@ -45,9 +45,9 @@ TEST_CASE("or_else", "[or_else][expected][expected_value]") constexpr auto fnError = [](Error e) -> operand_t { return {e.what.size()}; }; constexpr auto fnXerror - = [](Error e) -> fn::expected { return std::unexpected{"Was: " + e.what}; }; + = [](Error e) -> fn::expected { return ::pfn::unexpected{"Was: " + e.what}; }; constexpr auto wrong = [](Error) -> operand_t { throw 0; }; - constexpr auto fnFail = [](Error v) -> operand_t { return std::unexpected("Got: " + v.what); }; + constexpr auto fnFail = [](Error v) -> operand_t { return ::pfn::unexpected("Got: " + v.what); }; static_assert(is::invocable_with_any(fnError)); static_assert(is::invocable_with_any(fnXerror)); @@ -90,7 +90,7 @@ TEST_CASE("or_else", "[or_else][expected][expected_value]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | or_else(fnError)); static_assert(std::is_same_v); REQUIRE((a | or_else(fnError)).value() == 8); @@ -111,7 +111,7 @@ TEST_CASE("or_else", "[or_else][expected][expected_value]") } WHEN("calling member function") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | or_else(&Error::fn)); static_assert(std::is_same_v); REQUIRE((a | or_else(&Error::fn)).value() == 8); @@ -128,15 +128,15 @@ TEST_CASE("or_else", "[or_else][expected][expected_value]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | or_else(fnError)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | or_else(fnError)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} | or_else(fnError)).value() == 8); + REQUIRE((operand_t{::pfn::unexpect, "Not good"} | or_else(fnError)).value() == 8); WHEN("fail") { - using T = decltype(operand_t{std::unexpect, "Not good"} | or_else(fnFail)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | or_else(fnFail)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | or_else(fnFail)) .error() .what @@ -145,9 +145,9 @@ TEST_CASE("or_else", "[or_else][expected][expected_value]") } WHEN("calling member function") { - using T = decltype(operand_t{std::unexpect, "Not good"} | or_else(&Error::fn)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | or_else(&Error::fn)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} | or_else(&Error::fn)).value() == 8); + REQUIRE((operand_t{::pfn::unexpect, "Not good"} | or_else(&Error::fn)).value() == 8); } } } @@ -166,9 +166,9 @@ TEST_CASE("or_else", "[or_else][expected][expected_void]") return {}; }; constexpr auto fnXerror - = [](Error e) -> fn::expected { return std::unexpected{"Was: " + e.what}; }; + = [](Error e) -> fn::expected { return ::pfn::unexpected{"Was: " + e.what}; }; constexpr auto wrong = [](Error) -> operand_t { throw 0; }; - constexpr auto fnFail = [](Error v) -> operand_t { return std::unexpected("Got: " + v.what); }; + constexpr auto fnFail = [](Error v) -> operand_t { return ::pfn::unexpected("Got: " + v.what); }; static_assert(is::invocable_with_any(fnError)); static_assert(is::invocable_with_any(fnXerror)); @@ -201,7 +201,7 @@ TEST_CASE("or_else", "[or_else][expected][expected_void]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | or_else(fnError)); static_assert(std::is_same_v); (a | or_else(fnError)).value(); @@ -224,7 +224,7 @@ TEST_CASE("or_else", "[or_else][expected][expected_void]") } WHEN("calling member function") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | or_else(&Error::finalize)); static_assert(std::is_same_v); auto const before = Error::count; @@ -244,15 +244,15 @@ TEST_CASE("or_else", "[or_else][expected][expected_void]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | or_else(fnError)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | or_else(fnError)); static_assert(std::is_same_v); - (operand_t{std::unexpect, "Not good"} | or_else(fnError)).value(); + (operand_t{::pfn::unexpect, "Not good"} | or_else(fnError)).value(); WHEN("fail") { - using T = decltype(operand_t{std::unexpect, "Not good"} | or_else(fnFail)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | or_else(fnFail)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | or_else(fnFail)) .error() .what @@ -261,10 +261,10 @@ TEST_CASE("or_else", "[or_else][expected][expected_void]") } WHEN("calling member function") { - using T = decltype(operand_t{std::unexpect, "Not good"} | or_else(&Error::finalize)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | or_else(&Error::finalize)); static_assert(std::is_same_v); auto const before = Error::count; - (operand_t{std::unexpect, "Not good"} | or_else(&Error::finalize)).value(); + (operand_t{::pfn::unexpect, "Not good"} | or_else(&Error::finalize)).value(); CHECK(Error::count == before + 8); } } @@ -352,13 +352,13 @@ TEST_CASE("constexpr or_else expected", "[or_else][constexpr][expected]") constexpr auto fn = [](Error e) constexpr noexcept -> T { if (e == Error::SomethingElse) return {0}; - return std::unexpected{e}; + return ::pfn::unexpected{e}; }; constexpr auto r1 = T{0} | fn::or_else(fn); static_assert(r1.value() == 0); - constexpr auto r2 = T{std::unexpect, Error::SomethingElse} | fn::or_else(fn); + constexpr auto r2 = T{::pfn::unexpect, Error::SomethingElse} | fn::or_else(fn); static_assert(r2.value() == 0); - constexpr auto r3 = T{std::unexpect, Error::ThresholdExceeded} | fn::or_else(fn); + constexpr auto r3 = T{::pfn::unexpect, Error::ThresholdExceeded} | fn::or_else(fn); static_assert(r3.error() == Error::ThresholdExceeded); } @@ -372,12 +372,12 @@ TEST_CASE("constexpr or_else expected", "[or_else][constexpr][expected]") constexpr auto fn = [](Error e) constexpr noexcept -> T1 { if (e == Error::SomethingElse) return {true}; - return T1{std::unexpect}; + return T1{::pfn::unexpect}; }; - constexpr auto r1 = T{std::unexpect, Error::SomethingElse} | fn::or_else(fn); + constexpr auto r1 = T{::pfn::unexpect, Error::SomethingElse} | fn::or_else(fn); static_assert(std::is_same_v const>); static_assert(r1.value() == true); - constexpr auto r2 = T{std::unexpect, Error::ThresholdExceeded} | fn::or_else(fn); + constexpr auto r2 = T{::pfn::unexpect, Error::ThresholdExceeded} | fn::or_else(fn); static_assert(r2.error() == UnrecoverableError{}); } @@ -394,12 +394,12 @@ TEST_CASE("constexpr or_else expected with sum", "[or_else][constexpr][expected] constexpr auto fn = fn::overload{[](int i) constexpr noexcept -> T { if (i < 3) return {i + 1}; - return std::unexpected>{Error::ThresholdExceeded}; + return ::pfn::unexpected>{Error::ThresholdExceeded}; }, [](Error v) constexpr noexcept -> T { return {static_cast(v)}; }}; - constexpr auto r1 = T{std::unexpect, 0} | fn::or_else(fn); + constexpr auto r1 = T{::pfn::unexpect, 0} | fn::or_else(fn); static_assert(r1.value() == 1); - constexpr auto r2 = T{std::unexpect, 3} | fn::or_else(fn); + constexpr auto r2 = T{::pfn::unexpect, 3} | fn::or_else(fn); static_assert(r2.error() == fn::sum{Error::ThresholdExceeded}); } @@ -410,15 +410,15 @@ TEST_CASE("constexpr or_else expected with sum", "[or_else][constexpr][expected] = fn::overload{[](int i) constexpr noexcept -> T1 { if (i < 2) return {i + 1}; - return std::unexpected{Error::SomethingElse}; + return ::pfn::unexpected{Error::SomethingElse}; }, - [](Error) constexpr noexcept -> T1 { return std::unexpected{Error::UnexpectedType}; }}; - constexpr auto r1 = T{std::unexpect, 1} | fn::or_else(fn); + [](Error) constexpr noexcept -> T1 { return ::pfn::unexpected{Error::UnexpectedType}; }}; + constexpr auto r1 = T{::pfn::unexpect, 1} | fn::or_else(fn); static_assert(std::is_same_v const>); static_assert(r1.value() == 2); - constexpr auto r2 = T{std::unexpect, 2} | fn::or_else(fn); + constexpr auto r2 = T{::pfn::unexpect, 2} | fn::or_else(fn); static_assert(r2.error() == Error::SomethingElse); - constexpr auto r3 = T{std::unexpect, Error::ThresholdExceeded} | fn::or_else(fn); + constexpr auto r3 = T{::pfn::unexpect, Error::ThresholdExceeded} | fn::or_else(fn); static_assert(r3.error() == Error::UnexpectedType); } @@ -435,15 +435,15 @@ TEST_CASE("constexpr or_else graded monad", "[or_else][constexpr][expected][grad constexpr auto fn1 = [](Error i) -> fn::expected { if (i == Error::Unknown) return {0}; - return std::unexpected{(int)i}; + return ::pfn::unexpected{(int)i}; }; constexpr auto r1 = T{14} | fn::or_else(fn1); static_assert(std::is_same_v, int> const>); static_assert(r1.value() == fn::sum{14}); - constexpr auto r2 = T{std::unexpect, Error::InvalidValue} | fn::or_else(fn1); + constexpr auto r2 = T{::pfn::unexpect, Error::InvalidValue} | fn::or_else(fn1); static_assert(r2.error() == 1); - constexpr auto r3 = T{std::unexpect, Error::Unknown} | fn::or_else(fn1); + constexpr auto r3 = T{::pfn::unexpect, Error::Unknown} | fn::or_else(fn1); static_assert(r3.value() == fn::sum{0}); } diff --git a/tests/fn/recover.cpp b/tests/fn/recover.cpp index 6b93a0e8..8c717e96 100644 --- a/tests/fn/recover.cpp +++ b/tests/fn/recover.cpp @@ -66,7 +66,7 @@ TEST_CASE("recover", "[recover][expected][expected_value]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | recover(fnError)); static_assert(std::is_same_v); REQUIRE((a | recover(fnError)).value() == 8); @@ -83,9 +83,9 @@ TEST_CASE("recover", "[recover][expected][expected_value]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | recover(fnError)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | recover(fnError)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} | recover(fnError)).value() == 8); + REQUIRE((operand_t{::pfn::unexpect, "Not good"} | recover(fnError)).value() == 8); } } } @@ -128,7 +128,7 @@ TEST_CASE("recover", "[recover][expected][expected_void]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | recover(fnError)); static_assert(std::is_same_v); (a | recover(fnError)).value(); @@ -147,9 +147,9 @@ TEST_CASE("recover", "[recover][expected][expected_void]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | recover(fnError)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | recover(fnError)); static_assert(std::is_same_v); - (operand_t{std::unexpect, "Not good"} | recover(fnError)).value(); + (operand_t{::pfn::unexpect, "Not good"} | recover(fnError)).value(); REQUIRE(count == 1); } } @@ -223,9 +223,9 @@ TEST_CASE("constexpr recover expected", "[recover][constexpr][expected]") }; constexpr auto r1 = T{2} | fn::recover(fn); static_assert(r1.value() == 2); - constexpr auto r2 = T{std::unexpect, Error::SomethingElse} | fn::recover(fn); + constexpr auto r2 = T{::pfn::unexpect, Error::SomethingElse} | fn::recover(fn); static_assert(r2.value() == 0); - constexpr auto r3 = T{std::unexpect, Error::ThresholdExceeded} | fn::recover(fn); + constexpr auto r3 = T{::pfn::unexpect, Error::ThresholdExceeded} | fn::recover(fn); static_assert(r3.value() == 1); SUCCEED(); @@ -244,11 +244,11 @@ TEST_CASE("constexpr recover expected with sum", "[recover][constexpr][expected] [](bool e) constexpr noexcept -> int { return (int)e; }}; constexpr auto r1 = T{2} | fn::recover(fn); static_assert(r1.value() == 2); - constexpr auto r2 = T{std::unexpect, fn::sum{Error::SomethingElse}} | fn::recover(fn); + constexpr auto r2 = T{::pfn::unexpect, fn::sum{Error::SomethingElse}} | fn::recover(fn); static_assert(r2.value() == 0); - constexpr auto r3 = T{std::unexpect, fn::sum{true}} | fn::recover(fn); + constexpr auto r3 = T{::pfn::unexpect, fn::sum{true}} | fn::recover(fn); static_assert(r3.value() == 1); - constexpr auto r4 = T{std::unexpect, fn::sum{Error::ThresholdExceeded}} | fn::recover(fn); + constexpr auto r4 = T{::pfn::unexpect, fn::sum{Error::ThresholdExceeded}} | fn::recover(fn); static_assert(r4.value() == 1); SUCCEED(); diff --git a/tests/fn/transform.cpp b/tests/fn/transform.cpp index 4cb2ba42..cdb48b8e 100644 --- a/tests/fn/transform.cpp +++ b/tests/fn/transform.cpp @@ -69,7 +69,7 @@ TEST_CASE("transform", "[transform][expected][expected_value][pack]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | transform(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -92,7 +92,7 @@ TEST_CASE("transform", "[transform][expected][expected_value][pack]") WHEN("operand is error") { constexpr auto wrong = [](auto...) -> int { throw 0; }; - REQUIRE((operand_t{std::unexpect, Error{"Not good"}} | transform(wrong)).error().what == "Not good"); + REQUIRE((operand_t{::pfn::unexpect, Error{"Not good"}} | transform(wrong)).error().what == "Not good"); } } @@ -113,9 +113,9 @@ TEST_CASE("transform", "[transform][expected][expected_value][pack]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | transform(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | transform(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | transform(wrong)) .error() .what @@ -161,7 +161,7 @@ TEST_CASE("transform", "[transform][expected][expected_void]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | transform(wrong)); static_assert(std::is_same_v); REQUIRE((a // @@ -190,9 +190,9 @@ TEST_CASE("transform", "[transform][expected][expected_void]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | transform(wrong)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | transform(wrong)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | transform(wrong)) .error() .what @@ -381,7 +381,7 @@ TEST_CASE("constexpr transform expected", "[transform][constexpr][expected]") static_assert(r1.value() == 1); constexpr auto r2 = r1 | fn::transform(fn) | fn::transform(fn) | fn::transform(fn); static_assert(r2.value() == 2); - constexpr auto r3 = T{std::unexpect, Error::SomethingElse} | fn::transform(fn); + constexpr auto r3 = T{::pfn::unexpect, Error::SomethingElse} | fn::transform(fn); static_assert(r3.error() == Error::SomethingElse); } @@ -395,7 +395,7 @@ TEST_CASE("constexpr transform expected", "[transform][constexpr][expected]") static_assert(r2.value() == false); constexpr auto r3 = T{2} | fn::transform(fn); static_assert(r3.value() == false); - constexpr auto r4 = T{std::unexpect, Error::SomethingElse} | fn::transform(fn); + constexpr auto r4 = T{::pfn::unexpect, Error::SomethingElse} | fn::transform(fn); static_assert(r4.error() == Error::SomethingElse); } @@ -422,7 +422,7 @@ TEST_CASE("constexpr transform expected with sum", "[transform][constexpr][expec static_assert(r2.value() == fn::sum{3}); constexpr auto r3 = T{Xint{4}} | fn::transform(fn); static_assert(r3.value() == fn::sum{4}); - constexpr auto r4 = T{std::unexpect, Error::SomethingElse} | fn::transform(fn); + constexpr auto r4 = T{::pfn::unexpect, Error::SomethingElse} | fn::transform(fn); static_assert(r4.error() == Error::SomethingElse); } @@ -437,7 +437,7 @@ TEST_CASE("constexpr transform expected with sum", "[transform][constexpr][expec static_assert(r2.value() == fn::sum{false}); constexpr auto r3 = T{Xint{3}} | fn::transform(fn); static_assert(r3.value() == fn::sum{3}); - constexpr auto r4 = T{std::unexpect, Error::SomethingElse} | fn::transform(fn); + constexpr auto r4 = T{::pfn::unexpect, Error::SomethingElse} | fn::transform(fn); static_assert(r4.error() == Error::SomethingElse); } diff --git a/tests/fn/transform_error.cpp b/tests/fn/transform_error.cpp index 55c69056..7f8c4295 100644 --- a/tests/fn/transform_error.cpp +++ b/tests/fn/transform_error.cpp @@ -64,7 +64,7 @@ TEST_CASE("transform_error", "[transform_error][expected]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | transform_error(fnError)); static_assert(std::is_same_v); REQUIRE((a // @@ -92,9 +92,9 @@ TEST_CASE("transform_error", "[transform_error][expected]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | transform_error(fnError)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | transform_error(fnError)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} // + REQUIRE((operand_t{::pfn::unexpect, "Not good"} // | transform_error(fnError)) .error() .what @@ -102,9 +102,9 @@ TEST_CASE("transform_error", "[transform_error][expected]") WHEN("change type") { - using T = decltype(operand_t{std::unexpect, "Not good"} | transform_error(fnXerror)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | transform_error(fnXerror)); static_assert(std::is_same_v>); - REQUIRE((operand_t{std::unexpect, "Not good"} | transform_error(fnXerror)).error().value == 8); + REQUIRE((operand_t{::pfn::unexpect, "Not good"} | transform_error(fnXerror)).error().value == 8); } } } @@ -136,11 +136,11 @@ TEST_CASE("constexpr transform_error expected", "[transform_error][constexpr][ex }; constexpr auto r1 = T{0} | fn::transform_error(fn); static_assert(r1.value() == 0); - constexpr auto r2 = T{std::unexpect, Error::ThresholdExceeded} | fn::transform_error(fn); + constexpr auto r2 = T{::pfn::unexpect, Error::ThresholdExceeded} | fn::transform_error(fn); static_assert(r2.error() == Error::ThresholdExceeded); - constexpr auto r3 = T{std::unexpect, Error::SomethingElse} | fn::transform_error(fn); + constexpr auto r3 = T{::pfn::unexpect, Error::SomethingElse} | fn::transform_error(fn); static_assert(r3.error() == Error::SomethingElse); - constexpr auto r4 = T{std::unexpect, Error::Unknown} | fn::transform_error(fn); + constexpr auto r4 = T{::pfn::unexpect, Error::Unknown} | fn::transform_error(fn); static_assert(r4.error() == Error::SomethingElse); } @@ -154,9 +154,9 @@ TEST_CASE("constexpr transform_error expected", "[transform_error][constexpr][ex constexpr auto r1 = T{0} | fn::transform_error(fn); static_assert(std::is_same_v const>); static_assert(r1.value() == 0); - constexpr auto r2 = T{std::unexpect, Error::ThresholdExceeded} | fn::transform_error(fn); + constexpr auto r2 = T{::pfn::unexpect, Error::ThresholdExceeded} | fn::transform_error(fn); static_assert(r2.error() == UnrecoverableError{}); - constexpr auto r3 = T{std::unexpect, Error::SomethingElse} | fn::transform_error(fn); + constexpr auto r3 = T{::pfn::unexpect, Error::SomethingElse} | fn::transform_error(fn); static_assert(r3.error() == UnrecoverableError{}); } @@ -172,10 +172,10 @@ TEST_CASE("constexpr transform_error expected with sum", "[transform_error][cons { constexpr auto fn = fn::overload{[](bool i) constexpr noexcept -> fn::sum { return not i; }, [](Error v) constexpr noexcept -> fn::sum { return v; }}; - constexpr auto r1 = T{std::unexpect, fn::sum{Error::SomethingElse}} | fn::transform_error(fn); + constexpr auto r1 = T{::pfn::unexpect, fn::sum{Error::SomethingElse}} | fn::transform_error(fn); static_assert(std::is_same_v> const>); static_assert(r1.error() == fn::sum{Error::SomethingElse}); - constexpr auto r2 = T{std::unexpect, fn::sum{true}} | fn::transform_error(fn); + constexpr auto r2 = T{::pfn::unexpect, fn::sum{true}} | fn::transform_error(fn); static_assert(r2.error() == fn::sum{false}); constexpr auto r3 = T{42} | fn::transform_error(fn); static_assert(r3.value() == 42); @@ -185,10 +185,10 @@ TEST_CASE("constexpr transform_error expected with sum", "[transform_error][cons { constexpr auto fn = fn::overload{[](bool i) constexpr noexcept -> bool { return not i; }, [](Error v) constexpr noexcept -> int { return static_cast(v) + 1; }}; - constexpr auto r1 = T{std::unexpect, fn::sum{Error::SomethingElse}} | fn::transform_error(fn); + constexpr auto r1 = T{::pfn::unexpect, fn::sum{Error::SomethingElse}} | fn::transform_error(fn); static_assert(std::is_same_v> const>); static_assert(r1.error() == fn::sum{2}); - constexpr auto r2 = T{std::unexpect, fn::sum{true}} | fn::transform_error(fn); + constexpr auto r2 = T{::pfn::unexpect, fn::sum{true}} | fn::transform_error(fn); static_assert(r2.error() == fn::sum{false}); constexpr auto r3 = T{42} | fn::transform_error(fn); static_assert(r3.value() == 42); diff --git a/tests/fn/value_or.cpp b/tests/fn/value_or.cpp index 421f793d..930394b2 100644 --- a/tests/fn/value_or.cpp +++ b/tests/fn/value_or.cpp @@ -35,7 +35,7 @@ TEST_CASE("value_or", "[value_or][expected][expected_value]") } WHEN("operand is error") { - operand_t a{std::unexpect, "Not good"}; + operand_t a{::pfn::unexpect, "Not good"}; using T = decltype(a | value_or(3)); static_assert(std::is_same_v); REQUIRE((a | value_or(3)).value() == 3); @@ -52,9 +52,9 @@ TEST_CASE("value_or", "[value_or][expected][expected_value]") } WHEN("operand is error") { - using T = decltype(operand_t{std::unexpect, "Not good"} | value_or(3)); + using T = decltype(operand_t{::pfn::unexpect, "Not good"} | value_or(3)); static_assert(std::is_same_v); - REQUIRE((operand_t{std::unexpect, "Not good"} | value_or(3)).value() == 3); + REQUIRE((operand_t{::pfn::unexpect, "Not good"} | value_or(3)).value() == 3); } } } @@ -107,7 +107,7 @@ TEST_CASE("constexpr value_or expected", "[value_or][constexpr][expected]") constexpr auto r1 = T{2} | fn::value_or(3); static_assert(r1.value() == 2); - constexpr auto r2 = T{std::unexpect, Error::SomethingElse} | fn::value_or(3); + constexpr auto r2 = T{::pfn::unexpect, Error::SomethingElse} | fn::value_or(3); static_assert(r2.value() == 3); SUCCEED(); From c46d1f991c56ad9d867e136051e12cbc34c7a32f Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 22 May 2026 20:06:10 +0200 Subject: [PATCH 10/21] Formatting fixes --- examples/simple/main.cpp | 2 +- tests/fn/expected.cpp | 69 +++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/examples/simple/main.cpp b/examples/simple/main.cpp index 57554b40..a3d8ae4c 100644 --- a/examples/simple/main.cpp +++ b/examples/simple/main.cpp @@ -235,7 +235,7 @@ TEST_CASE("Demo expected", "[expected][pack][and_then][discard][transform_error] | fn::inspect_error([](Error) noexcept { CHECK(false); }) // | fn::discard(); - fn::expected{::pfn::unexpected{"discarded"}} // + fn::expected{::pfn::unexpected{"discarded"}} // | fn::inspect([](int) noexcept { CHECK(false); }) // | fn::inspect_error([](Error e) noexcept { REQUIRE(e.what == "discarded"); }) // | fn::discard(); diff --git a/tests/fn/expected.cpp b/tests/fn/expected.cpp index aac94b6f..7ccff426 100644 --- a/tests/fn/expected.cpp +++ b/tests/fn/expected.cpp @@ -455,7 +455,8 @@ TEST_CASE("graded monad", "[expected][sum][graded][and_then][or_else][sum_value] WHEN("error to error") { - constexpr auto fn = [](Error) -> fn::expected { return ::pfn::unexpected("Boo"); }; + constexpr auto fn + = [](Error) -> fn::expected { return ::pfn::unexpected("Boo"); }; static_assert(std::is_same_v, std::string>>); CHECK(s.or_else(fn).error() == "Boo"); CHECK(std::as_const(s).or_else(fn).error() == "Boo"); @@ -1366,7 +1367,8 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat == fn::sum{true}); CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() + == fn::sum{FileNotFound}); } } @@ -1405,7 +1407,8 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat == fn::sum{true}); CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{12}).error() == fn::sum{FileNotFound}); CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() + == fn::sum{FileNotFound}); } } @@ -1444,7 +1447,8 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat == fn::sum{true}); CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); CHECK((Lh{fn::pack{0.5, 3}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{13}); - CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, fn::sum{FileNotFound}} & Rh{::pfn::unexpect, 13}).error() + == fn::sum{FileNotFound}); } } } @@ -1675,7 +1679,8 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat == fn::sum{true}); CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() + == fn::sum{FileNotFound}); } } @@ -1714,7 +1719,8 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat == fn::sum{true}); CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{12}).error() == fn::sum{FileNotFound}); CHECK((Lh{fn::sum{fn::pack{0.5, 3}}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() + == fn::sum{FileNotFound}); } } @@ -1753,7 +1759,8 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat == fn::sum{true}); CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{fn::sum{12}}).error() == fn::sum{FileNotFound}); CHECK((Lh{fn::pack{0.5, 3}} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{13}); - CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() == fn::sum{FileNotFound}); + CHECK((Lh{::pfn::unexpect, FileNotFound} & Rh{::pfn::unexpect, fn::sum{13}}).error() + == fn::sum{FileNotFound}); } } } @@ -1853,8 +1860,9 @@ TEST_CASE("expected pack support", "[expected][pack][and_then][transform][operat CHECK((Lh{std::in_place, fn::pack{0.5, true}} & Rh{}) .transform([](double d, bool b) constexpr -> bool { return d == 0.5 && b; }) .value()); - CHECK((Lh{std::in_place, fn::pack{0.5, true}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() - == fn::sum{FileNotFound}); + CHECK( + (Lh{std::in_place, fn::pack{0.5, true}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() + == fn::sum{FileNotFound}); CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{}).error() == fn::sum{13}); CHECK((Lh{::pfn::unexpect, fn::sum{13}} & Rh{::pfn::unexpect, fn::sum{FileNotFound}}).error() == fn::sum{13}); } @@ -2254,18 +2262,19 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") .error() == FileNotFound); - CHECK(std::move(s) - .or_else( // - fn::overload([](int &) -> fn::expected { throw 0; }, - [](int const &) -> fn::expected { throw 0; }, - [](int &&) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, - [](int const &&) -> fn::expected { throw 0; }, - [](std::string_view &) -> fn::expected { throw 0; }, - [](std::string_view const &) -> fn::expected { throw 0; }, - [](std::string_view &&) -> fn::expected { throw 0; }, - [](std::string_view const &&) -> fn::expected { throw 0; })) - .error() - == FileNotFound); + CHECK( + std::move(s) + .or_else( // + fn::overload([](int &) -> fn::expected { throw 0; }, + [](int const &) -> fn::expected { throw 0; }, + [](int &&) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, + [](int const &&) -> fn::expected { throw 0; }, + [](std::string_view &) -> fn::expected { throw 0; }, + [](std::string_view const &) -> fn::expected { throw 0; }, + [](std::string_view &&) -> fn::expected { throw 0; }, + [](std::string_view const &&) -> fn::expected { throw 0; })) + .error() + == FileNotFound); WHEN("value") { @@ -2289,15 +2298,15 @@ TEST_CASE("expected sum support or_else", "[expected][sum][or_else]") WHEN("constexpr") { - constexpr auto fn - = fn::overload([](int &) -> fn::expected { throw 0; }, - [](int const &) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, - [](int &&) -> fn::expected { throw 0; }, - [](int const &&) -> fn::expected { throw 0; }, - [](std::string_view &) -> fn::expected { throw 0; }, - [](std::string_view const &) -> fn::expected { throw 0; }, - [](std::string_view &&) -> fn::expected { throw 0; }, - [](std::string_view const &&) -> fn::expected { throw 0; }); + constexpr auto fn = fn::overload( + [](int &) -> fn::expected { throw 0; }, + [](int const &) -> fn::expected { return ::pfn::unexpected{FileNotFound}; }, + [](int &&) -> fn::expected { throw 0; }, + [](int const &&) -> fn::expected { throw 0; }, + [](std::string_view &) -> fn::expected { throw 0; }, + [](std::string_view const &) -> fn::expected { throw 0; }, + [](std::string_view &&) -> fn::expected { throw 0; }, + [](std::string_view const &&) -> fn::expected { throw 0; }); constexpr fn::expected> a{::pfn::unexpect, fn::sum{42}}; static_assert(std::is_same_v>); static_assert(a.or_else(fn).error() == FileNotFound); From 1f3c02e6634069d8cc95ef39bdaff385e36604e7 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Sat, 23 May 2026 17:53:01 +0200 Subject: [PATCH 11/21] Draft refactor of fn::expected --- include/fn/expected.hpp | 1353 ++++++++++++++++++++------------------- 1 file changed, 694 insertions(+), 659 deletions(-) diff --git a/include/fn/expected.hpp b/include/fn/expected.hpp index 479e69ad..6f392759 100644 --- a/include/fn/expected.hpp +++ b/include/fn/expected.hpp @@ -6,12 +6,12 @@ #ifndef INCLUDE_FN_EXPECTED #define INCLUDE_FN_EXPECTED -#include "pfn/expected.hpp" +#include +#include #include #include -#include #include #include @@ -30,15 +30,521 @@ concept some_expected_void = // some_expected // && std::is_same_v::value_type>; -template struct expected final : ::pfn::expected { - using value_type = ::pfn::expected::value_type; - using error_type = ::pfn::expected::error_type; +namespace detail { + +struct expected_policy { + template using type = ::fn::expected; + template static constexpr bool is_specialization = _is_some_expected; +}; + +// Storage layer for ::fn::expected. Inherits the standard-conformant base from +// pfn, then hides the four monadic static helpers with sum-widening variants +// that materialise their result via `expected_policy::template type`. +template struct _storage : ::pfn::detail::_storage { + using _pfn_base = ::pfn::detail::_storage; + using _pfn_base::_pfn_base; + + // and_then, non-void value type + template + static constexpr auto _and_then(Self &&self, Fn &&fn) + requires(not ::std::is_void_v) && ::fn::detail::_is_invocable::value + && ::std::is_constructible_v + { + using type = typename ::fn::detail::_invoke_result::type; + static_assert(_is_some_expected); + static_assert(::std::is_same_v || some_sum); + if constexpr (::std::is_same_v) { + if (self.has_value()) + return ::fn::detail::_invoke(FWD(fn), _pfn_base::_value(FWD(self))); + else + return type(::pfn::unexpect, _pfn_base::_error(FWD(self))); + } else { + using new_error_type = sum_for; + using new_type = ::fn::expected; + if (self.has_value()) { + auto t = ::fn::detail::_invoke(FWD(fn), _pfn_base::_value(FWD(self))); + if (t.has_value()) + if constexpr (not ::std::is_void_v) + return new_type{::std::in_place, ::std::move(t).value()}; + else + return new_type{::std::in_place}; + else + return new_type{::pfn::unexpect, ::std::move(t).error()}; + } else { + if constexpr (not ::std::is_same_v>) + return new_type(::pfn::unexpect, _pfn_base::_error(FWD(self))); + else + ::std::unreachable(); + } + } + } + + // and_then, void value type + template + static constexpr auto _and_then(Self &&self, Fn &&fn) + requires(::std::is_void_v) && ::fn::detail::_is_invocable::value + && ::std::is_constructible_v + { + using type = typename ::fn::detail::_invoke_result::type; + static_assert(_is_some_expected); + static_assert(::std::is_same_v || some_sum); + if constexpr (::std::is_same_v) { + if (self.has_value()) + return ::fn::detail::_invoke(FWD(fn)); + else + return type(::pfn::unexpect, _pfn_base::_error(FWD(self))); + } else { + using new_error_type = sum_for; + using new_type = ::fn::expected; + if (self.has_value()) { + auto t = ::fn::detail::_invoke(FWD(fn)); + if (t.has_value()) + if constexpr (not ::std::is_void_v) + return new_type{::std::in_place, ::std::move(t).value()}; + else + return new_type{::std::in_place}; + else + return new_type{::pfn::unexpect, ::std::move(t).error()}; + } else { + if constexpr (not ::std::is_same_v>) + return new_type(::pfn::unexpect, _pfn_base::_error(FWD(self))); + else + ::std::unreachable(); + } + } + } + + // or_else, covers both void and non-void value type + template + static constexpr auto _or_else(Self &&self, Fn &&fn) + requires ::fn::detail::_is_invocable::value + && (::std::is_void_v || ::std::is_constructible_v) + { + using type = typename ::fn::detail::_invoke_result::type; + static_assert(_is_some_expected); + static_assert(::std::is_same_v || some_sum); + if constexpr (::std::is_same_v) { + if (self.has_value()) + if constexpr (not ::std::is_void_v) + return type(::std::in_place, _pfn_base::_value(FWD(self))); + else + return type(); + else + return ::fn::detail::_invoke(FWD(fn), _pfn_base::_error(FWD(self))); + } else { + static_assert(not ::std::is_void_v); + using new_value_type = sum_for; + using new_type = ::fn::expected; + if (self.has_value()) + return new_type{::std::in_place, _pfn_base::_value(FWD(self))}; + else { + auto t = ::fn::detail::_invoke(FWD(fn), _pfn_base::_error(FWD(self))); + if (t.has_value()) + return new_type{::std::in_place, ::std::move(t).value()}; + else + return new_type{::pfn::unexpect, ::std::move(t).error()}; + } + } + } + + // transform, non-void value type, not a sum + template + static constexpr auto _transform(Self &&self, Fn &&fn) + requires(not ::std::is_void_v) && (not some_sum) + && ::fn::detail::_is_invocable::value + && ::std::is_constructible_v + { + using new_value_type = typename ::fn::detail::_invoke_result::type; + using type = ::fn::expected; + if (self.has_value()) + if constexpr (::std::is_void_v) { + ::fn::detail::_invoke(FWD(fn), _pfn_base::_value(FWD(self))); + return type(); + } else + return type(::std::in_place, ::fn::detail::_invoke(FWD(fn), _pfn_base::_value(FWD(self)))); + else + return type(::pfn::unexpect, _pfn_base::_error(FWD(self))); + } + + // transform, value type is a sum (delegates to sum::transform) + template + static constexpr auto _transform(Self &&self, Fn &&fn) + requires some_sum + { + using new_value_type = decltype(_pfn_base::_value(FWD(self)).transform(FWD(fn))); + using type = ::fn::expected; + if (self.has_value()) + if constexpr (::std::is_void_v) { + _pfn_base::_value(FWD(self)).transform(FWD(fn)); + return type(); + } else + return type(::std::in_place, _pfn_base::_value(FWD(self)).transform(FWD(fn))); + else + return type(::pfn::unexpect, _pfn_base::_error(FWD(self))); + } + + // transform, void value type + template + static constexpr auto _transform(Self &&self, Fn &&fn) + requires(::std::is_void_v) && ::fn::detail::_is_invocable::value + { + using new_value_type = typename ::fn::detail::_invoke_result::type; + using type = ::fn::expected; + if (self.has_value()) + if constexpr (::std::is_void_v) { + ::fn::detail::_invoke(FWD(fn)); + return type(); + } else + return type(::std::in_place, ::fn::detail::_invoke(FWD(fn))); + else + return type(::pfn::unexpect, _pfn_base::_error(FWD(self))); + } + + // transform_error, error type is not a sum + template + static constexpr auto _transform_error(Self &&self, Fn &&fn) + requires(not some_sum) && ::fn::detail::_is_invocable::value + { + using new_error_type = typename ::fn::detail::_invoke_result::type; + using type = ::fn::expected; + if (self.has_value()) + if constexpr (not ::std::is_void_v) + return type(::std::in_place, _pfn_base::_value(FWD(self))); + else + return type(); + else + return type(::pfn::unexpect, ::fn::detail::_invoke(FWD(fn), _pfn_base::_error(FWD(self)))); + } + + // transform_error, error type is a sum (delegates to sum::transform) + template + static constexpr auto _transform_error(Self &&self, Fn &&fn) + requires some_sum + { + using new_error_type = decltype(_pfn_base::_error(FWD(self)).transform(FWD(fn))); + using type = ::fn::expected; + if (self.has_value()) + if constexpr (not ::std::is_void_v) + return type(::std::in_place, _pfn_base::_value(FWD(self))); + else + return type(); + else + return type(::pfn::unexpect, _pfn_base::_error(FWD(self)).transform(FWD(fn))); + } +}; + +} // namespace detail + +// Primary template - non-void value type +template struct expected : private detail::_storage { + using _base = detail::_storage; + using value_type = T; + using error_type = Err; using unexpected_type = ::pfn::unexpected; - static_assert(not std::is_same_v>); + static_assert(not ::std::is_same_v>); + + template using rebind = expected; + + // Allow sibling _storage instantiations to downcast into the private base. + template friend struct ::pfn::detail::_storage; + template friend struct ::fn::detail::_storage; + + // Constructors. Explicit forwarders to the base mirror pfn::expected. + constexpr expected() noexcept(::std::is_nothrow_default_constructible_v) + requires ::std::is_default_constructible_v + : _base(::std::in_place) + { + } + + template + constexpr explicit(not ::std::is_convertible_v || not ::std::is_convertible_v) + expected(expected const &s) // + noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) + requires(_base::template _can_copy_convert::value) + : _base(s) + { + } + template + constexpr explicit(not ::std::is_convertible_v || not ::std::is_convertible_v) + expected(expected &&s) // + noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) + requires(_base::template _can_move_convert::value) + : _base(::std::move(s)) + { + } + template > + constexpr explicit(not ::std::is_convertible_v) expected(U &&v) // + noexcept(::std::is_nothrow_constructible_v) + requires(_base::template _can_convert::value) + : _base(::std::in_place, FWD(v)) + { + } + + template + constexpr explicit(not ::std::is_convertible_v) expected(::pfn::unexpected const &g) // + noexcept(::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v) + : _base(::pfn::unexpect, ::std::forward(g.error())) + { + } + template + constexpr explicit(not ::std::is_convertible_v) expected(::pfn::unexpected &&g) // + noexcept(::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v) + : _base(::pfn::unexpect, ::std::forward(g.error())) + { + } + + template + constexpr explicit expected(::std::in_place_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) + requires ::std::is_constructible_v + : _base(::std::in_place, FWD(a)...) + { + } + template + constexpr explicit expected(::std::in_place_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) + requires ::std::is_constructible_v &, Args...> + : _base(::std::in_place, il, FWD(a)...) + { + } + template + constexpr explicit expected(::pfn::unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) // + requires ::std::is_constructible_v + : _base(::pfn::unexpect, FWD(a)...) + { + } + template + constexpr explicit expected(::pfn::unexpect_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) + requires ::std::is_constructible_v &, Args...> + : _base(::pfn::unexpect, il, FWD(a)...) + { + } + + constexpr expected(expected const &) = delete; + constexpr expected(expected const &s) // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_copy_constructible_v) + requires(::std::is_copy_constructible_v && ::std::is_copy_constructible_v + && ::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v) + = default; + constexpr expected(expected const &s) // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_copy_constructible_v) + requires(::std::is_copy_constructible_v && ::std::is_copy_constructible_v + && (not ::std::is_trivially_copy_constructible_v || not ::std::is_trivially_copy_constructible_v)) + : _base(s.set_, FWD(s).storage_) + { + } + constexpr expected(expected &&s) + requires(::std::is_move_constructible_v && ::std::is_move_constructible_v + && ::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v) + = default; + constexpr expected(expected &&s) // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) + requires(::std::is_move_constructible_v && ::std::is_move_constructible_v + && (not ::std::is_trivially_move_constructible_v || not ::std::is_trivially_move_constructible_v)) + : _base(s.set_, FWD(s).storage_) + { + } + + constexpr ~expected() = default; + + // Assignment. Explicit forwarders mirror pfn::expected to avoid an MSVC bug. + template + constexpr expected &operator=(U &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) + requires(_base::template _can_convert_assign::value) + { + this->_assign_value(FWD(s)); + return *this; + } + template + constexpr expected &operator=(::pfn::unexpected const &s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v && ::std::is_assignable_v + && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v + || ::std::is_nothrow_move_constructible_v)) + { + this->_assign_unexpected(s); + return *this; + } + template + constexpr expected &operator=(::pfn::unexpected &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v && ::std::is_assignable_v + && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v + || ::std::is_nothrow_move_constructible_v)) + { + this->_assign_unexpected(::std::move(s)); + return *this; + } + constexpr expected &operator=(expected const &) = delete; + constexpr expected &operator=(expected const &s) // + noexcept(::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v + && ::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v) + requires(::std::is_copy_assignable_v && ::std::is_copy_constructible_v && ::std::is_copy_assignable_v + && ::std::is_copy_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) + { + this->_assign(static_cast<_base const &>(s)); + return *this; + } + constexpr expected &operator=(expected &&s) // + noexcept(::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v + && ::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) + requires(::std::is_move_constructible_v && ::std::is_move_assignable_v && ::std::is_move_constructible_v + && ::std::is_move_assignable_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) + { + this->_assign(static_cast<_base &&>(s)); + return *this; + } + + // Observers inherited from _storage + using _base::operator*; + using _base::operator->; + using _base::operator bool; + using _base::error; + using _base::error_or; + using _base::has_error; + using _base::has_value; + using _base::value; + using _base::value_or; + + // Emplace inherited from _storage + using _base::emplace; + + // Swap; body delegates to _storage helper + constexpr void + swap(expected &rhs) noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v + && ::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) + requires(::std::is_swappable_v && ::std::is_swappable_v && ::std::is_move_constructible_v + && ::std::is_move_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) + { + this->_swap_with(rhs); + } + + // Monadic operations. Bodies delegate to _storage static helpers, which perform sum-widening. + template + constexpr auto and_then(F &&f) & // + noexcept(noexcept(_base::_and_then(*this, FWD(f)))) // extension + -> decltype(_base::_and_then(*this, FWD(f))) + { + return _base::_and_then(*this, FWD(f)); + } + template + constexpr auto and_then(F &&f) && // + noexcept(noexcept(_base::_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_and_then(::std::move(*this), FWD(f))) + { + return _base::_and_then(::std::move(*this), FWD(f)); + } + template + constexpr auto and_then(F &&f) const & // + noexcept(noexcept(_base::_and_then(*this, FWD(f)))) // extension + -> decltype(_base::_and_then(*this, FWD(f))) + { + return _base::_and_then(*this, FWD(f)); + } + template + constexpr auto and_then(F &&f) const && // + noexcept(noexcept(_base::_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_and_then(::std::move(*this), FWD(f))) + { + return _base::_and_then(::std::move(*this), FWD(f)); + } - using ::pfn::expected::expected; + template + constexpr auto or_else(F &&f) & // + noexcept(noexcept(_base::_or_else(*this, FWD(f)))) // extension + -> decltype(_base::_or_else(*this, FWD(f))) + { + return _base::_or_else(*this, FWD(f)); + } + template + constexpr auto or_else(F &&f) && // + noexcept(noexcept(_base::_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_or_else(::std::move(*this), FWD(f))) + { + return _base::_or_else(::std::move(*this), FWD(f)); + } + template + constexpr auto or_else(F &&f) const & // + noexcept(noexcept(_base::_or_else(*this, FWD(f)))) // extension + -> decltype(_base::_or_else(*this, FWD(f))) + { + return _base::_or_else(*this, FWD(f)); + } + template + constexpr auto or_else(F &&f) const && // + noexcept(noexcept(_base::_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_or_else(::std::move(*this), FWD(f))) + { + return _base::_or_else(::std::move(*this), FWD(f)); + } - // convert to graded monad + template + constexpr auto transform(F &&f) & // + noexcept(noexcept(_base::_transform(*this, FWD(f)))) // extension + -> decltype(_base::_transform(*this, FWD(f))) + { + return _base::_transform(*this, FWD(f)); + } + template + constexpr auto transform(F &&f) && // + noexcept(noexcept(_base::_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform(::std::move(*this), FWD(f))) + { + return _base::_transform(::std::move(*this), FWD(f)); + } + template + constexpr auto transform(F &&f) const & // + noexcept(noexcept(_base::_transform(*this, FWD(f)))) // extension + -> decltype(_base::_transform(*this, FWD(f))) + { + return _base::_transform(*this, FWD(f)); + } + template + constexpr auto transform(F &&f) const && // + noexcept(noexcept(_base::_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform(::std::move(*this), FWD(f))) + { + return _base::_transform(::std::move(*this), FWD(f)); + } + + template + constexpr auto transform_error(F &&f) & // + noexcept(noexcept(_base::_transform_error(*this, FWD(f)))) // extension + -> decltype(_base::_transform_error(*this, FWD(f))) + { + return _base::_transform_error(*this, FWD(f)); + } + template + constexpr auto transform_error(F &&f) && // + noexcept(noexcept(_base::_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform_error(::std::move(*this), FWD(f))) + { + return _base::_transform_error(::std::move(*this), FWD(f)); + } + template + constexpr auto transform_error(F &&f) const & // + noexcept(noexcept(_base::_transform_error(*this, FWD(f)))) // extension + -> decltype(_base::_transform_error(*this, FWD(f))) + { + return _base::_transform_error(*this, FWD(f)); + } + template + constexpr auto transform_error(F &&f) const && // + noexcept(noexcept(_base::_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform_error(::std::move(*this), FWD(f))) + { + return _base::_transform_error(::std::move(*this), FWD(f)); + } + + // Convert to graded monad auto sum_error() const & -> expected> requires(not some_sum) { @@ -48,7 +554,6 @@ template struct expected final : ::pfn::expected(this->error())}; } - auto sum_error() && -> expected> requires(not some_sum) { @@ -58,25 +563,21 @@ template struct expected final : ::pfn::expected(std::move(*this).error())}; } - auto sum_error() & -> decltype(auto) requires(some_sum) { return *this; } - auto sum_error() const & -> decltype(auto) requires(some_sum) { return *this; } - auto sum_error() && -> decltype(auto) requires(some_sum) { return std::move(*this); } - auto sum_error() const && -> decltype(auto) requires(some_sum) { @@ -84,7 +585,7 @@ template struct expected final : ::pfn::expected expected, error_type> - requires(not std::is_same_v) && (not some_sum) + requires(not some_sum) { using type = expected, error_type>; if (this->has_value()) @@ -92,9 +593,8 @@ template struct expected final : ::pfn::expectederror()}; } - auto sum_value() && -> expected, error_type> - requires(not std::is_same_v) && (not some_sum) + requires(not some_sum) { using type = expected, error_type>; if (this->has_value()) @@ -102,740 +602,275 @@ template struct expected final : ::pfn::expected decltype(auto) requires(some_sum) { return *this; } - auto sum_value() const & -> decltype(auto) requires(some_sum) { return *this; } - auto sum_value() && -> decltype(auto) requires(some_sum) { return std::move(*this); } - auto sum_value() const && -> decltype(auto) requires(some_sum) { return std::move(*this); } +}; - // and_then non void - template - constexpr auto and_then(Fn &&fn) & - requires std::is_constructible_v && (not std::is_same_v) - { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn), this->value()); - else - return type(::pfn::unexpect, this->error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn), this->value()); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, this->error()); - else - std::unreachable(); - } - } - } +template struct expected : private detail::_storage { + using _base = detail::_storage; + using value_type = void; + using error_type = Err; + using unexpected_type = ::pfn::unexpected; - template - constexpr auto and_then(Fn &&fn) const & - requires std::is_constructible_v && (not std::is_same_v) - { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn), this->value()); - else - return type(::pfn::unexpect, this->error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn), this->value()); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, this->error()); - else - std::unreachable(); - } - } - } + template using rebind = expected; - template - constexpr auto and_then(Fn &&fn) && - requires std::is_constructible_v && (not std::is_same_v) - { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); - else - return type(::pfn::unexpect, std::move(*this).error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, std::move(*this).error()); - else - std::unreachable(); - } - } - } + template friend struct ::pfn::detail::_storage; + template friend struct ::fn::detail::_storage; + + constexpr expected() noexcept : _base(::std::in_place) {} - template - constexpr auto and_then(Fn &&fn) const && - requires std::is_constructible_v && (not std::is_same_v) + template + constexpr explicit(not ::std::is_convertible_v) expected(expected const &s) // + noexcept(::std::is_nothrow_constructible_v) + requires(_base::template _can_copy_convert::value) + : _base(s) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); - else - return type(::pfn::unexpect, std::move(*this).error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, std::move(*this).error()); - else - std::unreachable(); - } - } } - - // and_then void - template - constexpr auto and_then(Fn &&fn) & - requires std::is_constructible_v && std::is_same_v + template + constexpr explicit(not ::std::is_convertible_v) expected(expected &&s) // + noexcept(::std::is_nothrow_constructible_v) + requires(_base::template _can_move_convert::value) + : _base(::std::move(s)) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn)); - else - return type(::pfn::unexpect, this->error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn)); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, this->error()); - else - std::unreachable(); - } - } } - - template - constexpr auto and_then(Fn &&fn) const & - requires std::is_constructible_v && std::is_same_v + template + constexpr explicit(not ::std::is_convertible_v) expected(::pfn::unexpected const &g) // + noexcept(::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v) + : _base(::pfn::unexpect, ::std::forward(g.error())) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn)); - else - return type(::pfn::unexpect, this->error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn)); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, this->error()); - else - std::unreachable(); - } - } } - - template - constexpr auto and_then(Fn &&fn) && - requires std::is_constructible_v && std::is_same_v + template + constexpr explicit(not ::std::is_convertible_v) expected(::pfn::unexpected &&g) // + noexcept(::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v) + : _base(::pfn::unexpect, ::std::forward(g.error())) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn)); - else - return type(::pfn::unexpect, std::move(*this).error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn)); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, std::move(*this).error()); - else - std::unreachable(); - } - } } - template - constexpr auto and_then(Fn &&fn) const && - requires std::is_constructible_v && std::is_same_v + constexpr explicit expected(::std::in_place_t) noexcept : _base(::std::in_place) {} + + template + constexpr explicit expected(::pfn::unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) // + requires ::std::is_constructible_v + : _base(::pfn::unexpect, FWD(a)...) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - return ::fn::detail::_invoke(FWD(fn)); - else - return type(::pfn::unexpect, std::move(*this).error()); - } else { - using new_error_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) { - auto t = ::fn::detail::_invoke(FWD(fn)); - if (t.has_value()) - if constexpr (not std::is_same_v) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{std::in_place}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } else { - if constexpr (not std::is_same_v>) - return new_type(::pfn::unexpect, std::move(*this).error()); - else - std::unreachable(); - } - } } - - // or_else - template - constexpr auto or_else(Fn &&fn) & - requires(std::is_constructible_v || std::same_as) + template + constexpr explicit expected(::pfn::unexpect_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) + requires ::std::is_constructible_v &, Args...> + : _base(::pfn::unexpect, il, FWD(a)...) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, this->value()); - else - return type(); - else - return ::fn::detail::_invoke(FWD(fn), this->error()); - } else { - static_assert(not std::is_same_v); - using new_value_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) - return new_type{std::in_place, this->value()}; - else { - auto t = ::fn::detail::_invoke(FWD(fn), this->error()); - if (t.has_value()) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } - } } - template - constexpr auto or_else(Fn &&fn) const & - requires(std::is_constructible_v || std::same_as) + constexpr expected(expected const &) = delete; + constexpr expected(expected const &) + requires(::std::is_copy_constructible_v && ::std::is_trivially_copy_constructible_v) + = default; + constexpr expected(expected const &s) // + noexcept(::std::is_nothrow_copy_constructible_v) + requires(::std::is_copy_constructible_v && not ::std::is_trivially_copy_constructible_v) + : _base(s.set_, FWD(s).storage_) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, this->value()); - else - return type(); - else - return ::fn::detail::_invoke(FWD(fn), this->error()); - } else { - static_assert(not std::is_same_v); - using new_value_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) - return new_type{std::in_place, this->value()}; - else { - auto t = ::fn::detail::_invoke(FWD(fn), this->error()); - if (t.has_value()) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } - } } - - template - constexpr auto or_else(Fn &&fn) && - requires(std::is_constructible_v || std::same_as) + constexpr expected(expected &&s) + requires(::std::is_move_constructible_v && ::std::is_trivially_move_constructible_v) + = default; + constexpr expected(expected &&s) // + noexcept(::std::is_nothrow_move_constructible_v) + requires(::std::is_move_constructible_v && not ::std::is_trivially_move_constructible_v) + : _base(s.set_, FWD(s).storage_) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, std::move(*this).value()); - else - return type(); - else - return ::fn::detail::_invoke(FWD(fn), std::move(*this).error()); - } else { - static_assert(not std::is_same_v); - using new_value_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) - return new_type{std::in_place, std::move(*this).value()}; - else { - auto t = ::fn::detail::_invoke(FWD(fn), std::move(*this).error()); - if (t.has_value()) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } - } } - template - constexpr auto or_else(Fn &&fn) const && - requires(std::is_constructible_v || std::same_as) + constexpr ~expected() = default; + + template + constexpr expected &operator=(::pfn::unexpected const &s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v && ::std::is_assignable_v) { - using type = ::fn::detail::_invoke_result::type; - static_assert(some_expected); - static_assert(std::is_same_v || some_sum); - if constexpr (std::is_same_v) { - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, std::move(*this).value()); - else - return type(); - else - return ::fn::detail::_invoke(FWD(fn), std::move(*this).error()); - } else { - static_assert(not std::is_same_v); - using new_value_type = sum_for; - using new_type = ::fn::expected; - if (this->has_value()) - return new_type{std::in_place, std::move(*this).value()}; - else { - auto t = ::fn::detail::_invoke(FWD(fn), std::move(*this).error()); - if (t.has_value()) - return new_type{std::in_place, std::move(t).value()}; - else - return new_type{::pfn::unexpect, std::move(t).error()}; - } - } + this->_assign_unexpected(s); + return *this; } - - // transform not void, not sum - template - constexpr auto transform(Fn &&fn) & - requires(not std::is_same_v) && (not some_sum) + template + constexpr expected &operator=(::pfn::unexpected &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v && ::std::is_assignable_v) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn), this->value()); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn), this->value())); - else - return type(::pfn::unexpect, this->error()); + this->_assign_unexpected(::std::move(s)); + return *this; } - - template - constexpr auto transform(Fn &&fn) const & - requires(not std::is_same_v) && (not some_sum) + constexpr expected &operator=(expected const &) = delete; + constexpr expected &operator=(expected const &s) // + noexcept(::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v) + requires(::std::is_copy_assignable_v && ::std::is_copy_constructible_v) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn), this->value()); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn), this->value())); - else - return type(::pfn::unexpect, this->error()); + this->_assign(static_cast<_base const &>(s)); + return *this; } - - template - constexpr auto transform(Fn &&fn) && - requires(not std::is_same_v) && (not some_sum) + constexpr expected &operator=(expected &&s) // + noexcept(::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) + requires(::std::is_move_constructible_v && ::std::is_move_assignable_v) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn), std::move(*this).value())); - else - return type(::pfn::unexpect, std::move(*this).error()); + this->_assign(static_cast<_base &&>(s)); + return *this; } - template - constexpr auto transform(Fn &&fn) const && - requires(not std::is_same_v) && (not some_sum) + using _base::emplace; + + constexpr void swap(expected &rhs) // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) + requires(::std::is_swappable_v && ::std::is_move_constructible_v) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn), std::move(*this).value()); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn), std::move(*this).value())); - else - return type(::pfn::unexpect, std::move(*this).error()); + this->_swap_with(rhs); } - // transform sum - template - constexpr auto transform(Fn &&fn) & - requires some_sum + using _base::operator*; + using _base::operator bool; + using _base::error; + using _base::error_or; + using _base::has_error; + using _base::has_value; + using _base::value; + + // Monadic operations. Bodies delegate to _storage static helpers, which perform sum-widening. + template + constexpr auto and_then(F &&f) & // + noexcept(noexcept(_base::_and_then(*this, FWD(f)))) // extension + -> decltype(_base::_and_then(*this, FWD(f))) { - using new_value_type = decltype(this->value().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - this->value().transform(FWD(fn)); - return type(); - } else - return type(std::in_place, this->value().transform(FWD(fn))); - else - return type(::pfn::unexpect, this->error()); + return _base::_and_then(*this, FWD(f)); } - - template - constexpr auto transform(Fn &&fn) const & - requires some_sum + template + constexpr auto and_then(F &&f) && // + noexcept(noexcept(_base::_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_and_then(::std::move(*this), FWD(f))) { - using new_value_type = decltype(this->value().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - this->value().transform(FWD(fn)); - return type(); - } else - return type(std::in_place, this->value().transform(FWD(fn))); - else - return type(::pfn::unexpect, this->error()); + return _base::_and_then(::std::move(*this), FWD(f)); } - - template - constexpr auto transform(Fn &&fn) && - requires some_sum + template + constexpr auto and_then(F &&f) const & // + noexcept(noexcept(_base::_and_then(*this, FWD(f)))) // extension + -> decltype(_base::_and_then(*this, FWD(f))) { - using new_value_type = decltype(std::move(*this).value().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - std::move(*this).value().transform(FWD(fn)); - return type(); - } else - return type(std::in_place, std::move(*this).value().transform(FWD(fn))); - else - return type(::pfn::unexpect, std::move(*this).error()); + return _base::_and_then(*this, FWD(f)); } - - template - constexpr auto transform(Fn &&fn) const && - requires some_sum + template + constexpr auto and_then(F &&f) const && // + noexcept(noexcept(_base::_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_and_then(::std::move(*this), FWD(f))) { - using new_value_type = decltype(std::move(*this).value().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - std::move(*this).value().transform(FWD(fn)); - return type(); - } else - return type(std::in_place, std::move(*this).value().transform(FWD(fn))); - else - return type(::pfn::unexpect, std::move(*this).error()); + return _base::_and_then(::std::move(*this), FWD(f)); } - // transform void - template - constexpr auto transform(Fn &&fn) & - requires std::is_same_v + template + constexpr auto or_else(F &&f) & // + noexcept(noexcept(_base::_or_else(*this, FWD(f)))) // extension + -> decltype(_base::_or_else(*this, FWD(f))) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn)); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn))); - else - return type(::pfn::unexpect, this->error()); + return _base::_or_else(*this, FWD(f)); } - - template - constexpr auto transform(Fn &&fn) const & - requires std::is_same_v + template + constexpr auto or_else(F &&f) && // + noexcept(noexcept(_base::_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_or_else(::std::move(*this), FWD(f))) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn)); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn))); - else - return type(::pfn::unexpect, this->error()); + return _base::_or_else(::std::move(*this), FWD(f)); } - - template - constexpr auto transform(Fn &&fn) && - requires std::is_same_v + template + constexpr auto or_else(F &&f) const & // + noexcept(noexcept(_base::_or_else(*this, FWD(f)))) // extension + -> decltype(_base::_or_else(*this, FWD(f))) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn)); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn))); - else - return type(::pfn::unexpect, std::move(*this).error()); + return _base::_or_else(*this, FWD(f)); } - - template - constexpr auto transform(Fn &&fn) const && - requires std::is_same_v + template + constexpr auto or_else(F &&f) const && // + noexcept(noexcept(_base::_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_or_else(::std::move(*this), FWD(f))) { - using new_value_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (std::is_same_v) { - ::fn::detail::_invoke(FWD(fn)); - return type(); - } else - return type(std::in_place, ::fn::detail::_invoke(FWD(fn))); - else - return type(::pfn::unexpect, std::move(*this).error()); + return _base::_or_else(::std::move(*this), FWD(f)); } - // transform_error not sum - template - constexpr auto transform_error(Fn &&fn) & - requires(not some_sum) + template + constexpr auto transform(F &&f) & // + noexcept(noexcept(_base::_transform(*this, FWD(f)))) // extension + -> decltype(_base::_transform(*this, FWD(f))) { - using new_error_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, this->value()); - else - return type(); - else - return type(::pfn::unexpect, ::fn::detail::_invoke(FWD(fn), this->error())); + return _base::_transform(*this, FWD(f)); } - - template - constexpr auto transform_error(Fn &&fn) const & - requires(not some_sum) + template + constexpr auto transform(F &&f) && // + noexcept(noexcept(_base::_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform(::std::move(*this), FWD(f))) { - using new_error_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, this->value()); - else - return type(); - else - return type(::pfn::unexpect, ::fn::detail::_invoke(FWD(fn), this->error())); + return _base::_transform(::std::move(*this), FWD(f)); } - - template - constexpr auto transform_error(Fn &&fn) && - requires(not some_sum) + template + constexpr auto transform(F &&f) const & // + noexcept(noexcept(_base::_transform(*this, FWD(f)))) // extension + -> decltype(_base::_transform(*this, FWD(f))) { - using new_error_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, std::move(*this).value()); - else - return type(); - else - return type(::pfn::unexpect, ::fn::detail::_invoke(FWD(fn), std::move(*this).error())); + return _base::_transform(*this, FWD(f)); } - - template - constexpr auto transform_error(Fn &&fn) const && - requires(not some_sum) + template + constexpr auto transform(F &&f) const && // + noexcept(noexcept(_base::_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform(::std::move(*this), FWD(f))) { - using new_error_type = detail::_invoke_result::type; - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, std::move(*this).value()); - else - return type(); - else - return type(::pfn::unexpect, ::fn::detail::_invoke(FWD(fn), std::move(*this).error())); + return _base::_transform(::std::move(*this), FWD(f)); } - // transform_error sum - template - constexpr auto transform_error(Fn &&fn) & - requires some_sum + template + constexpr auto transform_error(F &&f) & // + noexcept(noexcept(_base::_transform_error(*this, FWD(f)))) // extension + -> decltype(_base::_transform_error(*this, FWD(f))) { - using new_error_type = decltype(this->error().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, this->value()); - else - return type(); - else - return type(::pfn::unexpect, this->error().transform(FWD(fn))); + return _base::_transform_error(*this, FWD(f)); } - - template - constexpr auto transform_error(Fn &&fn) const & - requires some_sum + template + constexpr auto transform_error(F &&f) && // + noexcept(noexcept(_base::_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform_error(::std::move(*this), FWD(f))) { - using new_error_type = decltype(this->error().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, this->value()); - else - return type(); - else - return type(::pfn::unexpect, this->error().transform(FWD(fn))); + return _base::_transform_error(::std::move(*this), FWD(f)); } - - template - constexpr auto transform_error(Fn &&fn) && - requires some_sum + template + constexpr auto transform_error(F &&f) const & // + noexcept(noexcept(_base::_transform_error(*this, FWD(f)))) // extension + -> decltype(_base::_transform_error(*this, FWD(f))) { - using new_error_type = decltype(std::move(*this).error().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, std::move(*this).value()); - else - return type(); - else - return type(::pfn::unexpect, std::move(*this).error().transform(FWD(fn))); + return _base::_transform_error(*this, FWD(f)); } - - template - constexpr auto transform_error(Fn &&fn) const && - requires some_sum + template + constexpr auto transform_error(F &&f) const && // + noexcept(noexcept(_base::_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_base::_transform_error(::std::move(*this), FWD(f))) { - using new_error_type = decltype(std::move(*this).error().transform(FWD(fn))); - using type = expected; - if (this->has_value()) - if constexpr (not std::is_same_v) - return type(std::in_place, std::move(*this).value()); - else - return type(); - else - return type(::pfn::unexpect, std::move(*this).error().transform(FWD(fn))); + return _base::_transform_error(::std::move(*this), FWD(f)); } }; - // Lifts for sum transformation functions [[nodiscard]] constexpr auto sum_value(some_expected auto &&src) -> decltype(auto) { return FWD(src).sum_value(); } [[nodiscard]] constexpr auto sum_error(some_expected auto &&src) -> decltype(auto) { return FWD(src).sum_error(); } From c28289b765ced2283e0082b347c1a00516e6f1ec Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Wed, 3 Jun 2026 14:19:36 +0100 Subject: [PATCH 12/21] Fix Bazel builds --- BUILD.bazel | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 2eecec27..f9475f38 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -13,6 +13,5 @@ cc_library( name = "fn", hdrs = glob(["include/fn/**/*.hpp"]), includes = ["include"], - # TODO uncomment once fn is reimplemented in terms of pfn. - # deps = [":pfn"], + deps = [":pfn"], ) From 1465b2ba4d62686be4900ac2df97c3d7a4849330 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Wed, 3 Jun 2026 22:56:36 +0100 Subject: [PATCH 13/21] Work around clang-18 or older miscompile in _or_else void+value path clang miscompiles `return type();` (and `return type{};`, `return type{in_place};`) in fn::detail::_storage::_or_else when T is void and self is in value state, for three of the four Self ref-qualifier instantiations (&, const &, const &&). The returned expected ends up observed as has_value() == false even though the path through _or_else verifiably constructs a value-state result. Naming a local `result` variable forces NRVO/move semantics and dodges the buggy mandatory-elision lowering. Safe specifically here because the void case constructs an empty expected with no user value forwarding, so we don't need mandatory copy elision to support non-copyable user types. Sibling branches (non-void value preservation, sum_for path) keep their prvalue returns and remain unaffected. Assisted-by: Claude:claude-opus-4-7 --- include/fn/expected.hpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/include/fn/expected.hpp b/include/fn/expected.hpp index 6f392759..dfce830e 100644 --- a/include/fn/expected.hpp +++ b/include/fn/expected.hpp @@ -127,8 +127,21 @@ template struct _storage : ::pfn::detail::_storage) return type(::std::in_place, _pfn_base::_value(FWD(self))); - else - return type(); + else { + static_assert(::std::is_void_v); +#if defined(__clang__) && __clang_major__ <= 18 + // clang 15-18 miscompile the prvalue return below for three of the four Self ref-qualifier + // instantiations (&, const &, const &&) at -O1/-O2: the value-state result is observed with + // set_ == false (storage-poison). Naming the local and returning it by name dodges the buggy + // mandatory copy-elision lowering. Fixed in clang-19; gcc unaffected — both take the prvalue + // path, which keeps guaranteed elision and so also supports a non-movable error type (whereas + // this workaround requires `type` be move-constructible). + type result{::std::in_place}; + return result; +#else + return type{::std::in_place}; +#endif + } else return ::fn::detail::_invoke(FWD(fn), _pfn_base::_error(FWD(self))); } else { From 4160feaa8f300beb49fcbfa4f59313b3eddb5a02 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 4 Jun 2026 19:39:32 +0100 Subject: [PATCH 14/21] Minor cleanup in .clangd and CLAUDE.md --- .clangd | 2 +- CLAUDE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.clangd b/.clangd index 403cab3d..57b56447 100644 --- a/.clangd +++ b/.clangd @@ -1,6 +1,6 @@ CompileFlags: Add: [-Wall, -Wextra, -Wpedantic, -Wno-missing-braces, -Wno-c2y-extensions] - Remove: -W* + Remove: [-W*, -funreachable-traps] Compiler: clang++ Diagnostics: ClangTidy: diff --git a/CLAUDE.md b/CLAUDE.md index 7f99648c..8375aa61 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ Conventions for AI agents in this repo (you are the primary reader — keep this ## Commits -- Trailer `Assisted-by: Claude:` (Linux-kernel convention), e.g. `claude-opus-4-7`. No `Co-Authored-By:`. +- Trailer `Assisted-by: Claude:` (Linux-kernel convention), e.g. `claude-opus-4-8`. No `Co-Authored-By:`. - Offer commits; never commit without confirmation. Terse messages: imperative topic, body only if needed. - Never `git push` or sign commits — the user signs (GPG) and pushes. From 4fe62056389c22bf89ba677cabaa540864572518 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 5 Jun 2026 11:31:01 +0000 Subject: [PATCH 15/21] Add fn::expected polyfill test nesting the pfn suite Assisted-by: Claude:claude-opus-4-8 --- tests/CMakeLists.txt | 1 + tests/fn/expected_polyfill.cpp | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 tests/fn/expected_polyfill.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ca091137..7f221667 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -122,6 +122,7 @@ set(TESTS_FN_SOURCES fn/concepts.cpp fn/discard.cpp fn/expected.cpp + fn/expected_polyfill.cpp fn/fail.cpp fn/filter.cpp fn/functional.cpp diff --git a/tests/fn/expected_polyfill.cpp b/tests/fn/expected_polyfill.cpp new file mode 100644 index 00000000..1a8ad3f3 --- /dev/null +++ b/tests/fn/expected_polyfill.cpp @@ -0,0 +1,20 @@ +// Copyright (c) 2026 Bronek Kozicki +// +// Distributed under the ISC License. See accompanying file LICENSE.md +// or copy at https://opensource.org/licenses/ISC + +// Run the whole pfn polyfill conformance suite against fn::expected. Bring the +// subject-under-test aliases into the global namespace, then include pfn/expected.cpp. +// The fn::expected sum/graded/pack behaviour is covered in fn/expected.cpp. + +#define LIBFN_ASSERT(...) +#include + +using fn::expected; +using pfn::bad_expected_access; +using pfn::unexpect; +using pfn::unexpect_t; +using pfn::unexpected; + +#define PFN_TEST_NESTED +#include "pfn/expected.cpp" From 88c53a76dd358221a12f8914b8e10b81279538f8 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 5 Jun 2026 12:48:19 +0000 Subject: [PATCH 16/21] Drop fn::expected polyfill tests now covered by nested pfn suite Assisted-by: Claude:claude-opus-4-8 --- tests/fn/expected.cpp | 493 ++++++++---------------------------------- 1 file changed, 88 insertions(+), 405 deletions(-) diff --git a/tests/fn/expected.cpp b/tests/fn/expected.cpp index 7ccff426..b0d6c65f 100644 --- a/tests/fn/expected.cpp +++ b/tests/fn/expected.cpp @@ -2557,443 +2557,126 @@ TEST_CASE("expected sum support transform_error", "[expected][sum][transform_err } } -TEST_CASE("expected polyfills and_then", "[expected][polyfill][and_then]") +TEST_CASE("expected pack support or_else", "[expected][or_else][pack]") { WHEN("value") { - fn::expected s{12}; - CHECK(s.and_then( // - fn::overload([](int &i) -> fn::expected { return i == 12; }, - [](int const &) -> fn::expected { throw 0; }, - [](int &&) -> fn::expected { throw 0; }, - [](int const &&) -> fn::expected { throw 0; })) // + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; + CHECK(s.or_else( // + fn::overload([](int, Error &e) -> fn::expected { return e == FileNotFound; }, + [](int, Error const &) -> fn::expected { throw 0; }, + [](int, Error &&) -> fn::expected { throw 0; }, + [](int, Error const &&) -> fn::expected { throw 0; })) // .value()); CHECK(std::as_const(s) - .and_then( // - fn::overload([](int &) -> fn::expected { throw 0; }, - [](int const &i) -> fn::expected { return i == 12; }, - [](int &&) -> fn::expected { throw 0; }, - [](int const &&) -> fn::expected { throw 0; })) // + .or_else( // + fn::overload([](int, Error &) -> fn::expected { throw 0; }, + [](int, Error const &e) -> fn::expected { return e == FileNotFound; }, + [](int, Error &&) -> fn::expected { throw 0; }, + [](int, Error const &&) -> fn::expected { throw 0; })) // .value()); CHECK(std::move(std::as_const(s)) - .and_then( // - fn::overload([](int &) -> fn::expected { throw 0; }, - [](int const &) -> fn::expected { throw 0; }, - [](int &&) -> fn::expected { throw 0; }, - [](int const &&i) -> fn::expected { return i == 12; })) // + .or_else( // + fn::overload([](int, Error &) -> fn::expected { throw 0; }, + [](int, Error const &) -> fn::expected { throw 0; }, + [](int, Error &&) -> fn::expected { throw 0; }, + [](int, Error const &&e) -> fn::expected { return e == FileNotFound; })) // .value()); CHECK(std::move(s) - .and_then( // - fn::overload([](int &) -> fn::expected { throw 0; }, - [](int const &) -> fn::expected { throw 0; }, - [](int &&i) -> fn::expected { return i == 12; }, - [](int const &&) -> fn::expected { throw 0; })) // + .or_else( // + fn::overload([](int, Error &) -> fn::expected { throw 0; }, + [](int, Error const &) -> fn::expected { throw 0; }, + [](int, Error &&e) -> fn::expected { return e == FileNotFound; }, + [](int, Error const &&) -> fn::expected { throw 0; })) // .value()); - - WHEN("error") - { - fn::expected s{::pfn::unexpect, Unknown}; - CHECK(s.and_then( // - [](auto) -> fn::expected { throw 0; }) - .error() - == Unknown); - CHECK(std::as_const(s) - .and_then( // - [](auto) -> fn::expected { throw 0; }) - .error() - == Unknown); - CHECK(std::move(std::as_const(s)) - .and_then( // - [](auto) -> fn::expected { throw 0; }) - .error() - == Unknown); - CHECK(std::move(s) - .and_then( // - [](auto) -> fn::expected { throw 0; }) - .error() - == Unknown); - } - } - - WHEN("void") - { - fn::expected s{}; - CHECK(s.and_then([]() -> fn::expected { return true; }).value()); - CHECK(std::as_const(s).and_then([]() -> fn::expected { return true; }).value()); - CHECK(fn::expected{}.and_then([]() -> fn::expected { return true; }).value()); - CHECK(std::move(std::as_const(s)).and_then([]() -> fn::expected { return true; }).value()); - - WHEN("error") - { - fn::expected s{::pfn::unexpect, Unknown}; - CHECK(s.and_then([]() -> fn::expected { throw 0; }).error() == Unknown); - CHECK(std::as_const(s).and_then([]() -> fn::expected { throw 0; }).error() == Unknown); - CHECK(std::move(std::as_const(s)).and_then([]() -> fn::expected { throw 0; }).error() == Unknown); - CHECK(std::move(s).and_then([]() -> fn::expected { throw 0; }).error() == Unknown); - } - } -} - -TEST_CASE("expected polyfills or_else", "[expected][polyfill][or_else][pack]") -{ - WHEN("value") - { - fn::expected s{1}; - CHECK(s.or_else([](auto) -> fn::expected { throw 0; }).value()); - CHECK(std::as_const(s).or_else([](auto) -> fn::expected { throw 0; }).value()); - CHECK(fn::expected{1}.or_else([](auto) -> fn::expected { throw 0; }).value()); - CHECK(std::move(std::as_const(s)).or_else([](auto) -> fn::expected { throw 0; }).value()); - - WHEN("error") - { - fn::expected s{::pfn::unexpect, FileNotFound}; - CHECK(s.or_else( // - fn::overload([](Error &e) -> fn::expected { return e == FileNotFound; }, - [](Error const &) -> fn::expected { throw 0; }, - [](Error &&) -> fn::expected { throw 0; }, - [](Error const &&) -> fn::expected { throw 0; })) // - .value()); - CHECK(std::as_const(s) - .or_else( // - fn::overload([](Error &) -> fn::expected { throw 0; }, - [](Error const &e) -> fn::expected { return e == FileNotFound; }, - [](Error &&) -> fn::expected { throw 0; }, - [](Error const &&) -> fn::expected { throw 0; })) // - .value()); - CHECK(std::move(std::as_const(s)) - .or_else( // - fn::overload([](Error &) -> fn::expected { throw 0; }, - [](Error const &) -> fn::expected { throw 0; }, - [](Error &&) -> fn::expected { throw 0; }, - [](Error const &&e) -> fn::expected { return e == FileNotFound; })) // - .value()); - CHECK(std::move(s) - .or_else( // - fn::overload([](Error &) -> fn::expected { throw 0; }, - [](Error const &) -> fn::expected { throw 0; }, - [](Error &&e) -> fn::expected { return e == FileNotFound; }, - [](Error const &&) -> fn::expected { throw 0; })) // - .value()); - } - - WHEN("pack error") - { - fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; - CHECK(s.or_else( // - fn::overload([](int, Error &e) -> fn::expected { return e == FileNotFound; }, - [](int, Error const &) -> fn::expected { throw 0; }, - [](int, Error &&) -> fn::expected { throw 0; }, - [](int, Error const &&) -> fn::expected { throw 0; })) // - .value()); - CHECK(std::as_const(s) - .or_else( // - fn::overload([](int, Error &) -> fn::expected { throw 0; }, - [](int, Error const &e) -> fn::expected { return e == FileNotFound; }, - [](int, Error &&) -> fn::expected { throw 0; }, - [](int, Error const &&) -> fn::expected { throw 0; })) // - .value()); - CHECK(std::move(std::as_const(s)) - .or_else( // - fn::overload([](int, Error &) -> fn::expected { throw 0; }, - [](int, Error const &) -> fn::expected { throw 0; }, - [](int, Error &&) -> fn::expected { throw 0; }, - [](int, Error const &&e) -> fn::expected { return e == FileNotFound; })) // - .value()); - CHECK(std::move(s) - .or_else( // - fn::overload([](int, Error &) -> fn::expected { throw 0; }, - [](int, Error const &) -> fn::expected { throw 0; }, - [](int, Error &&e) -> fn::expected { return e == FileNotFound; }, - [](int, Error const &&) -> fn::expected { throw 0; })) // - .value()); - } } WHEN("void") { - fn::expected s{}; - CHECK(s.or_else([](auto) -> fn::expected { throw 0; }).has_value()); - CHECK(std::as_const(s).or_else([](auto) -> fn::expected { throw 0; }).has_value()); - CHECK(fn::expected{}.or_else([](auto) -> fn::expected { throw 0; }).has_value()); - CHECK(std::move(std::as_const(s)).or_else([](auto) -> fn::expected { throw 0; }).has_value()); - - WHEN("error") - { - fn::expected s{::pfn::unexpect, FileNotFound}; - CHECK(s.or_else( // - fn::overload([](Error &) -> fn::expected { return {}; }, - [](Error const &) -> fn::expected { throw 0; }, - [](Error &&) -> fn::expected { throw 0; }, - [](Error const &&) -> fn::expected { throw 0; })) - .has_value()); - CHECK(std::as_const(s) - .or_else( // - fn::overload([](Error &) -> fn::expected { throw 0; }, - [](Error const &) -> fn::expected { return {}; }, - [](Error &&) -> fn::expected { throw 0; }, - [](Error const &&) -> fn::expected { throw 0; })) - .has_value()); - CHECK(std::move(std::as_const(s)) - .or_else( // - fn::overload([](Error &) -> fn::expected { throw 0; }, - [](Error const &) -> fn::expected { throw 0; }, - [](Error &&) -> fn::expected { throw 0; }, - [](Error const &&) -> fn::expected { return {}; })) - .has_value()); - CHECK(std::move(s) - .or_else( // - fn::overload([](Error &) -> fn::expected { throw 0; }, - [](Error const &) -> fn::expected { throw 0; }, - [](Error &&) -> fn::expected { return {}; }, - [](Error const &&) -> fn::expected { throw 0; })) - .has_value()); - } - - WHEN("pack error") - { - fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; - CHECK(s.or_else( // - fn::overload([](int, Error &) -> fn::expected { return {}; }, - [](int, Error const &) -> fn::expected { throw 0; }, - [](int, Error &&) -> fn::expected { throw 0; }, - [](int, Error const &&) -> fn::expected { throw 0; })) - .has_value()); - CHECK(std::as_const(s) - .or_else( // - fn::overload([](int, Error &) -> fn::expected { throw 0; }, - [](int, Error const &) -> fn::expected { return {}; }, - [](int, Error &&) -> fn::expected { throw 0; }, - [](int, Error const &&) -> fn::expected { throw 0; })) - .has_value()); - CHECK(std::move(std::as_const(s)) - .or_else( // - fn::overload([](int, Error &) -> fn::expected { throw 0; }, - [](int, Error const &) -> fn::expected { throw 0; }, - [](int, Error &&) -> fn::expected { throw 0; }, - [](int, Error const &&) -> fn::expected { return {}; })) - .has_value()); - CHECK(std::move(s) - .or_else( // - fn::overload([](int, Error &) -> fn::expected { throw 0; }, - [](int, Error const &) -> fn::expected { throw 0; }, - [](int, Error &&) -> fn::expected { return {}; }, - [](int, Error const &&) -> fn::expected { throw 0; })) - .has_value()); - } - } -} - -TEST_CASE("expected polyfills transform", "[expected][polyfill][transform]") -{ - WHEN("value") - { - fn::expected s{12}; - CHECK(s.transform( // - fn::overload([](int &i) -> bool { return i == 12; }, [](int const &) -> bool { throw 0; }, - [](int &&) -> bool { throw 0; }, [](int const &&) -> bool { throw 0; })) // - .value()); + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; + CHECK(s.or_else( // + fn::overload([](int, Error &) -> fn::expected { return {}; }, + [](int, Error const &) -> fn::expected { throw 0; }, + [](int, Error &&) -> fn::expected { throw 0; }, + [](int, Error const &&) -> fn::expected { throw 0; })) + .has_value()); CHECK(std::as_const(s) - .transform( // - fn::overload([](int &) -> bool { throw 0; }, [](int const &i) -> bool { return i == 12; }, - [](int &&) -> bool { throw 0; }, [](int const &&) -> bool { throw 0; })) // - .value()); + .or_else( // + fn::overload([](int, Error &) -> fn::expected { throw 0; }, + [](int, Error const &) -> fn::expected { return {}; }, + [](int, Error &&) -> fn::expected { throw 0; }, + [](int, Error const &&) -> fn::expected { throw 0; })) + .has_value()); CHECK(std::move(std::as_const(s)) - .transform( // - fn::overload([](int &) -> bool { throw 0; }, [](int const &) -> bool { throw 0; }, - [](int &&) -> bool { throw 0; }, [](int const &&i) -> bool { return i == 12; })) // - .value()); + .or_else( // + fn::overload([](int, Error &) -> fn::expected { throw 0; }, + [](int, Error const &) -> fn::expected { throw 0; }, + [](int, Error &&) -> fn::expected { throw 0; }, + [](int, Error const &&) -> fn::expected { return {}; })) + .has_value()); CHECK(std::move(s) - .transform( // - fn::overload([](int &) -> bool { throw 0; }, [](int const &) -> bool { throw 0; }, - [](int &&i) -> bool { return i == 12; }, [](int const &&) -> bool { throw 0; })) // - .value()); - - WHEN("void result") - { - fn::expected s{12}; - CHECK(s.transform( // - fn::overload([](int &) -> void {}, [](int const &) -> void { throw 0; }, - [](int &&) -> void { throw 0; }, [](int const &&) -> void { throw 0; })) // - .has_value()); - CHECK(std::as_const(s) - .transform( // - fn::overload([](int &) -> void { throw 0; }, [](int const &) -> void {}, - [](int &&) -> void { throw 0; }, [](int const &&) -> void { throw 0; })) // - .has_value()); - CHECK(std::move(std::as_const(s)) - .transform( // - fn::overload([](int &) -> void { throw 0; }, [](int const &) -> void { throw 0; }, - [](int &&) -> void { throw 0; }, [](int const &&) -> void {})) // - .has_value()); - CHECK(std::move(s) - .transform( // - fn::overload([](int &) -> void { throw 0; }, [](int const &) -> void { throw 0; }, - [](int &&) -> void {}, [](int const &&) -> void { throw 0; })) // - .has_value()); - } - - WHEN("error") - { - fn::expected s{::pfn::unexpect, Unknown}; - CHECK(s.transform([](auto) -> bool { throw 0; }).error() == Unknown); - CHECK(std::as_const(s).transform([](auto) -> bool { throw 0; }).error() == Unknown); - CHECK(std::move(std::as_const(s)).transform([](auto) -> bool { throw 0; }).error() == Unknown); - CHECK(std::move(s).transform([](auto) -> bool { throw 0; }).error() == Unknown); - } - } - - WHEN("void") - { - fn::expected s{}; - CHECK(s.transform([]() -> bool { return true; }).value()); - CHECK(std::as_const(s).transform([]() -> bool { return true; }).value()); - CHECK(std::move(std::as_const(s)).transform([]() -> bool { return true; }).value()); - CHECK(std::move(s).transform([]() -> bool { return true; }).value()); - - WHEN("void result") - { - fn::expected s{}; - CHECK(s.transform([]() -> void {}).has_value()); - CHECK(std::as_const(s).transform([]() -> void {}).has_value()); - CHECK(std::move(std::as_const(s)).transform([]() -> void {}).has_value()); - CHECK(std::move(s).transform([]() -> void {}).has_value()); - } - - WHEN("error") - { - fn::expected s{::pfn::unexpect, Unknown}; - CHECK(s.transform([]() -> bool { throw 0; }).error() == Unknown); - CHECK(std::as_const(s).transform([]() -> bool { throw 0; }).error() == Unknown); - CHECK(std::move(std::as_const(s)).transform([]() -> bool { throw 0; }).error() == Unknown); - CHECK(std::move(s).transform([]() -> bool { throw 0; }).error() == Unknown); - } + .or_else( // + fn::overload([](int, Error &) -> fn::expected { throw 0; }, + [](int, Error const &) -> fn::expected { throw 0; }, + [](int, Error &&) -> fn::expected { return {}; }, + [](int, Error const &&) -> fn::expected { throw 0; })) + .has_value()); } } -TEST_CASE("expected polyfills transform_error", "[expected][polyfill][transform_error][pack]") +TEST_CASE("expected pack support transform_error", "[expected][transform_error][pack]") { WHEN("value") { - fn::expected s{12}; - CHECK(s.transform_error([](Error) -> bool { throw 0; }).value() == 12); - CHECK(std::as_const(s).transform_error([](Error) -> bool { throw 0; }).value() == 12); - CHECK(fn::expected{12}.transform_error([](Error) -> bool { throw 0; }).value() == 12); - CHECK(std::move(std::as_const(s)).transform_error([](Error) -> bool { throw 0; }).value() == 12); - - WHEN("error") - { - fn::expected s{::pfn::unexpect, FileNotFound}; - CHECK( - s.transform_error( // - fn::overload([](Error &e) -> bool { return e == FileNotFound; }, [](Error const &) -> bool { throw 0; }, - [](Error &&) -> bool { throw 0; }, [](Error const &&) -> bool { throw 0; })) // + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; + CHECK(s.transform_error( // + fn::overload([](int, Error &e) -> bool { return e == FileNotFound; }, + [](int, Error const &) -> bool { throw 0; }, [](int, Error &&) -> bool { throw 0; }, + [](int, Error const &&) -> bool { throw 0; })) // .error()); - CHECK(std::as_const(s) - .transform_error( // - fn::overload([](Error &) -> bool { throw 0; }, - [](Error const &e) -> bool { return e == FileNotFound; }, - [](Error &&) -> bool { throw 0; }, [](Error const &&) -> bool { throw 0; })) // - .error()); - CHECK(std::move(std::as_const(s)) - .transform_error( // - fn::overload([](Error &) -> bool { throw 0; }, [](Error const &) -> bool { throw 0; }, - [](Error &&) -> bool { throw 0; }, - [](Error const &&e) -> bool { return e == FileNotFound; })) // - .error()); - CHECK(std::move(s) - .transform_error( // - fn::overload([](Error &) -> bool { throw 0; }, [](Error const &) -> bool { throw 0; }, - [](Error &&e) -> bool { return e == FileNotFound; }, - [](Error const &&) -> bool { throw 0; })) // - .error()); - } - - WHEN("pack error") - { - fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; - CHECK(s.transform_error( // - fn::overload([](int, Error &e) -> bool { return e == FileNotFound; }, - [](int, Error const &) -> bool { throw 0; }, [](int, Error &&) -> bool { throw 0; }, - [](int, Error const &&) -> bool { throw 0; })) // - .error()); - CHECK( - std::as_const(s) + CHECK(std::as_const(s) .transform_error( // fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &e) -> bool { return e == FileNotFound; }, [](int, Error &&) -> bool { throw 0; }, [](int, Error const &&) -> bool { throw 0; })) // .error()); - CHECK(std::move(std::as_const(s)) - .transform_error( // - fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, - [](int, Error &&) -> bool { throw 0; }, - [](int, Error const &&e) -> bool { return e == FileNotFound; })) // - .error()); - CHECK(std::move(s) - .transform_error( // - fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, - [](int, Error &&e) -> bool { return e == FileNotFound; }, - [](int, Error const &&) -> bool { throw 0; })) // - .error()); - } + CHECK(std::move(std::as_const(s)) + .transform_error( // + fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, + [](int, Error &&) -> bool { throw 0; }, + [](int, Error const &&e) -> bool { return e == FileNotFound; })) // + .error()); + CHECK(std::move(s) + .transform_error( // + fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, + [](int, Error &&e) -> bool { return e == FileNotFound; }, + [](int, Error const &&) -> bool { throw 0; })) // + .error()); } WHEN("void") { - fn::expected s{}; - CHECK(s.transform_error([](auto) -> bool { throw 0; }).has_value()); - CHECK(std::as_const(s).transform_error([](auto) -> bool { throw 0; }).has_value()); - CHECK(fn::expected{}.transform_error([](auto) -> bool { throw 0; }).has_value()); - CHECK(std::move(std::as_const(s)).transform_error([](auto) -> bool { throw 0; }).has_value()); - - WHEN("error") - { - fn::expected s{::pfn::unexpect, FileNotFound}; - CHECK(s.transform_error( // - fn::overload([](Error &) -> bool { return true; }, [](Error const &) -> bool { throw 0; }, - [](Error &&) -> bool { throw 0; }, [](Error const &&) -> bool { throw 0; })) - .error()); - CHECK(std::as_const(s) - .transform_error( // - fn::overload([](Error &) -> bool { throw 0; }, [](Error const &) -> bool { return true; }, - [](Error &&) -> bool { throw 0; }, [](Error const &&) -> bool { throw 0; })) - .error()); - CHECK(std::move(std::as_const(s)) - .transform_error( // - fn::overload([](Error &) -> bool { throw 0; }, [](Error const &) -> bool { throw 0; }, - [](Error &&) -> bool { throw 0; }, [](Error const &&) -> bool { return true; })) - .error()); - CHECK(std::move(s) - .transform_error( // - fn::overload([](Error &) -> bool { throw 0; }, [](Error const &) -> bool { throw 0; }, - [](Error &&) -> bool { return true; }, [](Error const &&) -> bool { throw 0; })) - .error()); - } - - WHEN("pack error") - { - fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; - CHECK(s.transform_error( // - fn::overload([](int, Error &) -> bool { return true; }, [](int, Error const &) -> bool { throw 0; }, - [](int, Error &&) -> bool { throw 0; }, [](int, Error const &&) -> bool { throw 0; })) - .error()); - CHECK(std::as_const(s) - .transform_error( // - fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { return true; }, - [](int, Error &&) -> bool { throw 0; }, [](int, Error const &&) -> bool { throw 0; })) - .error()); - CHECK(std::move(std::as_const(s)) - .transform_error( // - fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, - [](int, Error &&) -> bool { throw 0; }, - [](int, Error const &&) -> bool { return true; })) - .error()); - CHECK(std::move(s) - .transform_error( // - fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, - [](int, Error &&) -> bool { return true; }, - [](int, Error const &&) -> bool { throw 0; })) - .error()); - } + fn::expected> s{::pfn::unexpect, fn::pack{12, FileNotFound}}; + CHECK(s.transform_error( // + fn::overload([](int, Error &) -> bool { return true; }, [](int, Error const &) -> bool { throw 0; }, + [](int, Error &&) -> bool { throw 0; }, [](int, Error const &&) -> bool { throw 0; })) + .error()); + CHECK(std::as_const(s) + .transform_error( // + fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { return true; }, + [](int, Error &&) -> bool { throw 0; }, [](int, Error const &&) -> bool { throw 0; })) + .error()); + CHECK( + std::move(std::as_const(s)) + .transform_error( // + fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, + [](int, Error &&) -> bool { throw 0; }, [](int, Error const &&) -> bool { return true; })) + .error()); + CHECK( + std::move(s) + .transform_error( // + fn::overload([](int, Error &) -> bool { throw 0; }, [](int, Error const &) -> bool { throw 0; }, + [](int, Error &&) -> bool { return true; }, [](int, Error const &&) -> bool { throw 0; })) + .error()); } } From 4788cee57b84b4d42525f75a4a427bef1c56f4f2 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 5 Jun 2026 13:26:27 +0000 Subject: [PATCH 17/21] Cover move-only and immovable value/error types in expected tests Assisted-by: Claude:claude-opus-4-8 --- tests/pfn/expected.cpp | 169 +++++++++++++++++++++++++++++++++++- tests/util/helper_types.hpp | 89 +++++++++++++++++++ 2 files changed, 257 insertions(+), 1 deletion(-) diff --git a/tests/pfn/expected.cpp b/tests/pfn/expected.cpp index db2b7e33..73fea46d 100644 --- a/tests/pfn/expected.cpp +++ b/tests/pfn/expected.cpp @@ -891,7 +891,50 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_copy_constructible_v>); static_assert(not std::is_move_constructible_v>); static_assert(not std::is_move_constructible_v>); - SUCCEED(); + + // An immovable (non-copyable, non-movable) value or error type is still + // usable: the expected can be built in place and observed, even though it is + // itself neither copyable nor movable. + static_assert(not std::is_copy_constructible_v>); + static_assert(not std::is_move_constructible_v>); + static_assert(not std::is_copy_constructible_v>); + static_assert(not std::is_move_constructible_v>); + { + expected const a(std::in_place, 6, 7); + CHECK(a.value().v == 6 * 7); + } + { + expected const a(unexpect, 6, 7); + CHECK(a.error().v == 6 * 7); + } + } + + SECTION("move-only value type") + { + using T = expected; + static_assert(not std::is_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not extension || std::is_nothrow_move_constructible_v); + + T a(std::in_place, 7); + T b = std::move(a); + CHECK(b.has_value()); + CHECK(b.value().v == 7 * from_rval); + } + + SECTION("move-only error type") + { + using T = expected; + static_assert(not std::is_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not extension || std::is_nothrow_move_constructible_v); + + T a(unexpect, 7); + T b = std::move(a); + CHECK(not b.has_value()); + CHECK(b.error().v == 7 * from_rval); } SECTION("trivial") @@ -2097,6 +2140,49 @@ TEST_CASE("expected non void", "[expected][polyfill]") } } + SECTION("move-only value type") + { + // Copy-assignment is deleted, so only rval (move) assignment is available. + using T = expected; + static_assert(not std::is_copy_assignable_v); + static_assert(std::is_move_assignable_v); + + SECTION("value to value") + { + T a(std::in_place, 3); + a = T(std::in_place, 7); + CHECK(a.value().v == 7 * from_rval); + } + + SECTION("error to value") + { + T a(unexpect, Error::unknown); + a = T(std::in_place, 7); + CHECK(a.value().v == 7 * from_rval); + } + } + + SECTION("move-only error type") + { + using T = expected; + static_assert(not std::is_copy_assignable_v); + static_assert(std::is_move_assignable_v); + + SECTION("error to error") + { + T a(unexpect, 3); + a = T(unexpect, 7); + CHECK(a.error().v == 7 * from_rval); + } + + SECTION("value to error") + { + T a(std::in_place, 5); + a = T(unexpect, 7); + CHECK(a.error().v == 7 * from_rval); + } + } + SECTION("constexpr") { SECTION("nothrow move") @@ -2238,6 +2324,21 @@ TEST_CASE("expected non void", "[expected][polyfill]") } } + SECTION("move-only value type") + { + // emplace constructs in place, so it needs neither copy nor move of the value. + expected, Error> a(unexpect, Error::file_not_found); + a.emplace(7); + CHECK(a.value().v == 7); + } + + SECTION("immovable value type") + { + expected, Error> a(unexpect, Error::unknown); + a.emplace(6, 7); + CHECK(a.value().v == 6 * 7); + } + SECTION("constexpr") { using T = expected; @@ -3761,6 +3862,34 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("copy, move and dtor") { + SECTION("immovable error type") + { + // An immovable (non-copyable, non-movable) error type leaves the expected + // neither copyable nor movable, yet it can still be built in place and observed. + static_assert(not std::is_copy_constructible_v>); + static_assert(not std::is_move_constructible_v>); + + expected const a(unexpect, 6, 7); + CHECK(a.error().v == 6 * 7); + + expected const b(std::in_place); + CHECK(b.has_value()); + } + + SECTION("move-only error type") + { + using T = expected; + static_assert(not std::is_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not extension || std::is_nothrow_move_constructible_v); + + T a(unexpect, 7); + T b = std::move(a); + CHECK(not b.has_value()); + CHECK(b.error().v == 7 * from_rval); + } + SECTION("trivial") { using T = expected; @@ -4458,6 +4587,28 @@ TEST_CASE("expected void", "[expected_void][polyfill]") } } + SECTION("move-only error type") + { + // Copy-assignment is deleted, so only rval (move) assignment is available. + using T = expected; + static_assert(not std::is_copy_assignable_v); + static_assert(std::is_move_assignable_v); + + SECTION("error to error") + { + T a(unexpect, 3); + a = T(unexpect, 7); + CHECK(a.error().v == 7 * from_rval); + } + + SECTION("value to error") + { + T a(std::in_place); + a = T(unexpect, 7); + CHECK(a.error().v == 7 * from_rval); + } + } + SECTION("constexpr") { using T = expected; @@ -4542,6 +4693,22 @@ TEST_CASE("expected void", "[expected_void][polyfill]") #endif } + SECTION("move-only error type") + { + // emplace() destroys the error and sets the value state, so the error type + // need be neither copyable nor movable. + expected a(unexpect, 7); + a.emplace(); + CHECK(a.has_value()); + } + + SECTION("immovable error type") + { + expected a(unexpect, 6, 7); + a.emplace(); + CHECK(a.has_value()); + } + SECTION("constexpr") { using T = expected; diff --git a/tests/util/helper_types.hpp b/tests/util/helper_types.hpp index 18e9a072..79ec3809 100644 --- a/tests/util/helper_types.hpp +++ b/tests/util/helper_types.hpp @@ -183,3 +183,92 @@ template constexpr void swap(helper_t &l, helper_t &r) } using helper = helper_t<0>; + +// Move-only counterpart to helper_t: copy construction/assignment are deleted; +// the move operations are witnessed by a prime factor and parameterized by V with +// the same scheme as helper_t (V in [3,5) throws on move-construct, V in [40,41) +// throws on move-assign, V < 8 throws on the value constructor). +template struct helper_move_only_t { + int v = {}; + + helper_move_only_t() = delete; + constexpr ~helper_move_only_t() noexcept {}; + + helper_move_only_t(helper_move_only_t const &) = delete; + helper_move_only_t &operator=(helper_move_only_t const &) = delete; + + constexpr helper_move_only_t(helper_move_only_t &&o) noexcept(V < 3 || V >= 5) : v(o.v) + { + v *= from_rval; + if constexpr (V >= 3 && V < 5) { + if (v == 0) + throw std::runtime_error("invalid input"); + } + } + constexpr helper_move_only_t(helper_move_only_t const &&o) noexcept : v(o.v) { v *= from_rval_const; } + + constexpr helper_move_only_t &operator=(helper_move_only_t &&o) noexcept(V < 40 || V >= 41) + { + if constexpr (V >= 40 && V < 41) { + if (o.v == 0) + throw std::runtime_error("invalid input"); + } + v = o.v; + v *= from_rval; + return *this; + } + + helper_move_only_t(std::integral auto... a) noexcept(V >= 8) + requires(sizeof...(a) > 0) // intentionally implicit when sizeof...(a) == 1 + : v((1 * ... * a)) + { + if constexpr (V < 8) { + if (v == 0) + throw std::runtime_error("invalid input"); + } + } + + constexpr bool operator==(helper_move_only_t const &) const noexcept = default; +}; + +static_assert(not std::is_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(not std::is_nothrow_move_constructible_v>); // throwing move ctor +static_assert(not std::is_copy_assignable_v>); +static_assert(std::is_move_assignable_v>); +static_assert(not std::is_nothrow_move_assignable_v>); // throwing move assign + +// Non-copyable AND non-movable: can only be constructed in place (from a value) +// and observed; V < 8 makes the value constructor throw on a zero result. +template struct helper_immovable_t { + int v = {}; + + helper_immovable_t() = delete; + constexpr ~helper_immovable_t() noexcept {}; + + helper_immovable_t(helper_immovable_t const &) = delete; + helper_immovable_t(helper_immovable_t &&) = delete; + helper_immovable_t &operator=(helper_immovable_t const &) = delete; + helper_immovable_t &operator=(helper_immovable_t &&) = delete; + + helper_immovable_t(std::integral auto... a) noexcept(V >= 8) + requires(sizeof...(a) > 0) // intentionally implicit when sizeof...(a) == 1 + : v((1 * ... * a)) + { + if constexpr (V < 8) { + if (v == 0) + throw std::runtime_error("invalid input"); + } + } + + constexpr bool operator==(helper_immovable_t const &) const noexcept = default; +}; + +static_assert(not std::is_copy_constructible_v>); +static_assert(not std::is_move_constructible_v>); +static_assert(not std::is_copy_assignable_v>); +static_assert(not std::is_move_assignable_v>); + +using helper_move_only = helper_move_only_t<0>; +using helper_immovable = helper_immovable_t<0>; From 9c20049f7f4f0e6074c63a502b8389eda423e31b Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 5 Jun 2026 14:10:57 +0000 Subject: [PATCH 18/21] Pin expected forwarding-ctor same-type exclusion against rot The value-forwarding ctor expected(U&&) must reject a same-type U, or it out-competes copy/move for same-type construction and silently hijacks them (for a non-const lvalue, U&& deduces an exact-match expected&, beating the copy ctor's const expected&). The guard is _can_convert's `not is_same_v, remove_cvref_t>`. For a normal T it is backstopped by is_constructible_v; for a greedy T (constructible from expected) it is the only thing standing, and it leans on the Policy::type indirection resolving correctly -- a piece that could rot with no diagnostic, especially for fn::expected's own policy. greedy_t is constructible from anything yet non-copyable/non-movable, so an expected constructible from itself could only be that hijack; the static_asserts pin it at compile time. Verified live by neutralising the exclusion (the asserts fire, including for fn::expected via the nested polyfill suite) and holds against std::expected (validation build). SonarQube flags this ctor for the same reason but cannot see through the _can_convert trait -- a false positive; this test is the durable guard rather than a duplicated constraint. Assisted-by: Claude:claude-opus-4-8 --- include/pfn/expected.hpp | 2 +- tests/pfn/expected.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index f9cf4c19..d6ec4f41 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -1118,7 +1118,7 @@ template class expected : private detail::_storage> - constexpr explicit(not ::std::is_convertible_v) expected(U &&v) // + constexpr explicit(not ::std::is_convertible_v) expected(U &&v) // NOSONAR cpp:S6458 _can_convert excludes self noexcept(::std::is_nothrow_constructible_v) // extension requires(_base::template _can_convert::value) : _base(::std::in_place, FWD(v)) diff --git a/tests/pfn/expected.cpp b/tests/pfn/expected.cpp index 73fea46d..d1145312 100644 --- a/tests/pfn/expected.cpp +++ b/tests/pfn/expected.cpp @@ -35,6 +35,13 @@ using pfn::unexpected; enum Error { unknown = 1, file_not_found = 5 }; +struct greedy_t { + greedy_t() = delete; + greedy_t(greedy_t const &) = delete; + greedy_t(greedy_t &&) = delete; + template constexpr greedy_t(U &&) noexcept {} +}; + TEST_CASE("bad_expected_access", "[expected][polyfill][bad_expected_access]") { std::string const e1 = "bad access to expected"; @@ -720,6 +727,14 @@ TEST_CASE("expected non void", "[expected][polyfill]") T const c(helper(13)); CHECK(c.value().v == 13 * from_rval); + + using H = expected; + static_assert(not std::is_copy_constructible_v); + static_assert(not std::is_move_constructible_v); + static_assert(not std::is_constructible_v); + static_assert(not std::is_constructible_v); + static_assert(not std::is_constructible_v); + static_assert(not std::is_constructible_v); } SECTION("from unexpected rval") From e33710f1b885e18c10311ebfbf466473d587865b Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 5 Jun 2026 15:54:50 +0000 Subject: [PATCH 19/21] Make trivial expected move ctors noexcept; NOSONAR move/swap (S5018) The trivial defaulted move constructors are constrained to is_trivially_move_constructible T/E, which is always nothrow, so they are now unconditionally noexcept; their non-trivial sibling overloads keep the conditional spec. Together they reproduce the standard's single conditional move ctor ([expected.object.cons]/15, [expected.void.cons]/7) for the trivial case, so triviality and the observable exception spec are unchanged. Move assignment and swap keep their conditional noexcept(...) and are marked // NOSONAR cpp:S5018: forcing them unconditionally noexcept would be a bug -- they handle possibly-throwing T/E and must let the exception propagate, as mandated by [expected.object.assign]/9, [expected.object.swap]/4, [expected.void.assign]/8, [expected.void.swap]/4. All six specs verified against the working draft. Assisted-by: Claude:claude-opus-4-8 --- include/pfn/expected.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index d6ec4f41..874a5ff9 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -1181,7 +1181,7 @@ template class expected : private detail::_storage && ::std::is_move_constructible_v && ::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v) = default; @@ -1239,7 +1239,7 @@ template class expected : private detail::_storage && ::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && ::std::is_move_assignable_v && ::std::is_move_constructible_v @@ -1254,9 +1254,9 @@ template class expected : private detail::_storage && ::std::is_nothrow_swappable_v - && ::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) + constexpr void swap(expected &rhs) // NOSONAR cpp:S5018 standard mandated `noexcept` spec. + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v + && ::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) requires(::std::is_swappable_v && ::std::is_swappable_v && ::std::is_move_constructible_v && ::std::is_move_constructible_v && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) @@ -1470,7 +1470,7 @@ template class expected : private detail::_storage && ::std::is_trivially_move_constructible_v) // = default; constexpr expected(expected &&s) // @@ -1511,7 +1511,7 @@ template class expected : private detail::_storage && ::std::is_nothrow_move_constructible_v) // required requires(::std::is_move_constructible_v && ::std::is_move_assignable_v) { @@ -1523,7 +1523,7 @@ template class expected : private detail::_storage && ::std::is_nothrow_swappable_v) requires(::std::is_swappable_v && ::std::is_move_constructible_v) { From bc9884a0d072a188f7f19fdaf9a5d274b61d64d1 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 5 Jun 2026 16:32:14 +0000 Subject: [PATCH 20/21] NOSONAR S6031 on expected's unexpected ctors (forward is spec wording) The unexpected const& constructor uses std::forward(g.error()) -- a no-op identity cast that cpp:S6031 flags, but it is exactly the standard's Effects wording: [expected.object.cons]/29 specifies "... with std::forward(e.error())", with GF = const G& for this overload. pfn mirrors the standard verbatim, so this is a false positive -- suppress rather than rewrite. (Reverts the earlier std::move/g.error() rewrite, which deviated from the spec.) Assisted-by: Claude:claude-opus-4-8 --- include/pfn/expected.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 874a5ff9..79cf7181 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -1128,7 +1128,7 @@ template class expected : private detail::_storage) expected(unexpected const &g) // noexcept(::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v) - : _base(unexpect, ::std::forward(g.error())) + : _base(unexpect, ::std::forward(g.error())) // NOSONAR cpp:S6031 forward per the standard { } template @@ -1432,7 +1432,7 @@ template class expected : private detail::_storage) expected(unexpected const &g) // noexcept(::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v) - : _base(unexpect, ::std::forward(g.error())) + : _base(unexpect, ::std::forward(g.error())) // NOSONAR cpp:S6031 forward per the standard { } template From c5ebd000f41c5c3a08c6d054d9bb35157dcfacdb Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Fri, 5 Jun 2026 21:22:49 +0000 Subject: [PATCH 21/21] Simplify clang-18 _or_else workaround to return std::move(type{...}) Equivalent to the named-local form -- materialise the value-state result, then move-construct the return, avoiding the direct-prvalue guaranteed-elision path that clang 15-18 miscompile -- but one line, with the required move semantics visible at the call site. No -Wpessimizing-move because `type` is dependent. Verified on clang-18: compiles -Wall -Wextra -Werror clean and the _or_else suite passes at -O1/-O2/-O3 (the prvalue form fails it). gcc and clang-19+ are unaffected -- the #else branch is unchanged. Assisted-by: Claude:claude-opus-4-8 --- include/fn/expected.hpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/include/fn/expected.hpp b/include/fn/expected.hpp index dfce830e..a8decb3c 100644 --- a/include/fn/expected.hpp +++ b/include/fn/expected.hpp @@ -132,12 +132,8 @@ template struct _storage : ::pfn::detail::_storage