Issue 4015: LWG 3973 broke const overloads of std::optional monadic operations (original) (raw)
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.
4015. LWG 3973 broke const overloads of std::optional monadic operations
Section: 22.5.3.8 [optional.monadic] Status: WP Submitter: Jonathan Wakely Opened: 2023-11-24 Last modified: 2025-11-11
Priority: 1
View all issues with WP status.
Discussion:
The resolution of LWG 3973(i) (adopted in Kona) changed all occurrences of value() to *val. The intention was not to change the meaning, just avoid the non-freestandingvalue() function, and avoid ADL that would be caused by using**this. However, in the const overloads such asand_then(F&&) const the type of value()was const T&, but the type of *val is alwaysT&. This implies that the const overloads invoke the callable with a non-const argument, which is incorrect (and would be undefined behaviour for a const std::optional<T>).
On the LWG reflector it was suggested that we should rewrite the specification of std::optional to stop using an exposition-only data member of type T*. No such member ever exists in real implemetations, so it is misleading and leads to specification bugs of this sort.
Change the class definition in 22.5.3.1 [optional.optional.general]to use a union, and update every use of val accordingly throughout 22.5.3 [optional.optional]. For consistency with 22.8.6.1 [expected.object.general] we might also want to introduce a bool has_val member and refer to that in the specification.
private: T *val; // exposition only bool has_val; // exposition only union { T val; // exposition only }; };
For example, in 22.5.3.9 [optional.mod]:
-1- Effects: If
*thiscontains a value, callsval~~->~~.T::~T()to destroy the contained value and setshas_valtofalse; otherwise no effect.
[2023-11-26; Daniel provides wording]
The proposed wording is considerably influenced by that of the specification of expected, but attempts to reduce the amount of changes to not perfectly mimic it. Although "the contained value" is a magic word of power it seemed feasible and simpler to use the new exposition-only member _val_directly in some (but not all) places, usually involved with initializations.
Furthermore, I have only added "and sets _hasval_ to true/false" where either the Effects wording says "otherwise no effect" or in other cases if the postconditions did not already say that indirectly. I also added extra mentioning of _hasval_ changes in tables where different cells had very different effects on that member (unless these cells specify postconditions), to prevent misunderstanding.
[2024-03-11; Reflector poll]
Set priority to 1 after reflector poll in November 2023. Six votes for 'Tentatively Ready' but enough uncertainty to deserve discussion at a meeting.
Previous resolution [SUPERSEDED]:
This wording is relative to N4964 after application of the wording of LWG 3973(i).
- Modify 22.5.3.1 [optional.optional.general], class template
optionalsynopsis, as indicated:namespace std {
template
class optional {
public:
using value_type = T;
[…]
private:
bool hasval; // exposition only
union {
T val*val; // exposition only};
};[…]
} 2. Modify 22.5.3.1 [optional.optional.general] as indicated:
-2- Member_hasval_indicates whether anoptional<T>object contains a valueWhen an. 3. Modify 22.5.3.2 [optional.ctor] as indicated:optional<T>object contains a value, membervalpoints to the contained value
[Drafting note: Normatively, this subclause doesn't require any changes, but I'm suggesting to replace phrases of the form "[…]initializes the contained value with"] by "[…]initializes_val_with" as we do in 22.8.6.2 [expected.object.cons]. I intentionally did not add extra "and sets_hasval_totrue/false" since those effects are already guaranteed by the postconditions]
constexpr optional(const optional& rhs);-4- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewith~~*~~rhs._val_.-5- Postconditions:
rhs.has_value() == this->has_value().[…]
constexpr optional(optional&& rhs) noexcept(see below);
-8- Constraints: […]
-9- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewithstd::move(~~*~~rhs._val_).rhs.has_value()is unchanged.-10- Postconditions:
rhs.has_value() == this->has_value().[…]
template<class... Args> constexpr explicit optional(in_place_t, Args&&... args);
-13- Constraints: […]
-14- Effects: Direct-non-list-initializes
_val_the contained valuewithstd::forward<Args>(args)....-15- Postconditions:
*thiscontains a value.[…]
template<class U, class... Args>
constexpr explicit optional(in_place_t, initializer_list il, Args&&... args);-18- Constraints: […]
-19- Effects: Direct-non-list-initializes
_val_the contained valuewithil, std::forward<Args>(args)....-20- Postconditions:
*thiscontains a value.[…]
template constexpr explicit(see below) optional(U&& v);
-23- Constraints: […]
-24- Effects: Direct-non-list-initializes
_val_the contained valuewithstd::forward<U>(v).-25- Postconditions:
*thiscontains a value.[…]
template constexpr explicit(see below) optional(const optional& rhs);
-28- Constraints: […]
-29- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewith~~*~~rhs._val_.-30- Postconditions:
rhs.has_value() == this->has_value().[…]
template constexpr explicit(see below) optional(optional&& rhs);
-33- Constraints: […]
-34- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewithstd::move(~~*~~rhs._val_).rhs.has_value()is unchanged.-35- Postconditions:
rhs.has_value() == this->has_value().[…] 4. Modify 22.5.3.3 [optional.dtor] as indicated:
constexpr ~optional();-1- Effects: If
is_trivially_destructible_v<T> != trueand*thiscontains a value, calls~~val->~~_val_.T::~T(). 5. Modify 22.5.3.4 [optional.assign] as indicated:
constexpr optional& operator=(nullopt_t) noexcept;-1- Effects: If
*thiscontains a value, calls~~val->~~_val_.T::~T()to destroy the contained value and sets_hasval_tofalse; otherwise no effect.-2- Postconditions:
*thisdoes not contain a value.constexpr optional& operator=(const optional& rhs);
-4- Effects: See Table 58.
Table 58 — optional::operator=(const optional&) effects [tab:optional.assign.copy]
*this contains a value *this does not contain a value rhs contains a value assigns *rhs.val to valthe contained valuedirect-non-list-initializes val the contained valuewith*rhs.val and sets has_val to truerhs does not contain a value destroys the contained value by calling val->val.T::~T() and sets has_val to falseno effect -5- Postconditions:
rhs.has_value() == this->has_value().[…]
constexpr optional& operator=(optional&& rhs) noexcept(see below);
-8- Constraints: […]
-9- Effects: See Table 59. The result of the expression
rhs.has_value()remains unchanged.-10- Postconditions:
rhs.has_value() == this->has_value().-11- Returns:
*this.Table 59 — optional::operator=(optional&&) effects [tab:optional.assign.move]
*this contains a value *this does not contain a value rhs contains a value assigns std::move( *rhs.val) to valthe contained valuedirect-non-list-initializes val the contained valuewithstd::move(*rhs.val) and sets has_val to truerhs does not contain a value destroys the contained value by calling val->val.T::~T()and sets has_val to falseno effect -12- Remarks: […]
-13- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's move constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's move constructor. If an exception is thrown during the call toT's move assignment, the state of~~*~~_val_~~val~~and~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's move assignment.template constexpr optional& operator=(U&& v);
-14- Constraints: […]
-15- Effects: If
*thiscontains a value, assignsstd::forward<U>(v)to_val_the contained value; otherwise direct-non-list-initializes_val_the contained valuewithstd::forward<U>(v).-16- Postconditions:
*thiscontains a value.-17- Returns:
*this.-18- Remarks: If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofvis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of_val_~~*val~~andvis determined by the exception safety guarantee ofT's assignment.template constexpr optional& operator=(const optional& rhs);
-19- Constraints: […]
-20- Effects: See Table 60.
Table 60 — optional::operator=(const optional&) effects [tab:optional.assign.copy.templ]
*this contains a value *this does not contain a value rhs contains a value assigns *rhs.val to valthe contained valuedirect-non-list-initializes val the contained valuewith*rhs.val and sets has_val to truerhs does not contain a value destroys the contained value by calling val->val.T::~T() and sets has_val to falseno effect -21- Postconditions:
rhs.has_value() == this->has_value().-22- Returns:
*this.-23- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of_val_~~*val~~and~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's assignment.template constexpr optional& operator=(optional&& rhs);
-24- Constraints: […]
-25- Effects: See Table 61. The result of the expression
rhs.has_value()remains unchanged.Table 61 — optional::operator=(optional&&) effects [tab:optional.assign.move.templ]
*this contains a value *this does not contain a value rhs contains a value assigns std::move( *rhs.val) to valthe contained valuedirect-non-list-initializes val the contained valuewith std::move(*rhs.val) and sets has_val to truerhs does not contain a value destroys the contained value by calling val->val.T::~T() and sets has_val to falseno effect -26- Postconditions:
rhs.has_value() == this->has_value().-27- Returns:
*this.-28- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of_val_~~*val~~and~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's assignment.template<class... Args> constexpr T& emplace(Args&&... args);
-29- Mandates: […]
-30- Effects: Calls
*this = nullopt. Then direct-non-list-initializes_val_the contained valuewithstd::forward<Args>(args)....-31- Postconditions:
*thiscontains a value.-32- Returns:
_val_A reference to the new contained value.[…]
-34- Remarks: If an exception is thrown during the call to
T's constructor,*thisdoes not contain a value, and the previous_val_~~*val~~(if any) has been destroyed.template<class U, class... Args> constexpr T& emplace(initializer_list il, Args&&... args);
-35- Constraints: […]
-36- Effects: Calls
*this = nullopt. Then direct-non-list-initializes_val_the contained valuewithil, std::forward<Args>(args)....-37- Postconditions:
*thiscontains a value.-38- Returns:
_val_A reference to the new contained value.[…]
-40- Remarks: If an exception is thrown during the call to
T's constructor,*thisdoes not contain a value, and the previous_val_~~*val~~(if any) has been destroyed. 6. Modify 22.5.3.5 [optional.swap] as indicated:
constexpr void swap(optional& rhs) noexcept(see below);-1- Mandates: […]
-2- Preconditions: […]
-3- Effects: See Table 62.
Table 62 — optional::swap(optional&) effects [tab:optional.swap]
*this contains a value *this does not contain a value rhs contains a value calls swap(val *(*this),*rhs.val)direct-non-list-initializes val the contained value of *thiswith std::move(*rhs.val), followed by rhs.val.val->T::~T();postcondition is that *this contains a value and rhs doesnot contain a valuerhs does not contain a value direct-non-list-initializes the contained value ofrhs._val_with std::move(val*(*this)), followed by val.val->T::~T();postcondition is that *this does not contain a value and rhscontains a valueno effect -4- Throws: […]
-5- Remarks: […]
-6- If any exception is thrown, the results of the expressions
this->has_value()andrhs.has_value()remain unchanged. If an exception is thrown during the call to functionswap, the state of_val_~~*val~~and~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofswapfor lvalues ofT. If an exception is thrown during the call toT's move constructor, the state of_val_~~*val~~and~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's move constructor. 7. Modify 22.5.3.7 [optional.observe] as indicated:
constexpr const T* operator->() const noexcept;
constexpr T* operator->() noexcept;-1- Preconditions:
*thiscontains a value.-2- Returns:
addressof(_val_)~~val~~.-3- […]
constexpr const T& operator*() const & noexcept;
constexpr T& operator*() & noexcept;-4- Preconditions:
*thiscontains a value.-5- Returns:
_val_~~*val~~.-6- […]
constexpr T&& operator*() && noexcept;
constexpr const T&& operator*() const && noexcept;-7- Preconditions:
*thiscontains a value.-8- Effects: Equivalent to:
return std::move(_val_~~*val~~);constexpr explicit operator bool() const noexcept;
-9- Returns:trueif and only if*thiscontains a value.
-10- Remarks: This function is a constexpr function.constexpr bool has_value() const noexcept;
-11- Returns:
_hasval_.trueif and only if*thiscontains a value-12- Remarks: These functions are
This function is aconstexpr functions.constexpr const T& value() const &;
constexpr T& value() &;-13- Effects: Equivalent to:
return has_value() ? val
*val: throw bad_optional_access();constexpr T&& value() &&;
constexpr const T&& value() const &&;-14- Effects: Equivalent to:
return has_value() ? std::move(val
*val) : throw bad_optional_access();template constexpr T value_or(U&& v) const &;
-15- Mandates: […]
-16- Effects: Equivalent to:
return has_value() ? val
**this: static_cast(std::forward(v));template constexpr T value_or(U&& v) &&;
-17- Mandates: […]
-18- Effects: Equivalent to:
return has_value() ? std::move(val
**this) : static_cast(std::forward(v)); 8. Modify 22.5.3.8 [optional.monadic] as indicated:
template constexpr auto and_then(F&& f) &;
template constexpr auto and_then(F&& f) const &;-1- Let
Ubeinvoke_result_t<F, decltype((_val_)~~*val~~)>.-2- Mandates: […]
-3- Effects: Equivalent to:
if (*this) {
return invoke(std::forward(f), val*val);
} else {
return remove_cvref_t();
}template constexpr auto and_then(F&& f) &&;
template constexpr auto and_then(F&& f) const &&;-4- Let
Ubeinvoke_result_t<F, decltype(std::move(_val_~~*val~~))>.-5- Mandates: […]
-6- Effects: Equivalent to:
if (*this) {
return invoke(std::forward(f), std::move(val*val));
} else {
return remove_cvref_t();
}template constexpr auto transform(F&& f) &;
template constexpr auto transform(F&& f) const &;-7- Let
Uberemove_cv_t<invoke_result_t<F, decltype((_val_)~~*val~~)>>.-8- Mandates:
Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward(f), val
*val));is well-formed for some invented variable
u.[…]
-9- Returns: If
*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), _val_~~*val~~); otherwise,optional<U>().template constexpr auto transform(F&& f) &&;
template constexpr auto transform(F&& f) const &&;-10- Let
Uberemove_cv_t<invoke_result_t<F, decltype(std::move(_val_~~*val~~))>>.-11- Mandates:
Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward(f), std::move(val
*val)));is well-formed for some invented variable
u.[…]
-12- Returns: If
*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(_val_~~*val~~)); otherwise,optional<U>(). 9. Modify 22.5.3.9 [optional.mod] as indicated:
constexpr void reset() noexcept;-1- Effects: If
*thiscontains a value, calls~~val->~~_val_.T::~T()to destroy the contained value and sets_hasval_tofalse; otherwise no effect.-2- Postconditions:
*thisdoes not contain a value.
[St. Louis 2024-06-24; Jonathan provides improved wording]
[2024-08-21; LWG telecon]
During telecon review it was suggested to replace 22.5.3.1 [optional.optional.general] p1 and p2. On the reflector Daniel requested to keep the "additional storage" prohibition, so that will be addressed by issue [4141](lwg-defects.html#4141 "Improve prohibitions on "additional storage" (Status: WP)")(i) instead.
[2024-10-02; Jonathan tweaks proposed resolution]
On the reflector we decided that the union member should use remove_cv_t, as proposed for expected by issue 3891(i). The rest of the proposed resolution is unchanged, so that edit was made in-place below, instead of as a new resolution that supersedes the old one.
Previous resolution [SUPERSEDED]:
This wording is relative to N4988.
- Modify 22.5.3.1 [optional.optional.general], class template
optionalsynopsis, as indicated:namespace std {
template
class optional {
public:
using value_type = T;
[…]
private:
*val // exposition only;
union {
remove_cv_t val; // exposition only};
};[…]
} 2. Modify 22.5.3.1 [optional.optional.general] as indicated:
-1-When its member_val_is active (11.5.1 [class.union.general]), an instance ofoptional<T>is said to_contain a value_, and_val_is referred to as its_contained value_.Any instance ofAnoptional object'scontained valueoptional<T>at any given time either contains a value or does not contain a value. When an instance ofoptional<T>contains a value, it means that an object of typeT, referred to as thecontained value,is allocated within the storage of the optional object. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate its contained value.When an object of typeoptional<T>is contextually converted tobool, the conversion returnstrueif the object contains a value; otherwise the conversion returnsfalse.
-2- When an3. Modify 22.5.3.2 [optional.ctor] as indicated:optional<T>object contains a value, membervalpoints to the contained value.
constexpr optional(const optional& rhs);-4- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewith~~*~~rhs._val_.-5- Postconditions:
rhs.has_value() == this->has_value().[…]
constexpr optional(optional&& rhs) noexcept(see below);
-8- Constraints: […]
-9- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewithstd::move(~~*~~rhs._val_).rhs.has_value()is unchanged.-10- Postconditions:
rhs.has_value() == this->has_value().[…]
template<class... Args> constexpr explicit optional(in_place_t, Args&&... args);
-13- Constraints: […]
-14- Effects: Direct-non-list-initializes
_val_the contained valuewithstd::forward<Args>(args)....-15- Postconditions:
*thiscontains a value.[…]
template<class U, class... Args>
constexpr explicit optional(in_place_t, initializer_list il, Args&&... args);-18- Constraints: […]
-19- Effects: Direct-non-list-initializes
_val_the contained valuewithil, std::forward<Args>(args)....-20- Postconditions:
*thiscontains a value.[…]
template constexpr explicit(see below) optional(U&& v);
-23- Constraints: […]
-24- Effects: Direct-non-list-initializes
_val_the contained valuewithstd::forward<U>(v).-25- Postconditions:
*thiscontains a value.[…]
template constexpr explicit(see below) optional(const optional& rhs);
-28- Constraints: […]
-29- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewith~~*~~rhs._val_.-30- Postconditions:
rhs.has_value() == this->has_value().[…]
template constexpr explicit(see below) optional(optional&& rhs);
-33- Constraints: […]
-34- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewithstd::move(~~*~~rhs._val_).rhs.has_value()is unchanged.-35- Postconditions:
rhs.has_value() == this->has_value().[…] 4. Modify 22.5.3.3 [optional.dtor] as indicated:
constexpr ~optional();-1- Effects: If
is_trivially_destructible_v<T> != trueand*thiscontains a value, calls~~val->~~_val_.T::~T(). 5. Modify 22.5.3.4 [optional.assign] as indicated:
constexpr optional& operator=(nullopt_t) noexcept;-1- Effects: If
*thiscontains a value, calls~~val->~~_val_.T::~T()to destroy the contained value; otherwise no effect.-2- Postconditions:
*thisdoes not contain a value.constexpr optional& operator=(const optional& rhs);
-4- Effects: See Table 58.
Table 58 — optional::operator=(const optional&) effects [tab:optional.assign.copy]
*this contains a value *this does not contain a value rhs contains a value assigns *rhs.val to valthe contained valuedirect-non-list-initializes val the contained valuewith*rhs.valrhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -5- Postconditions:
rhs.has_value() == this->has_value().[…]
constexpr optional& operator=(optional&& rhs) noexcept(see below);
-8- Constraints: […]
-9- Effects: See Table 59. The result of the expression
rhs.has_value()remains unchanged.-10- Postconditions:
rhs.has_value() == this->has_value().-11- Returns:
*this.Table 59 — optional::operator=(optional&&) effects [tab:optional.assign.move]
*this contains a value *this does not contain a value rhs contains a value assigns std::move( *rhs.val) to valthe contained valuedirect-non-list-initializes val the contained valuewithstd::move(*rhs.val)rhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -12- Remarks: […]
-13- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's move constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's move constructor. If an exception is thrown during the call toT's move assignment, the statesstateof~~*~~_val_~~val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's move assignment.template constexpr optional& operator=(U&& v);
-14- Constraints: […]
-15- Effects: If
*thiscontains a value, assignsstd::forward<U>(v)to_val_the contained value; otherwise direct-non-list-initializes_val_the contained valuewithstd::forward<U>(v).-16- Postconditions:
*thiscontains a value.-17- Returns:
*this.-18- Remarks: If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofvis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the statesstateof_val_~~*val~~andvareisdetermined by the exception safety guarantee ofT's assignment.template constexpr optional& operator=(const optional& rhs);
-19- Constraints: […]
-20- Effects: See Table 60.
Table 60 — optional::operator=(const optional&) effects [tab:optional.assign.copy.templ]
*this contains a value *this does not contain a value rhs contains a value assigns *rhs.val to valthe contained valuedirect-non-list-initializes val the contained valuewith*rhs.valrhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -21- Postconditions:
rhs.has_value() == this->has_value().-22- Returns:
*this.-23- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the statesstateof_val_~~*val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's assignment.template constexpr optional& operator=(optional&& rhs);
-24- Constraints: […]
-25- Effects: See Table 61. The result of the expression
rhs.has_value()remains unchanged.Table 61 — optional::operator=(optional&&) effects [tab:optional.assign.move.templ]
*this contains a value *this does not contain a value rhs contains a value assigns std::move( *rhs.val) to valthe contained valuedirect-non-list-initializes val the contained valuewith std::move(*rhs.val)rhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -26- Postconditions:
rhs.has_value() == this->has_value().-27- Returns:
*this.-28- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the statesstateof_val_~~*val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's assignment.template<class... Args> constexpr T& emplace(Args&&... args);
-29- Mandates: […]
-30- Effects: Calls
*this = nullopt. Then direct-non-list-initializes_val_the contained valuewithstd::forward<Args>(args)....-31- Postconditions:
*thiscontains a value.-32- Returns:
_val_A reference to the new contained value.[…]
-34- Remarks: If an exception is thrown during the call to
T's constructor,*thisdoes not contain a value, and the previous_val_~~*val~~(if any) has been destroyed.template<class U, class... Args> constexpr T& emplace(initializer_list il, Args&&... args);
-35- Constraints: […]
-36- Effects: Calls
*this = nullopt. Then direct-non-list-initializes_val_the contained valuewithil, std::forward<Args>(args)....-37- Postconditions:
*thiscontains a value.-38- Returns:
_val_A reference to the new contained value.[…]
-40- Remarks: If an exception is thrown during the call to
T's constructor,*thisdoes not contain a value, and the previous_val_~~*val~~(if any) has been destroyed. 6. Modify 22.5.3.5 [optional.swap] as indicated:
constexpr void swap(optional& rhs) noexcept(see below);-1- Mandates: […]
-2- Preconditions: […]
-3- Effects: See Table 62.
Table 62 — optional::swap(optional&) effects [tab:optional.swap]
*this contains a value *this does not contain a value rhs contains a value calls swap(val *(*this),*rhs.val)direct-non-list-initializes val the contained value of *thiswith std::move(*rhs.val), followed by rhs.val.val->T::~T();postcondition is that *this contains a value and rhs doesnot contain a valuerhs does not contain a value direct-non-list-initializes the contained value ofrhs._val_with std::move(val*(*this)), followed by val.val->T::~T();postcondition is that *this does not contain a value and rhscontains a valueno effect -4- Throws: […]
-5- Remarks: […]
-6- If any exception is thrown, the results of the expressions
this->has_value()andrhs.has_value()remain unchanged. If an exception is thrown during the call to functionswap, the state of_val_~~*val~~and~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofswapfor lvalues ofT. If an exception is thrown during the call toT's move constructor, the statesstateof_val_~~*val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's move constructor. 7. Modify 22.5.3.7 [optional.observe] as indicated:
constexpr const T* operator->() const noexcept;
constexpr T* operator->() noexcept;-1- Preconditions:
*thiscontains a value.-2- Returns:
addressof(_val_)~~val~~.-3- […]
constexpr const T& operator*() const & noexcept;
constexpr T& operator*() & noexcept;-4- Preconditions:
*thiscontains a value.-5- Returns:
_val_~~*val~~.-6- […]
constexpr T&& operator*() && noexcept;
constexpr const T&& operator*() const && noexcept;-7- Preconditions:
*thiscontains a value.-8- Effects: Equivalent to:
return std::move(_val_~~*val~~);constexpr explicit operator bool() const noexcept;
-9- Returns:
trueif and only if*thiscontains a value.-10- Remarks: This function is a constexpr function.
constexpr bool has_value() const noexcept;
-11- Returns:
trueif and only if*thiscontains a value.-12- Remarks: This function is a constexpr function.
constexpr const T& value() const &;
constexpr T& value() &;-13- Effects: Equivalent to:
return has_value() ? val
*val: throw bad_optional_access();constexpr T&& value() &&;
constexpr const T&& value() const &&;-14- Effects: Equivalent to:
return has_value() ? std::move(val
*val) : throw bad_optional_access();template constexpr T value_or(U&& v) const &;
-15- Mandates: […]
-16- Effects: Equivalent to:
return has_value() ? val
**this: static_cast(std::forward(v));template constexpr T value_or(U&& v) &&;
-17- Mandates: […]
-18- Effects: Equivalent to:
return has_value() ? std::move(val
**this) : static_cast(std::forward(v)); 8. Modify 22.5.3.8 [optional.monadic] as indicated:
template constexpr auto and_then(F&& f) &;
template constexpr auto and_then(F&& f) const &;-1- Let
Ubeinvoke_result_t<F, decltype((_val_)~~*val~~)>.-2- Mandates: […]
-3- Effects: Equivalent to:
if (*this) {
return invoke(std::forward(f), val*val);
} else {
return remove_cvref_t();
}template constexpr auto and_then(F&& f) &&;
template constexpr auto and_then(F&& f) const &&;-4- Let
Ubeinvoke_result_t<F, decltype(std::move(_val_~~*val~~))>.-5- Mandates: […]
-6- Effects: Equivalent to:
if (*this) {
return invoke(std::forward(f), std::move(val*val));
} else {
return remove_cvref_t();
}template constexpr auto transform(F&& f) &;
template constexpr auto transform(F&& f) const &;-7- Let
Uberemove_cv_t<invoke_result_t<F, decltype((_val_)~~*val~~)>>.-8- Mandates:
Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward(f), val
*val));is well-formed for some invented variable
u.[…]
-9- Returns: If
*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), _val_~~*val~~); otherwise,optional<U>().template constexpr auto transform(F&& f) &&;
template constexpr auto transform(F&& f) const &&;-10- Let
Uberemove_cv_t<invoke_result_t<F, decltype(std::move(_val_~~*val~~))>>.-11- Mandates:
Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward(f), std::move(val
*val)));is well-formed for some invented variable
u.[…]
-12- Returns: If
*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(_val_~~*val~~)); otherwise,optional<U>(). 9. Modify 22.5.3.9 [optional.mod] as indicated:
constexpr void reset() noexcept;-1- Effects: If
*thiscontains a value, calls~~val->~~_val_.T::~T()to destroy the contained value; otherwise no effect.-2- Postconditions:
*thisdoes not contain a value.
[2025-11-03; Tomasz tweaks proposed resolution]
Updated converting constructor and assignments to use operator*()directly, required to correctly support optional<T&>. Also update corresponding constructor in specialization.
[Kona 2025-11-05; approved by LWG. Status changed: New → Immediate.]
[Kona 2025-11-08; Status changed: Immediate → WP.]
Proposed resolution:
This wording is relative to N5014.
- Modify 22.5.3.1 [optional.optional.general], class template
optionalsynopsis, as indicated:namespace std {
template
class optional {
public:
using value_type = T;
[…]
private:
T* val; // exposition only
union {
remove_cv_t val; // exposition only
};
};[…]
} 2. Modify 22.5.3.1 [optional.optional.general] as indicated:
-1-An instance ofoptional<T>is said to_contain a value_ when and only when its member_val_is active (11.5.1 [class.union.general]);_val_is referred to as its contained value.An object of typeAnoptional object'scontained valueoptional<T>at any given time either contains a value or does not contain a value. When an object of typeoptional<T>contains a value, it means that an object of typeT, referred to as thecontained value,is nested within (6.8.2 [intro.object]) the optional object.When an object of typeoptional<T>is contextually converted tobool, the conversion returnstrueif the object contains a value; otherwise the conversion returnsfalse.
-2- When an3. Modify 22.5.3.2 [optional.ctor] as indicated:optional<T>object contains a value, membervalpoints to the contained value.
constexpr optional(const optional& rhs);-4- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewith~~*~~rhs._val_.-5- Postconditions:
rhs.has_value() == this->has_value().[…]
constexpr optional(optional&& rhs) noexcept(see below);
-8- Constraints: […]
-9- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewithstd::move(~~*~~rhs._val_).rhs.has_value()is unchanged.-10- Postconditions:
rhs.has_value() == this->has_value().[…]
template<class... Args> constexpr explicit optional(in_place_t, Args&&... args);
-13- Constraints: […]
-14- Effects: Direct-non-list-initializes
_val_the contained valuewithstd::forward<Args>(args)....-15- Postconditions:
*thiscontains a value.[…]
template<class U, class... Args>
constexpr explicit optional(in_place_t, initializer_list il, Args&&... args);-18- Constraints: […]
-19- Effects: Direct-non-list-initializes
_val_the contained valuewithil, std::forward<Args>(args)....-20- Postconditions:
*thiscontains a value.[…]
template constexpr explicit(see below) optional(U&& v);
-23- Constraints: […]
-24- Effects: Direct-non-list-initializes
_val_the contained valuewithstd::forward<U>(v).-25- Postconditions:
*thiscontains a value.[…]
template constexpr explicit(see below) optional(const optional& rhs);
-28- Constraints: […]
-29- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewith~~*~~rhs.operator*().-30- Postconditions:
rhs.has_value() == this->has_value().[…]
template constexpr explicit(see below) optional(optional&& rhs);
-33- Constraints: […]
-34- Effects: If
rhscontains a value, direct-non-list-initializes_val_the contained valuewith~~*~~std::move(rhs).operator*().rhs.has_value()is unchanged.-35- Postconditions:
rhs.has_value() == this->has_value().[…] 4. Modify 22.5.3.3 [optional.dtor] as indicated:
constexpr ~optional();-1- Effects: If
is_trivially_destructible_v<T> != trueand*thiscontains a value, calls~~val->~~_val_.T::~T(). 5. Modify 22.5.3.4 [optional.assign] as indicated:
constexpr optional& operator=(nullopt_t) noexcept;-1- Effects: If
*thiscontains a value, calls~~val->~~_val_.T::~T()to destroy the contained value; otherwise no effect.-2- Postconditions:
*thisdoes not contain a value.constexpr optional& operator=(const optional& rhs);
-4- Effects: See Table 58.
Table 58 — optional::operator=(const optional&) effects [tab:optional.assign.copy]
*this contains a value *this does not contain a value rhs contains a value assigns *rhs.val to valthe contained valuedirect-non-list-initializes val the contained valuewith*rhs.valrhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -5- Postconditions:
rhs.has_value() == this->has_value().[…]
constexpr optional& operator=(optional&& rhs) noexcept(see below);
-8- Constraints: […]
-9- Effects: See Table 59. The result of the expression
rhs.has_value()remains unchanged.-10- Postconditions:
rhs.has_value() == this->has_value().-11- Returns:
*this.Table 59 — optional::operator=(optional&&) effects [tab:optional.assign.move]
*this contains a value *this does not contain a value rhs contains a value assigns std::move( *rhs.val) to valthe contained valuedirect-non-list-initializes val the contained valuewithstd::move(*rhs.val)rhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -12- Remarks: […]
-13- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's move constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's move constructor. If an exception is thrown during the call toT's move assignment, the statesstateof~~*~~_val_~~val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's move assignment.template constexpr optional& operator=(U&& v);
-14- Constraints: […]
-15- Effects: If
*thiscontains a value, assignsstd::forward<U>(v)to_val_the contained value; otherwise direct-non-list-initializes_val_the contained valuewithstd::forward<U>(v).-16- Postconditions:
*thiscontains a value.-17- Returns:
*this.-18- Remarks: If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofvis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the statesstateof_val_~~*val~~andvareisdetermined by the exception safety guarantee ofT's assignment.template constexpr optional& operator=(const optional& rhs);
-19- Constraints: […]
-20- Effects: See Table 60.
Table 60 — optional::operator=(const optional&) effects [tab:optional.assign.copy.templ]
*this contains a value *this does not contain a value rhs contains a value assigns *rhs.operator*() to valthe contained valuedirect-non-list-initializes val the contained valuewith*rhs.operator*()rhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -21- Postconditions:
rhs.has_value() == this->has_value().-22- Returns:
*this.-23- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the statesstateof_val_~~*val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's assignment.template constexpr optional& operator=(optional&& rhs);
-24- Constraints: […]
-25- Effects: See Table 61. The result of the expression
rhs.has_value()remains unchanged.Table 61 — optional::operator=(optional&&) effects [tab:optional.assign.move.templ]
*this contains a value *this does not contain a value rhs contains a value assigns *std::move(rhs).operator*() to valthe contained valuedirect-non-list-initializes val the contained valuewith*std::move(rhs).operator*()rhs does not contain a value destroys the contained value by calling val->val.T::~T()no effect -26- Postconditions:
rhs.has_value() == this->has_value().-27- Returns:
*this.-28- If any exception is thrown, the result of the expression
this->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state of~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the statesstateof_val_~~*val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's assignment.template<class... Args> constexpr T& emplace(Args&&... args);
-29- Mandates: […]
-30- Effects: Calls
*this = nullopt. Then direct-non-list-initializes_val_the contained valuewithstd::forward<Args>(args)....-31- Postconditions:
*thiscontains a value.-32- Returns:
_val_A reference to the new contained value.[…]
-34- Remarks: If an exception is thrown during the call to
T's constructor,*thisdoes not contain a value, and the previous_val_~~*val~~(if any) has been destroyed.template<class U, class... Args> constexpr T& emplace(initializer_list il, Args&&... args);
-35- Constraints: […]
-36- Effects: Calls
*this = nullopt. Then direct-non-list-initializes_val_the contained valuewithil, std::forward<Args>(args)....-37- Postconditions:
*thiscontains a value.-38- Returns:
_val_A reference to the new contained value.[…]
-40- Remarks: If an exception is thrown during the call to
T's constructor,*thisdoes not contain a value, and the previous_val_~~*val~~(if any) has been destroyed. 6. Modify 22.5.3.5 [optional.swap] as indicated:
constexpr void swap(optional& rhs) noexcept(see below);-1- Mandates: […]
-2- Preconditions: […]
-3- Effects: See Table 62.
Table 62 — optional::swap(optional&) effects [tab:optional.swap]
*this contains a value *this does not contain a value rhs contains a value calls swap(val *(*this),*rhs.val)direct-non-list-initializes val the contained value of *thiswith std::move(*rhs.val), followed by rhs.val.val->T::~T();postcondition is that *this contains a value and rhs doesnot contain a valuerhs does not contain a value direct-non-list-initializes the contained value ofrhs._val_with std::move(val*(*this)), followed by val.val->T::~T();postcondition is that *this does not contain a value and rhscontains a valueno effect -4- Throws: […]
-5- Remarks: […]
-6- If any exception is thrown, the results of the expressions
this->has_value()andrhs.has_value()remain unchanged. If an exception is thrown during the call to functionswap, the state of_val_~~*val~~and~~*~~rhs._val_~~val~~is determined by the exception safety guarantee ofswapfor lvalues ofT. If an exception is thrown during the call toT's move constructor, the statesstateof_val_~~*val~~and~~*~~rhs._val_~~val~~areisdetermined by the exception safety guarantee ofT's move constructor. 7. Modify 22.5.3.7 [optional.observe] as indicated:
constexpr const T* operator->() const noexcept;
constexpr T* operator->() noexcept;-1- Preconditions:
*thiscontains a value.-2- Returns:
addressof(_val_)~~val~~.-3- […]
constexpr const T& operator*() const & noexcept;
constexpr T& operator*() & noexcept;-4- Preconditions:
*thiscontains a value.-5- Returns:
_val_~~*val~~.-6- […]
constexpr T&& operator*() && noexcept;
constexpr const T&& operator*() const && noexcept;-7- Preconditions:
*thiscontains a value.-8- Effects: Equivalent to:
return std::move(_val_~~*val~~);constexpr explicit operator bool() const noexcept;
-9- Returns:
trueif and only if*thiscontains a value.-10- Remarks: This function is a constexpr function.
constexpr bool has_value() const noexcept;
-11- Returns:
trueif and only if*thiscontains a value.-12- Remarks: This function is a constexpr function.
constexpr const T& value() const &;
constexpr T& value() &;-13- Effects: Equivalent to:
return has_value() ? val
*val: throw bad_optional_access();constexpr T&& value() &&;
constexpr const T&& value() const &&;-14- Effects: Equivalent to:
return has_value() ? std::move(val
*val) : throw bad_optional_access();template constexpr T value_or(U&& v) const &;
-15- Mandates: […]
-16- Effects: Equivalent to:
return has_value() ? val
**this: static_cast(std::forward(v));template constexpr T value_or(U&& v) &&;
-17- Mandates: […]
-18- Effects: Equivalent to:
return has_value() ? std::move(val
**this) : static_cast(std::forward(v)); 8. Modify 22.5.3.8 [optional.monadic] as indicated:
template constexpr auto and_then(F&& f) &;
template constexpr auto and_then(F&& f) const &;-1- Let
Ubeinvoke_result_t<F, decltype((_val_)~~*val~~)>.-2- Mandates: […]
-3- Effects: Equivalent to:
if (*this) {
return invoke(std::forward(f), val*val);
} else {
return remove_cvref_t();
}template constexpr auto and_then(F&& f) &&;
template constexpr auto and_then(F&& f) const &&;-4- Let
Ubeinvoke_result_t<F, decltype(std::move(_val_~~*val~~))>.-5- Mandates: […]
-6- Effects: Equivalent to:
if (*this) {
return invoke(std::forward(f), std::move(val*val));
} else {
return remove_cvref_t();
}template constexpr auto transform(F&& f) &;
template constexpr auto transform(F&& f) const &;-7- Let
Uberemove_cv_t<invoke_result_t<F, decltype((_val_)~~*val~~)>>.-8- Mandates:
Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward(f), val
*val));is well-formed for some invented variable
u.[…]
-9- Returns: If
*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), _val_~~*val~~); otherwise,optional<U>().template constexpr auto transform(F&& f) &&;
template constexpr auto transform(F&& f) const &&;-10- Let
Uberemove_cv_t<invoke_result_t<F, decltype(std::move(_val_~~*val~~))>>.-11- Mandates:
Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward(f), std::move(val
*val)));is well-formed for some invented variable
u.[…]
-12- Returns: If
*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(_val_~~*val~~)); otherwise,optional<U>(). 9. Modify 22.5.3.9 [optional.mod] as indicated:
constexpr void reset() noexcept;-1- Effects: If
*thiscontains a value, calls~~val->~~_val_.T::~T()to destroy the contained value; otherwise no effect.-2- Postconditions:
*thisdoes not contain a value. 10. Modify 22.5.4.2 [optional.ref.ctor] as indicated:
template
constexpr explicit(!is_convertible_v<U&, T&>)
optional(optional& rhs) noexcept(is_nothrow_constructible_v<T&, U&>);
-8- Constraints: […]-9- Effects: Equivalent to:
if (rhs.has_value()) convert-ref-init-val(
*rhs.operator*());-10- Remarks: […]
template
constexpr explicit(!is_convertible_v<const U&, T&>)
optional(const optional& rhs) noexcept(is_nothrow_constructible_v<T&, const U&>);
-11- Constraints: […]-12- Effects: Equivalent to:
if (rhs.has_value()) convert-ref-init-val(
*rhs.operator*());-13- Remarks: […]
template
constexpr explicit(!is_convertible_v<U, T&>)
optional(optional&& rhs) noexcept(is_nothrow_constructible_v<T&, U>);
-14- Constraints: […]-15- Effects: Equivalent to:
if (rhs.has_value()) convert-ref-init-val(
*std::move(rhs).operator*());-16- Remarks: […]
template
constexpr explicit(!is_convertible_v<const U, T&>)
optional(const optional&& rhs) noexcept(is_nothrow_constructible_v<T&, const U>);
-17- Constraints: […]-18- Effects: Equivalent to:
if (rhs.has_value()) convert-ref-init-val(
*std::move(rhs).operator*());-19- Remarks: […]