Date
2023-02-07.12:12:38
Message id
13146

Content

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):

  1. (17.3) — is_constructible_v<T, expected<U, G>&> is false; and

  2. (17.4) — is_constructible_v<T, expected<U, G>> is false; and

  3. (17.5) — is_constructible_v<T, const expected<U, G>&> is false; and

  4. (17.6) — is_constructible_v<T, const expected<U, G>> is false; and

  5. (17.7) — is_convertible_v<expected<U, G>&, T> is false; and

  6. (17.8) — is_convertible_v<expected<U, G>&&, T> is false; and

  7. (17.9) — is_convertible_v<const expected<U, G>&, T> is false; and

  8. (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.