Skip to content

Commit 38827dd

Browse files
authored
Merge pull request #31 from zeus-cpp/feat/p3379r0
feat: implement P3379R0 constrained expected equality
2 parents bb3ddaf + fb4b32e commit 38827dd

6 files changed

Lines changed: 556 additions & 56 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Implemented C++ Standard Proposals:
1313
- [x] [P0323R12](https://wg21.link/p0323r12) `<expected>`
1414
- [x] [P2505R5](https://wg21.link/p2505r5) Monadic Functions For expected
1515
- [x] [P2549R1](https://wg21.link/p2549r1) `std::unexpected<E>` should have `error()` as member accessor
16+
- [x] [P3379R0](https://wg21.link/p3379r0) Constrain `std::expected` equality operators
1617

1718
Implemented LWG Issues:
1819

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

2931
Enhancements:
3032

include/zeus/expected.hpp

Lines changed: 128 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,42 @@ inline constexpr bool is_move_assignable_or_void_v = is_void_or_v<T, std::is_mov
130130
template<class From, class To>
131131
inline constexpr bool is_nothrow_convertible_v = noexcept(static_cast<To>(std::declval<From>()));
132132

133+
template<class Lhs, class Rhs, class = void>
134+
struct is_equality_result_convertible_to_bool : std::false_type
135+
{
136+
};
137+
138+
template<class Lhs, class Rhs>
139+
struct is_equality_result_convertible_to_bool<Lhs, Rhs, std::void_t<decltype(std::declval<const Lhs &>() == std::declval<const Rhs &>())>>
140+
: std::is_convertible<decltype(std::declval<const Lhs &>() == std::declval<const Rhs &>()), bool>
141+
{
142+
};
143+
144+
template<class Lhs, class Rhs>
145+
inline constexpr bool is_equality_result_convertible_to_bool_v = is_equality_result_convertible_to_bool<Lhs, Rhs>::value;
146+
147+
constexpr bool implicitly_convert_to_bool(bool value) noexcept
148+
{
149+
return value;
150+
}
151+
152+
template<class Lhs, class Rhs, class = void>
153+
struct is_nothrow_equality_result_convertible_to_bool : std::false_type
154+
{
155+
};
156+
157+
template<class Lhs, class Rhs>
158+
struct is_nothrow_equality_result_convertible_to_bool<
159+
Lhs,
160+
Rhs,
161+
std::void_t<decltype(implicitly_convert_to_bool(std::declval<const Lhs &>() == std::declval<const Rhs &>()))>
162+
> : std::bool_constant<noexcept(implicitly_convert_to_bool(std::declval<const Lhs &>() == std::declval<const Rhs &>()))>
163+
{
164+
};
165+
166+
template<class Lhs, class Rhs>
167+
inline constexpr bool is_nothrow_equality_result_convertible_to_bool_v = is_nothrow_equality_result_convertible_to_bool<Lhs, Rhs>::value;
168+
133169
} // namespace expected_detail
134170

135171
template<class E>
@@ -2037,9 +2073,16 @@ class expected
20372073
}
20382074

20392075
template<class T2, class E2>
2040-
[[nodiscard]] friend constexpr std::enable_if_t<!std::is_void_v<T2>, bool> operator==(
2041-
const expected &x, const expected<T2, E2> &y
2042-
) noexcept(noexcept(*x == *y) && noexcept(x.error() == y.error()))
2076+
[[nodiscard]] friend constexpr std::enable_if_t<
2077+
!std::is_void_v<T2> &&
2078+
expected_detail::is_equality_result_convertible_to_bool_v<T, T2> &&
2079+
expected_detail::is_equality_result_convertible_to_bool_v<E, E2>,
2080+
bool
2081+
>
2082+
operator==(const expected &x, const expected<T2, E2> &y) noexcept(
2083+
expected_detail::is_nothrow_equality_result_convertible_to_bool_v<T, T2> &&
2084+
expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>
2085+
)
20432086
{
20442087
if (x.has_value() != y.has_value())
20452088
{
@@ -2056,20 +2099,26 @@ class expected
20562099
}
20572100
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
20582101
template<class T2, class E2>
2059-
[[nodiscard]] friend constexpr std::enable_if_t<!std::is_void_v<T2>, bool> operator!=(
2060-
const expected &x, const expected<T2, E2> &y
2061-
) noexcept(noexcept(x == y))
2102+
[[nodiscard]] friend constexpr std::enable_if_t<
2103+
!std::is_void_v<T2> &&
2104+
expected_detail::is_equality_result_convertible_to_bool_v<T, T2> &&
2105+
expected_detail::is_equality_result_convertible_to_bool_v<E, E2>,
2106+
bool
2107+
> operator!=(const expected &x, const expected<T2, E2> &y) noexcept(noexcept(x == y))
20622108
{
20632109
return !(x == y);
20642110
}
20652111
#endif
20662112

20672113
template<class T2>
2068-
[[nodiscard]] friend constexpr bool operator==(const expected &x, const T2 &v) noexcept(noexcept(*x == v))
2114+
[[nodiscard]] friend constexpr std::enable_if_t<
2115+
!expected_detail::is_specialization_v<T2, zeus::expected> && expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
2116+
bool
2117+
> operator==(const expected &x, const T2 &v) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<T, T2>)
20692118
{
20702119
if (x.has_value())
20712120
{
2072-
return static_cast<bool>(*x == v);
2121+
return *x == v;
20732122
}
20742123
else
20752124
{
@@ -2078,54 +2127,37 @@ class expected
20782127
}
20792128
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
20802129
template<class T2>
2081-
[[nodiscard]] friend constexpr bool operator!=(const expected &x, const T2 &v) noexcept(noexcept(x == v))
2130+
[[nodiscard]] friend constexpr std::enable_if_t<
2131+
!expected_detail::is_specialization_v<T2, zeus::expected> && expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
2132+
bool
2133+
> operator!=(const expected &x, const T2 &v) noexcept(noexcept(x == v))
20822134
{
20832135
return !(x == v);
20842136
}
2085-
template<class T2>
2086-
[[nodiscard]] friend constexpr std::enable_if_t<!expected_detail::is_specialization_v<T2, zeus::expected>, bool> operator==(
2087-
const T2 &v, const expected &x
2088-
) noexcept(noexcept(x == v))
2089-
{
2090-
return x == v;
2091-
}
2092-
template<class T2>
2093-
[[nodiscard]] friend constexpr std::enable_if_t<!expected_detail::is_specialization_v<T2, zeus::expected>, bool> operator!=(
2094-
const T2 &v, const expected &x
2095-
) noexcept(noexcept(x == v))
2096-
{
2097-
return x != v;
2098-
}
20992137
#endif
21002138

21012139
template<class E2>
2102-
[[nodiscard]] friend constexpr bool operator==(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x.error() == e.error()))
2140+
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator==(
2141+
const expected &x, const unexpected<E2> &e
2142+
) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>)
21032143
{
21042144
if (x.has_value())
21052145
{
21062146
return false;
21072147
}
21082148
else
21092149
{
2110-
return static_cast<bool>(x.error() == e.error());
2150+
return x.error() == e.error();
21112151
}
21122152
}
21132153
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
21142154
template<class E2>
2115-
[[nodiscard]] friend constexpr bool operator!=(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x == e))
2155+
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator!=(
2156+
const expected &x, const unexpected<E2> &e
2157+
) noexcept(noexcept(x == e))
21162158
{
21172159
return !(x == e);
21182160
}
2119-
template<class E2>
2120-
[[nodiscard]] friend constexpr bool operator==(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
2121-
{
2122-
return x == e;
2123-
}
2124-
template<class E2>
2125-
[[nodiscard]] friend constexpr bool operator!=(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
2126-
{
2127-
return x != e;
2128-
}
21292161
#endif
21302162
};
21312163

@@ -2743,60 +2775,100 @@ class expected<void, E>
27432775
}
27442776

