std::expected<cv T, E>
Section: 22.8.6.1 [expected.object.general] Status: New Submitter: Jiang An Opened: 2023-02-19 Last modified: 2024-10-02
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 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
(22.8.6.4 [expected.object.assign], 22.8.6.5 [expected.object.swap]).
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."
Previous resolution [SUPERSEDED]:
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()
istrue
, equivalent tovalue()
.val= *rhs[…]
constexpr expected& operator=(expected&& rhs) noexcept(see below);[…][…]
-6- Effects:
(6.1) — If
this->has_value() && rhs.has_value()
istrue
, equivalent tovalue()
.val= std::move(*rhs)[…]
template<class U = T> constexpr expected& operator=(U&& v);[…]
-10- Effects:
(10.1) — If
has_value()
istrue
, equivalent tovalue()
.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 whererhs.value()
isfalse
andthis->has_value()
istrue
, 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;
[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.
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 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()
istrue
, equivalent tovalue()
.val= *rhs[…]
constexpr expected& operator=(expected&& rhs) noexcept(see below);[…][…]
-6- Effects:
(6.1) — If
this->has_value() && rhs.has_value()
istrue
, equivalent tovalue()
.val= std::move(*rhs)[…]
template<class U = T> constexpr expected& operator=(U&& v);[…]
-10- Effects:
(10.1) — If
has_value()
istrue
, equivalent tovalue()
.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 whererhs.value()
isfalse
andthis->has_value()
istrue
, 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;