Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 23 additions & 26 deletions include/pfn/expected.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
#define ASSERT(...) assert((__VA_ARGS__) == true)
#endif

// This header is a polyfill for std::expected, tracking the C++ working draft
// and changes planned for the future standard revisions. As a deliberate extension,
// member functions whose `noexcept` specification is left unspecified by the
// standard are given a `noexcept` clause derived from the properties of the
// underlying types T, E and (where applicable) invocable arguments. Each such
// clause is marked inline with a "// extension" trailing comment.

namespace pfn {

template <class E> class bad_expected_access;
Expand Down Expand Up @@ -137,9 +144,11 @@ template <class E> class unexpected {
swap(e_, other.e_);
}

template <class E2> constexpr friend bool operator==(unexpected const &x, unexpected<E2> const &y)
template <class E2>
constexpr friend bool operator==(unexpected const &x, unexpected<E2> const &y) //
noexcept(noexcept(static_cast<bool>(x.error() == y.error()))) // extension
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

include/pfn/expected.hpp:149 — The noexcept computation uses static_cast<bool>(x.error() == y.error()); this can succeed even when the comparison result is only explicitly convertible to bool, while the return statement still requires an implicit conversion to bool. Consider aligning the noexcept/participation check with implicit convertibility to avoid surprising hard errors in some generic contexts.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

{
return x.e_ == y.e_;
return x.error() == y.error();
}

constexpr friend void swap(unexpected &x, unexpected &y) noexcept(noexcept(x.swap(y)))
Expand Down Expand Up @@ -216,7 +225,7 @@ template <class T, class E> class expected {
not ::std::is_same_v<expected, ::std::remove_cvref_t<U>> //
&& not detail::_is_some_unexpected<::std::remove_cvref_t<U>> //
&& ::std::is_constructible_v<T, U> //
&& ::std::is_assignable_v<T, U> //
&& ::std::is_assignable_v<T &, U> //
&& (::std::is_nothrow_constructible_v<T, U> //
|| ::std::is_nothrow_move_constructible_v<T> //
|| ::std::is_nothrow_move_constructible_v<E>)>;
Expand Down Expand Up @@ -575,9 +584,8 @@ template <class T, class E> class expected {
}

template <class U = T>
constexpr expected &operator=(U &&s) //
noexcept(::std::is_nothrow_assignable_v<T, U> && ::std::is_nothrow_constructible_v<T, U>
&& (::std::is_nothrow_move_constructible_v<T> || ::std::is_nothrow_move_constructible_v<E>)) // extension
constexpr expected &operator=(U &&s) //
noexcept(::std::is_nothrow_assignable_v<T &, U> && ::std::is_nothrow_constructible_v<T, U>) // extension
requires(_can_convert_assign<U>::value)
{
if (set_) {
Expand All @@ -591,8 +599,8 @@ template <class T, class E> class expected {

template <class G>
constexpr expected &operator=(unexpected<G> const &s) //
noexcept(::std::is_nothrow_assignable_v<E, G const &> && ::std::is_nothrow_constructible_v<E, G const &>
&& (::std::is_nothrow_move_constructible_v<E> || ::std::is_nothrow_move_constructible_v<T>)) // extension
noexcept(::std::is_nothrow_assignable_v<E &, G const &>
&& ::std::is_nothrow_constructible_v<E, G const &>) // extension
requires(::std::is_constructible_v<E, G const &> && ::std::is_assignable_v<E &, G const &>
&& (::std::is_nothrow_constructible_v<E, G const &> || ::std::is_nothrow_move_constructible_v<T>
|| ::std::is_nothrow_move_constructible_v<E>))
Expand All @@ -607,9 +615,8 @@ template <class T, class E> class expected {
}

template <class G>
constexpr expected &operator=(unexpected<G> &&s) //
noexcept(::std::is_nothrow_assignable_v<E, G> && ::std::is_nothrow_constructible_v<E, G>
&& (::std::is_nothrow_move_constructible_v<E> || ::std::is_nothrow_move_constructible_v<T>)) // extension
constexpr expected &operator=(unexpected<G> &&s) //
noexcept(::std::is_nothrow_assignable_v<E &, G> && ::std::is_nothrow_constructible_v<E, G>) // extension
requires(::std::is_constructible_v<E, G> && ::std::is_assignable_v<E &, G>
&& (::std::is_nothrow_constructible_v<E, G> || ::std::is_nothrow_move_constructible_v<T>
|| ::std::is_nothrow_move_constructible_v<E>))
Expand Down Expand Up @@ -699,7 +706,7 @@ template <class T, class E> class expected {
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_; }
constexpr bool has_error() const noexcept { return !set_; } // P3798
constexpr T const &value() const &
{
static_assert(::std::is_copy_constructible_v<E>);
Expand Down Expand Up @@ -974,16 +981,6 @@ class expected<T, E> {
template <class U, class G> using _can_move_convert = _can_convert_detail<U, G, U, G>;
template <class U, class G> friend class expected;

template <class U>
using _can_convert = ::std::bool_constant< //
not ::std::is_same_v<::std::remove_cvref_t<U>, ::std::in_place_t> //
&& not ::std::is_same_v<::std::remove_cvref_t<U>, unexpect_t> // LWG4222
&& not ::std::is_same_v<expected, ::std::remove_cvref_t<U>> //
&& not detail::_is_some_unexpected<::std::remove_cvref_t<U>> //
&& ::std::is_constructible_v<T, U> //
&& (not ::std::is_same_v<bool, ::std::remove_cv_t<T>> //
|| not detail::_is_some_expected<::std::remove_cvref_t<U>>)>;

template <typename Self, typename Fn>
static constexpr auto _and_then(Self &&self, Fn &&fn) //
noexcept(::std::is_nothrow_invocable_v<Fn> && ::std::is_nothrow_constructible_v<E, decltype(FWD(self).error())>)
Expand Down Expand Up @@ -1203,7 +1200,7 @@ class expected<T, E> {

template <class G>
constexpr expected &operator=(unexpected<G> const &s) //
noexcept(::std::is_nothrow_assignable_v<E, G const &>
noexcept(::std::is_nothrow_assignable_v<E &, G const &>
&& ::std::is_nothrow_constructible_v<E, G const &>) // extension
requires(::std::is_constructible_v<E, G const &> && ::std::is_assignable_v<E &, G const &>)
{
Expand All @@ -1218,8 +1215,8 @@ class expected<T, E> {
}

template <class G>
constexpr expected &operator=(unexpected<G> &&s) //
noexcept(::std::is_nothrow_assignable_v<E, G> && ::std::is_nothrow_constructible_v<E, G>) // extension
constexpr expected &operator=(unexpected<G> &&s) //
noexcept(::std::is_nothrow_assignable_v<E &, G> && ::std::is_nothrow_constructible_v<E, G>) // extension
requires(::std::is_constructible_v<E, G> && ::std::is_assignable_v<E &, G>)
{
if (set_) {
Expand Down Expand Up @@ -1276,7 +1273,7 @@ class expected<T, E> {
// [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_; }
constexpr bool has_error() const noexcept { return !set_; } // P3798
constexpr void operator*() const noexcept { ASSERT(set_); }
constexpr void value() const &
{
Expand Down
Loading
Loading