Created on 2023-02-19.00:00:00 last changed 1 month ago
Proposed resolution:
This wording is relative to N4988.
[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 [expected.object.general] as indicated:
[…] bool has_val; // exposition only union { remove_cv_t<T> val; // exposition only E unex; // exposition only }; […]
Modify [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 [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(val)); destroy_at(addressof(val)); construct_at(addressof(unex), std::move(tmp)); } catch(...) { construct_at(addressof(rhs.unex), std::move(tmp)); throw; } } else { remove_cv_t<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;
[ 2024-10-02; Jonathan provides improved wording ]
Removed the use of `value()` in the [expected.object.swap] p2 Effects: and added `remove_cv_t` to the local `T` in the `else`-branch.
[ 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."
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 [expected.object.general] as indicated:
[…] bool has_val; // exposition only union { remove_cv_t<T> val; // exposition only E unex; // exposition only }; […]
Modify [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 [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;
Currently the value_type of std::expected can be a cv-qualified type, which is possibly intended. 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 ([expected.object.assign], [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.History | |||
---|---|---|---|
Date | User | Action | Args |
2024-10-02 11:44:43 | admin | set | messages: + msg14397 |
2023-03-22 22:40:39 | admin | set | messages: + msg13478 |
2023-02-20 11:57:11 | admin | set | messages: + msg13428 |
2023-02-19 00:00:00 | admin | create |