This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.
expected constructor from a single value missing a constraintSection: 22.8.6.2 [expected.object.cons] Status: WP Submitter: Bronek Kozicki Opened: 2025-03-12 Last modified: 2025-06-23
Priority: Not Prioritized
View all other issues in [expected.object.cons].
View all issues with WP status.
Discussion:
When an expected object is initialized with a constructor taking first parameter of type unexpect_t ,
the expectation is that the object will be always initialized in disengaged state (i.e. the user expected
postcondition is that has_value() will be false), as in the example:
struct T { explicit T(auto) {} };
struct E { E() {} };
int main() {
expected<T, E> a(unexpect);
assert(!a.has_value());
}
This does not hold when both value type T and error type E have certain properties. Observe:
struct T { explicit T(auto) {} };
struct E { E(int) {} }; // Only this line changed from the above example
int main() {
expected<T, E> a(unexpect);
assert(!a.has_value()); // This assert will now fail
}
In the example above the overload resolution of a finds the universal single parameter constructor for
initializing expected in engaged state (22.8.6.2 [expected.object.cons] p23):
template<class U = remove_cv_t<T>> constexpr explicit(!is_convertible_v<U, T>) expected(U&& v);
This constructor has a list of constraints which does not mention unexpect_t (but it mentions e.g. unexpected and
in_place_t). Email exchange with the author of expected confirmed that it is an omission.
is_same_v<remove_cvref_t<U>, unexpect_t>isfalse
This will result in the above, most likely buggy, program to become ill-formed. If the user intent was for the object
to be constructed in an engaged state, passing unexpect_t to the T constructor, they can fix the compilation error
like so:
expected<T, E> a(in_place, unexpect);
[2025-06-13; Reflector poll]
Set status to Tentatively Ready after nine votes in favour during reflector poll.
[Sofia 2025-06-21; Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N5001.
Modify 22.8.6.2 [expected.object.cons] as indicated:
template<class U = remove_cv_t<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.?) —
is_same_v<remove_cvref_t<U>, unexpect_t>isfalse; and(23.3) —
remove_cvref_t<U>is not a specialization ofunexpected; and(23.4) —
is_constructible_v<T, U>istrue; and(23.5) — if
Tis cvbool,remove_cvref_t<U>is not a specialization ofexpected.-24- Effects: Direct-non-list-initializes
-25- Postconditions:valwithstd::forward<U>(v).has_value()istrue. -26- Throws: Any exception thrown by the initialization ofval.