The issue came up when implementing std::expected in libc++. Given the following example:
struct BaseError{};
struct DerivedError : BaseError{};
std::expected<int, DerivedError> e1(5);
std::expected<int, BaseError> e2(e1); // e2 holds 5
In the above example, e2 is constructed with the conversion constructor
expected::expected(const expected<U, G>&)
and the value 5 is correctly copied into e2 as expected.
However, if we change the type from int to bool, the behaviour is very surprising.
std::expected<bool, DerivedError> e1(false);
std::expected<bool, BaseError> e2(e1); // e2 holds true
In this example e2 is constructed with
expected::expected(U&&)
together with
expected::operator bool() const
Instead of copying e1's "false" into e2, it uses operator bool, which returns true in this case and e2 would hold "true" instead.
This is surprising behaviour given how inconsistent between int and bool.
The reason why the second example uses a different overload is that the constructor expected(const expected<U, G>& rhs); has the following constraint ([expected.object.cons] p17):
(17.3) — is_constructible_v<T, expected<U, G>&> is false; and
(17.4) — is_constructible_v<T, expected<U, G>> is false; and
(17.5) — is_constructible_v<T, const expected<U, G>&> is false; and
(17.6) — is_constructible_v<T, const expected<U, G>> is false; and
(17.7) — is_convertible_v<expected<U, G>&, T> is false; and
(17.8) — is_convertible_v<expected<U, G>&&, T> is false; and
(17.9) — is_convertible_v<const expected<U, G>&, T> is false; and
(17.10) — is_convertible_v<const expected<U, G>&&, T> is false; and
Since T is bool in the second example, and bool can be constructed from std::expected, this overload will be removed. and the overload that takes U&& will be selected.
I would suggest to special case bool, i.e.
(The above 8 constraints); or
is_same_v<remove_cv_t<T>, bool> is true
And we need to make sure this overload and the overload that takes expected(U&&) be mutually exclusive.