27452777
template<class T2, class E2>
2746-
[[nodiscard]] friend constexpr std::enable_if_t<std::is_void_v<T2>, bool> operator==(
2747-
const expected &x, const expected<T2, E2> &y
2748-
) noexcept(noexcept(x.error() == y.error()))
2778+
[[nodiscard]] friend constexpr std::
2779+
enable_if_t<std::is_void_v<T2> && expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool>
2780+
operator==(
2781+
const expected &x, const expected<T2, E2> &y
2782+
) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>)
27492783
{
27502784
if (x.has_value() != y.has_value())
27512785
{
27522786
return false;
27532787
}
2788+
else if (x.has_value())
2789+
{
2790+
return true;
2791+
}
27542792
else
27552793
{
2756-
return x.has_value() || static_cast<bool>(x.error() == y.error());
2794+
return x.error() == y.error();
27572795
}
27582796
}
27592797
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
27602798
template<class T2, class E2>
2761-
[[nodiscard]] friend constexpr std::enable_if_t<std::is_void_v<T2>, bool> operator!=(
2762-
const expected &x, const expected<T2, E2> &y
2763-
) noexcept(noexcept(x == y))
2799+
[[nodiscard]] friend constexpr std::
2800+
enable_if_t<std::is_void_v<T2> && expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator!=(
2801+
const expected &x, const expected<T2, E2> &y
2802+
) noexcept(noexcept(x == y))
27642803
{
27652804
return !(x == y);
27662805
}
27672806
#endif
27682807

