Skip to content
Merged
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Implemented C++ Standard Proposals:
- [x] [P0323R12](https://wg21.link/p0323r12) `<expected>`
- [x] [P2505R5](https://wg21.link/p2505r5) Monadic Functions For expected
- [x] [P2549R1](https://wg21.link/p2549r1) `std::unexpected<E>` should have `error()` as member accessor
- [x] [P3379R0](https://wg21.link/p3379r0) Constrain `std::expected` equality operators

Implemented LWG Issues:

Expand All @@ -25,6 +26,7 @@ Implemented LWG Issues:
- [x] [LWG-4031](https://wg21.link/lwg4031) `bad_expected_access<void>` member functions should be noexcept
- [x] [LWG-4222](https://wg21.link/lwg4222) `expected` constructor from a single value missing a constraint
- [x] [LWG-4025](https://wg21.link/lwg4025) Move assignment operator of `std::expected<cv void, E>` should not be conditionally deleted
- [x] [LWG-4366](https://wg21.link/lwg4366) Heterogeneous comparison of `expected` may be ill-formed

Enhancements:

Expand Down
184 changes: 128 additions & 56 deletions include/zeus/expected.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,42 @@ inline constexpr bool is_move_assignable_or_void_v = is_void_or_v<T, std::is_mov
template<class From, class To>
inline constexpr bool is_nothrow_convertible_v = noexcept(static_cast<To>(std::declval<From>()));

template<class Lhs, class Rhs, class = void>
struct is_equality_result_convertible_to_bool : std::false_type
{
};

template<class Lhs, class Rhs>
struct is_equality_result_convertible_to_bool<Lhs, Rhs, std::void_t<decltype(std::declval<const Lhs &>() == std::declval<const Rhs &>())>>
: std::is_convertible<decltype(std::declval<const Lhs &>() == std::declval<const Rhs &>()), bool>
{
};

template<class Lhs, class Rhs>
inline constexpr bool is_equality_result_convertible_to_bool_v = is_equality_result_convertible_to_bool<Lhs, Rhs>::value;

constexpr bool implicitly_convert_to_bool(bool value) noexcept
{
return value;
}

template<class Lhs, class Rhs, class = void>
struct is_nothrow_equality_result_convertible_to_bool : std::false_type
{
};

template<class Lhs, class Rhs>
struct is_nothrow_equality_result_convertible_to_bool<
Lhs,
Rhs,
std::void_t<decltype(implicitly_convert_to_bool(std::declval<const Lhs &>() == std::declval<const Rhs &>()))>
> : std::bool_constant<noexcept(implicitly_convert_to_bool(std::declval<const Lhs &>() == std::declval<const Rhs &>()))>
{
};

template<class Lhs, class Rhs>
inline constexpr bool is_nothrow_equality_result_convertible_to_bool_v = is_nothrow_equality_result_convertible_to_bool<Lhs, Rhs>::value;

} // namespace expected_detail

template<class E>
Expand Down Expand Up @@ -2037,9 +2073,16 @@ class expected
}

template<class T2, class E2>
[[nodiscard]] friend constexpr std::enable_if_t<!std::is_void_v<T2>, bool> operator==(
const expected &x, const expected<T2, E2> &y
) noexcept(noexcept(*x == *y) && noexcept(x.error() == y.error()))
[[nodiscard]] friend constexpr std::enable_if_t<
!std::is_void_v<T2> &&
expected_detail::is_equality_result_convertible_to_bool_v<T, T2> &&
expected_detail::is_equality_result_convertible_to_bool_v<E, E2>,
bool
>
operator==(const expected &x, const expected<T2, E2> &y) noexcept(
expected_detail::is_nothrow_equality_result_convertible_to_bool_v<T, T2> &&
expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>
)
{
if (x.has_value() != y.has_value())
{
Expand All @@ -2056,20 +2099,26 @@ class expected
}
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
template<class T2, class E2>
[[nodiscard]] friend constexpr std::enable_if_t<!std::is_void_v<T2>, bool> operator!=(
const expected &x, const expected<T2, E2> &y
) noexcept(noexcept(x == y))
[[nodiscard]] friend constexpr std::enable_if_t<
!std::is_void_v<T2> &&
expected_detail::is_equality_result_convertible_to_bool_v<T, T2> &&
expected_detail::is_equality_result_convertible_to_bool_v<E, E2>,
bool
> operator!=(const expected &x, const expected<T2, E2> &y) noexcept(noexcept(x == y))
{
return !(x == y);
}
#endif

template<class T2>
[[nodiscard]] friend constexpr bool operator==(const expected &x, const T2 &v) noexcept(noexcept(*x == v))
[[nodiscard]] friend constexpr std::enable_if_t<
!expected_detail::is_specialization_v<T2, zeus::expected> && expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
bool
> operator==(const expected &x, const T2 &v) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<T, T2>)
{
if (x.has_value())
{
return static_cast<bool>(*x == v);
return *x == v;
}
else
{
Expand All @@ -2078,54 +2127,37 @@ class expected
}
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
template<class T2>
[[nodiscard]] friend constexpr bool operator!=(const expected &x, const T2 &v) noexcept(noexcept(x == v))
[[nodiscard]] friend constexpr std::enable_if_t<
!expected_detail::is_specialization_v<T2, zeus::expected> && expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
bool
> operator!=(const expected &x, const T2 &v) noexcept(noexcept(x == v))
{
return !(x == v);
}
template<class T2>
[[nodiscard]] friend constexpr std::enable_if_t<!expected_detail::is_specialization_v<T2, zeus::expected>, bool> operator==(
const T2 &v, const expected &x
) noexcept(noexcept(x == v))
{
return x == v;
}
template<class T2>
[[nodiscard]] friend constexpr std::enable_if_t<!expected_detail::is_specialization_v<T2, zeus::expected>, bool> operator!=(
const T2 &v, const expected &x
) noexcept(noexcept(x == v))
{
return x != v;
}
#endif

template<class E2>
[[nodiscard]] friend constexpr bool operator==(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x.error() == e.error()))
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator==(
const expected &x, const unexpected<E2> &e
) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>)
{
if (x.has_value())
{
return false;
}
else
{
return static_cast<bool>(x.error() == e.error());
return x.error() == e.error();
}
}
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
template<class E2>
[[nodiscard]] friend constexpr bool operator!=(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x == e))
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator!=(
const expected &x, const unexpected<E2> &e
) noexcept(noexcept(x == e))
{
return !(x == e);
}
template<class E2>
[[nodiscard]] friend constexpr bool operator==(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
{
return x == e;
}
template<class E2>
[[nodiscard]] friend constexpr bool operator!=(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
{
return x != e;
}
#endif
};

Expand Down Expand Up @@ -2743,60 +2775,100 @@ class expected<void, E>
}

template<class T2, class E2>
[[nodiscard]] friend constexpr std::enable_if_t<std::is_void_v<T2>, bool> operator==(
const expected &x, const expected<T2, E2> &y
) noexcept(noexcept(x.error() == y.error()))
[[nodiscard]] friend constexpr std::
enable_if_t<std::is_void_v<T2> && expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool>
operator==(
const expected &x, const expected<T2, E2> &y
) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>)
{
if (x.has_value() != y.has_value())
{
return false;
}
else if (x.has_value())
{
return true;
}
else
{
return x.has_value() || static_cast<bool>(x.error() == y.error());
return x.error() == y.error();
}
}
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
template<class T2, class E2>
[[nodiscard]] friend constexpr std::enable_if_t<std::is_void_v<T2>, bool> operator!=(
const expected &x, const expected<T2, E2> &y
) noexcept(noexcept(x == y))
[[nodiscard]] friend constexpr std::
enable_if_t<std::is_void_v<T2> && expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator!=(
const expected &x, const expected<T2, E2> &y
) noexcept(noexcept(x == y))
{
return !(x == y);
}
#endif

template<class E2>
[[nodiscard]] friend constexpr bool operator==(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x.error() == e.error()))
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator==(
const expected &x, const unexpected<E2> &e
) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>)
{
if (x.has_value())
{
return false;
}
else
{
return static_cast<bool>(x.error() == e.error());
return x.error() == e.error();
}
}
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
template<class E2>
[[nodiscard]] friend constexpr bool operator!=(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x == e))
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator!=(
const expected &x, const unexpected<E2> &e
) noexcept(noexcept(x == e))
{
return !(x == e);
}
template<class E2>
[[nodiscard]] friend constexpr bool operator==(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
{
return x == e;
}
template<class E2>
[[nodiscard]] friend constexpr bool operator!=(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
{
return x != e;
}
#endif
};

#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
// Deduce the expected operand to reject conversions exposed by MSVC's permissive C++17 hidden-friend lookup.
template<class T, class E, class T2>
[[nodiscard]] constexpr std::enable_if_t<
!std::is_void_v<T> &&
!expected_detail::is_specialization_v<T2, zeus::expected> &&
expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
bool
>
operator==(const T2 &v, const expected<T, E> &x) noexcept(noexcept(x == v))
{
return x == v;
}

template<class T, class E, class T2>
[[nodiscard]] constexpr std::enable_if_t<
!std::is_void_v<T> &&
!expected_detail::is_specialization_v<T2, zeus::expected> &&
expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
bool
>
operator!=(const T2 &v, const expected<T, E> &x) noexcept(noexcept(x == v))
{
return x != v;
}

template<class T, class E, class E2, std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>> * = nullptr>
[[nodiscard]] constexpr bool operator==(const unexpected<E2> &e, const expected<T, E> &x) noexcept(noexcept(x == e))
{
return x == e;
}

template<class T, class E, class E2, std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>> * = nullptr>
[[nodiscard]] constexpr bool operator!=(const unexpected<E2> &e, const expected<T, E> &x) noexcept(noexcept(x == e))
{
return x != e;
}
#endif

// standalone swap for void value type
template<class E, std::enable_if_t<std::is_move_constructible_v<E> && std::is_swappable_v<E>> * = nullptr>
constexpr void swap(expected<void, E> &lhs, expected<void, E> &rhs) noexcept(noexcept(lhs.swap(rhs)))
Expand Down
3 changes: 3 additions & 0 deletions tests/test_expected/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ set(SOURCES
monadic_tests.cpp
noexcept_tests.cpp
equality_tests.cpp
equality_noexcept_tests.cpp
p3379_tests.cpp
lwg_3886_tests.cpp
lwg_4031_tests.cpp
lwg_4222_tests.cpp
lwg_4025_tests.cpp
lwg_4366_tests.cpp
)

find_package(Catch2 3 REQUIRED)
Expand Down
Loading
Loading