std::expected<bool, E1> conversion constructor expected(const expected<U, G>&)
should take precedence over expected(U&&) with operator boolSection: 22.8.6.2 [expected.object.cons] Status: C++23 Submitter: Hui Xie Opened: 2022-11-30 Last modified: 2023-11-22
Priority: 1
View all other issues in [expected.object.cons].
View all issues with C++23 status.
Discussion:
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.
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.
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
(22.8.6.2 [expected.object.cons] p17):
(17.3) —
is_constructible_v<T, expected<U, G>&>isfalse; and(17.4) —
is_constructible_v<T, expected<U, G>>isfalse; and(17.5) —
is_constructible_v<T, const expected<U, G>&>isfalse; and(17.6) —
is_constructible_v<T, const expected<U, G>>isfalse; and(17.7) —
is_convertible_v<expected<U, G>&, T>isfalse; and(17.8) —
is_convertible_v<expected<U, G>&&, T>isfalse; and(17.9) —
is_convertible_v<const expected<U, G>&, T>isfalse; and(17.10) —
is_convertible_v<const expected<U, G>&&, T>isfalse; 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.
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.
[2023-01-06; Reflector poll]
Set priority to 1 after reflector poll.
There was a mix of votes for P1 and P2 but also one for NAD
("The design of forward/repack construction for expected matches optional,
when if the stored value can be directly constructed, we use that.").
std::optional<bool> is similarly affected.
Any change should consider the effects on
expected<expected<>> use cases.
[Issaquah 2023-02-08; Jonathan provides wording]
[Issaquah 2023-02-09; LWG]
Move to Immediate for C++23
[2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Immediate → WP.]
Proposed resolution:
This wording is relative to N4928.
Modify 22.5.3.2 [optional.ctor] as indicated:
template<class U = T> constexpr explicit(see below) optional(U&& v);-23- Constraints:
[Drafting note: Change this paragraph to a bulleted list.]
- (23.1) —
is_constructible_v<T, U>istrue,- (23.2) —
is_same_v<remove_cvref_t<U>, in_place_t>isfalse,and- (23.3) —
is_same_v<remove_cvref_t<U>, optional>isfalse, and- (23.4) — if
Tis cvbool,remove_cvref_t<U>is not a specialization ofoptional.-24- Effects: Direct-non-list-initializes the contained value with
std::forward>U>(v).-25- Postconditions:
*thishas a value.-26- Throws: Any exception thrown by the selection constructor of
T.-27- Remarks: If
T's selected constructor is a constexpr constructor, this constructor is a constexpr constructor. The expression insideexplicitis equivalent to:
!is_convertible_v<U, T>template<class U> constexpr explicit(see below) optional(const optional<U>& rhs);-28- Constraints:
- (28.1) —
is_constructible_v<T, const U&>istrue, and- (28.1) — if
Tis not cvbool,converts-from-any-cvref<T, optional<U>>isfalse.-29- Effects: If
rhscontains a value, direct-non-list-initializes the contained value with*rhs.-30- Postconditions:
rhs.has_value() == this->has_value().-31- Throws: Any exception thrown by the selection constructor of
T.-32- Remarks: The expression inside
explicitis equivalent to:
!is_convertible_v<const U&, T>template<class U> constexpr explicit(see below) optional(optional<U>&& rhs);-33- Constraints:
- (33.1) —
is_constructible_v<T, U>istrue, and- (33.1) — if
Tis not cvbool,converts-from-any-cvref<T, optional<U>>isfalse.-34- Effects: If
rhscontains a value, direct-non-list-initializes the contained value withstd::move(*rhs).rhs.has_value()is unchanged.-35- Postconditions:
rhs.has_value() == this->has_value().-36- Throws: Any exception thrown by the selection constructor of
T.-37- Remarks: The expression inside
explicitis equivalent to:
!is_convertible_v<U, T>
Modify 22.8.6.2 [expected.object.cons] as indicated:
template<class U, class G> constexpr explicit(see below) expected(const expected<U, G>& rhs); template<class U, class G> constexpr explicit(see below) expected(expected<U, G>&& rhs);-17- Let:
- (17.1) —
UFbeconst U&for the first overload andUfor the second overload.- (17.2) —
GFbeconst G&for the first overload andGfor the second overload.-18- Constraints:
- (18.1) —
is_constructible_v<T, UF>istrue; and- (18.2) —
is_constructible_v<E, GF>istrue; and- (18.3) — if
Tis not cvbool,converts-from-any-cvref<T, expected<U, G>>isfalse; and- (18.4) —
is_constructible_v<unexpected<E>, expected<U, G>&>isfalse; and- (18.5) —
is_constructible_v<unexpected<E>, expected<U, G>>isfalse; and- (18.6) —
is_constructible_v<unexpected<E>, const expected<U, G>&>isfalse; and- (18.7) —
is_constructible_v<unexpected<E>, const expected<U, G>>isfalse.-19- Effects: If
rhs.has_value(), direct-non-list-initializesvalwithstd::forward>UF>(*rhs). Otherwise, direct-non-list-initializesunexwithstd::forward>GF>(rhs.error()).-20- Postconditions:
rhs.has_value()is unchanged;rhs.has_value() == this->has_value()istrue.-21- Throws: Any exception thrown by the initialization of
valorunex.-22- Remarks: The expression inside
explicitis equivalent to!is_convertible_v<UF, T> || !is_convertible_v<GF, E>.template<class U = T> constexpr explicit(!is_convertible_v<U, T>) expected(U&& v);-23- Constraints:
- (23.1) —
is_same_v<remove_cvref_t<U>, in_place_t>isfalse; and- (23.2) —
is_same_v<expected, remove_cvref_t<U>>isfalse; and- (23.3) —
is_constructible_v<T, U>istrue; and- (23.4) —
remove_cvref_t<U>is not a specialization ofunexpected; and- (23.5) — if
Tis cvbool,remove_cvref_t<U>is not a specialization ofexpected.-24- Effects: Direct-non-list-initializes
valwithstd::forward>U>(v).-25- Postconditions:
has_value()istrue.-26- Throws: Any exception thrown by the initialization of
val.