27692808
template<class E2>
2770-
[[nodiscard]] friend constexpr bool operator==(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x.error() == e.error()))
2809+
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator==(
2810+
const expected &x, const unexpected<E2> &e
2811+
) noexcept(expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>)
27712812
{
27722813
if (x.has_value())
27732814
{
27742815
return false;
27752816
}
27762817
else
27772818
{
2778-
return static_cast<bool>(x.error() == e.error());
2819+
return x.error() == e.error();
27792820
}
27802821
}
27812822
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
27822823
template<class E2>
2783-
[[nodiscard]] friend constexpr bool operator!=(const expected &x, const unexpected<E2> &e) noexcept(noexcept(x == e))
2824+
[[nodiscard]] friend constexpr std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> operator!=(
2825+
const expected &x, const unexpected<E2> &e
2826+
) noexcept(noexcept(x == e))
27842827
{
27852828
return !(x == e);
27862829
}
2787-
template<class E2>
2788-
[[nodiscard]] friend constexpr bool operator==(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
2789-
{
2790-
return x == e;
2791-
}
2792-
template<class E2>
2793-
[[nodiscard]] friend constexpr bool operator!=(const unexpected<E2> &e, const expected &x) noexcept(noexcept(x == e))
2794-
{
2795-
return x != e;
2796-
}
27972830
#endif
27982831
};
27992832

2833+
#if ZEUS_EXPECTED_CPLUSPLUS < 202'002L
2834+
// Deduce the expected operand to reject conversions exposed by MSVC's permissive C++17 hidden-friend lookup.
2835+
template<class T, class E, class T2>
2836+
[[nodiscard]] constexpr std::enable_if_t<
2837+
!std::is_void_v<T> &&
2838+
!expected_detail::is_specialization_v<T2, zeus::expected> &&
2839+
expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
2840+
bool
2841+
>
2842+
operator==(const T2 &v, const expected<T, E> &x) noexcept(noexcept(x == v))
2843+
{
2844+
return x == v;
2845+
}
2846+
2847+
template<class T, class E, class T2>
2848+
[[nodiscard]] constexpr std::enable_if_t<
2849+
!std::is_void_v<T> &&
2850+
!expected_detail::is_specialization_v<T2, zeus::expected> &&
2851+
expected_detail::is_equality_result_convertible_to_bool_v<T, T2>,
2852+
bool
2853+
>
2854+
operator!=(const T2 &v, const expected<T, E> &x) noexcept(noexcept(x == v))
2855+
{
2856+
return x != v;
2857+
}
2858+
2859+
template<class T, class E, class E2, std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>> * = nullptr>
2860+
[[nodiscard]] constexpr bool operator==(const unexpected<E2> &e, const expected<T, E> &x) noexcept(noexcept(x == e))
2861+
{
2862+
return x == e;
2863+
}
2864+
2865+
template<class T, class E, class E2, std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>> * = nullptr>
2866+
[[nodiscard]] constexpr bool operator!=(const unexpected<E2> &e, const expected<T, E> &x) noexcept(noexcept(x == e))
2867+
{
2868+
return x != e;
2869+
}
2870+
#endif
2871+
28002872
// standalone swap for void value type
28012873
template<class E, std::enable_if_t<std::is_move_constructible_v<E> && std::is_swappable_v<E>> * = nullptr>
28022874
constexpr void swap(expected<void, E> &lhs, expected<void, E> &rhs) noexcept(noexcept(lhs.swap(rhs)))

tests/test_expected/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ set(SOURCES
55
monadic_tests.cpp
66
noexcept_tests.cpp
77
equality_tests.cpp
8+
equality_noexcept_tests.cpp
9+
p3379_tests.cpp
810
lwg_3886_tests.cpp
911
lwg_4031_tests.cpp
1012
lwg_4222_tests.cpp
1113
lwg_4025_tests.cpp
14+
lwg_4366_tests.cpp
1215
)
1316

1417
find_package(Catch2 3 REQUIRED)

0 commit comments

Comments
 (0)