This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
Section: 22.8.6.1 [expected.object.general] Status: New Submitter: Jiang An Opened: 2023-02-19 Last modified: 2023-03-22
Priority: 2
View all other issues in [expected.object.general].
View all issues with New status.
Discussion:
Currently the value_type of std::expected can be a cv-qualified type, which is possibly intented. However, LWG 3870 disallows std::construct_at to construct objects via cv T*, which breaks std::expected<cv T, E> because some operations are specified with std::construct_at (22.8.6.4 [expected.object.assign], 22.8.6.5 [expected.object.swap]).
I think when T is cv-qualified, it would be better to store std::remove_cv_t<T> subobject while sometimes (other than construction/destruction) access it via a cv-qualified glvalue, which can also avoid UB associated with const/volatile objects.[2023-03-22; Reflector poll]
Set priority to 2 after reflector poll.
"Not clear if all these wording changes are needed or desired."
"Unconvinced that the mixed-value-error swap should use value()
,
source is destroyed immediately anyway. The else branch should use
remove_cv_t
too."
Proposed resolution:
This wording is relative to N4928.
[Drafting note: When assignment and swap need to backup the old value by move construction, the source should be considered cv-unqualified, as the backup mechanism is only used internally.]
Modify 22.8.6.1 [expected.object.general] as indicated:
[…] bool has_val; // exposition only union { remove_cv_t<T> val; // exposition only E unex; // exposition only }; […]
Modify 22.8.6.4 [expected.object.assign] as indicated:
constexpr expected& operator=(const expected& rhs);[…]-2- Effects:
(2.1) — If this->has_value() && rhs.has_value() is true, equivalent to value()
val= *rhs.[…]
constexpr expected& operator=(expected&& rhs) noexcept(see below);[…][…]
-6- Effects:
(6.1) — If this->has_value() && rhs.has_value() is true, equivalent to value()
val= std::move(*rhs).[…]
template<class U = T> constexpr expected& operator=(U&& v);[…]
-10- Effects:
(10.1) — If has_value() is true, equivalent to value()
val= std::forward<U>(v).[…]
Modify Table 64: swap(expected&) effects [tab:expected.object.swap] as indicated:
Table 64 — swap(expected&) effects [tab:expected.object.swap] this->has_value() !this->has_value() rhs.has_value() equivalent to: using std::swap;
swap(value()val, rhs.value()val);calls rhs.swap(*this) […]
Modify 22.8.6.5 [expected.object.swap] as indicated:
constexpr void swap(expected& rhs) noexcept(see below);-1- Constraints: […]
-2- Effects: See Table 64 [tab:expected.object.swap]. For the case where rhs.value() is false and this->has_value() is true, equivalent to:if constexpr (is_nothrow_move_constructible_v<E>) { E tmp(std::move(rhs.unex)); destroy_at(addressof(rhs.unex)); try { construct_at(addressof(rhs.val), std::move(value()val)); destroy_at(addressof(val)); construct_at(addressof(unex), std::move(tmp)); } catch(...) { construct_at(addressof(rhs.unex), std::move(tmp)); throw; } } else { T tmp(std::move(val)); destroy_at(addressof(val)); try { construct_at(addressof(unex), std::move(rhs.unex)); destroy_at(addressof(rhs.unex)); construct_at(addressof(rhs.val), std::move(tmp)); } catch (...) { construct_at(addressof(val), std::move(tmp)); throw; } } has_val = false; rhs.has_val = true;