This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Immediate status.
const overloads of std::optional monadic operationsSection: 22.5.3.8 [optional.monadic] Status: Immediate Submitter: Jonathan Wakely Opened: 2023-11-24 Last modified: 2025-11-05
Priority: 1
View all issues with Immediate 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-freestanding
value() function, and avoid ADL that would be caused by using
**this.
However, in the const overloads such as
and_then(F&&) const the type of value()
was const T&, but the type of *val is always
T&. 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 onlybool has_val; // exposition only union { T val; // exposition only }; };
For example, in 22.5.3.9 [optional.mod]:
-1- Effects: If
*thiscontains a value, callsvalto destroy the contained value and sets->.T::~T()has_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.
has_val 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 has_val 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 T> class optional { public: using value_type = T; […] private: bool has_val; // exposition only union { T val*val; // exposition only }; }; […] }Modify 22.5.3.1 [optional.optional.general] as indicated:
-2- Member
has_valindicates whether anoptional<T>object contains a valueWhen an.optional<T>object contains a value, membervalpoints to the contained valueModify 22.5.3.2 [optional.ctor] as indicated:
[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
valwith" as we do in 22.8.6.2 [expected.object.cons]. I intentionally did not add extra "and setshas_valtotrue/false" since those effects are already guaranteed by the postconditions]constexpr optional(const optional& rhs);-4- Effects: If
-5- Postconditions:rhscontains a value, direct-non-list-initializesvalthe contained valuewith.*rhs.valrhs.has_value() == this->has_value(). […]constexpr optional(optional&& rhs) noexcept(see below);-8- Constraints: […]
-9- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe 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-initializesvalthe contained valuewithstd::forward<Args>(args).... -15- Postconditions:*thiscontains a value. […]template<class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);-18- Constraints: […]
-19- Effects: Direct-non-list-initializesvalthe contained valuewithil, std::forward<Args>(args).... -20- Postconditions:*thiscontains a value. […]template<class U = T> constexpr explicit(see below) optional(U&& v);-23- Constraints: […]
-24- Effects: Direct-non-list-initializesvalthe contained valuewithstd::forward<U>(v). -25- Postconditions:*thiscontains a value. […]template<class U> constexpr explicit(see below) optional(const optional<U>& rhs);-28- Constraints: […]
-29- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe contained valuewith. -30- Postconditions:*rhs.valrhs.has_value() == this->has_value(). […]template<class U> constexpr explicit(see below) optional(optional<U>&& rhs);-33- Constraints: […]
-34- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe contained valuewithstd::move(.*rhs.val)rhs.has_value()is unchanged. -35- Postconditions:rhs.has_value() == this->has_value(). […]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()Modify 22.5.3.4 [optional.assign] as indicated:
constexpr optional<T>& operator=(nullopt_t) noexcept;-1- Effects: If
-2- Postconditions:*thiscontains a value, callsto destroy the contained value and setsval->val.T::~T()has_valtofalse; otherwise no effect.*thisdoes not contain a value.constexpr optional<T>& operator=(const optional& rhs);-4- Effects: See Table 58.
Table 58 — optional::operator=(const optional&)effects [tab:optional.assign.copy]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns to*rhs.valvalthe contained valuedirect-non-list-initializes valthe contained valuewith*rhs.val
and setshas_valtotruerhsdoes not contain a valuedestroys the contained value by calling val->val.T::~T()
and setshas_valtofalseno effect -5- Postconditions:
[…]rhs.has_value() == this->has_value().constexpr optional<T>& operator=(optional&& rhs) noexcept(see below);-8- Constraints: […]
-9- Effects: See Table 59. The result of the expressionrhs.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]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns std::move(to*rhs.val)valthe contained valuedirect-non-list-initializes valthe contained valuewithstd::move(and sets*rhs.val)has_valtotruerhsdoes not contain a valuedestroys the contained value by calling
and setsval->val.T::~T()has_valtofalseno effect -12- Remarks: […]
-13- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's move constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's move constructor. If an exception is thrown during the call toT's move assignment, the state ofand*valvalis determined by the exception safety guarantee of*rhs.valvalT's move assignment.template<class U = T> constexpr optional<T>& operator=(U&& v);-14- Constraints: […]
-15- Effects: If*thiscontains a value, assignsstd::forward<U>(v)tovalthe contained value; otherwise direct-non-list-initializesvalthe contained valuewithstd::forward<U>(v). -16- Postconditions:*thiscontains a value. -17- Returns:*this. -18- Remarks: If any exception is thrown, the result of the expressionthis->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 ofvaland*valvis determined by the exception safety guarantee ofT's assignment.template<class U> constexpr optional<T>& operator=(const optional<U>& rhs);-19- Constraints: […]
-20- Effects: See Table 60.
Table 60 — optional::operator=(const optional<U>&)effects [tab:optional.assign.copy.templ]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns to*rhs.valvalthe contained valuedirect-non-list-initializes valthe contained valuewithand sets*rhs.valhas_valtotruerhsdoes not contain a valuedestroys the contained value by calling
and setsval->val.T::~T()has_valtofalseno effect -21- Postconditions:
-22- Returns:rhs.has_value() == this->has_value().*this. -23- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's constructor. If an exception is thrown during the call toT's assignment, the state ofvaland*valis determined by the exception safety guarantee of*rhs.valvalT's assignment.template<class U> constexpr optional<T>& operator=(optional<U>&& rhs);-24- Constraints: […]
-25- Effects: See Table 61. The result of the expressionrhs.has_value()remains unchanged.
Table 61 — optional::operator=(optional<U>&&)effects [tab:optional.assign.move.templ]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns std::move(to*rhs.val)valthe contained valuedirect-non-list-initializes valthe contained valuewith
std::move(and sets*rhs.val)has_valtotruerhsdoes not contain a valuedestroys the contained value by calling
and setsval->val.T::~T()has_valtofalseno effect -26- Postconditions:
-27- Returns:rhs.has_value() == this->has_value().*this. -28- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's constructor. If an exception is thrown during the call toT's assignment, the state ofvaland*valis determined by the exception safety guarantee of*rhs.valvalT's assignment.template<class... Args> constexpr T& emplace(Args&&... args);-29- Mandates: […]
-30- Effects: Calls*this = nullopt. Then direct-non-list-initializesvalthe contained valuewithstd::forward<Args>(args).... -31- Postconditions:*thiscontains a value. -32- Returns:valA reference to the new contained value. […] -34- Remarks: If an exception is thrown during the call toT's constructor,*thisdoes not contain a value, and the previousval(if any) has been destroyed.*valtemplate<class U, class... Args> constexpr T& emplace(initializer_list<U> il, Args&&... args);-35- Constraints: […]
-36- Effects: Calls*this = nullopt. Then direct-non-list-initializesvalthe contained valuewithil, std::forward<Args>(args).... -37- Postconditions:*thiscontains a value. -38- Returns:valA reference to the new contained value. […] -40- Remarks: If an exception is thrown during the call toT's constructor,*thisdoes not contain a value, and the previousval(if any) has been destroyed.*valModify 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]*thiscontains a value*thisdoes not contain a valuerhscontains a valuecalls swap(val*(*this),*rhs.val)direct-non-list-initializes valthe contained value of*this
withstd::move(, followed by*rhs.val)rhs.val.;val->T::~T()
postcondition is that*thiscontains a value andrhsdoes
not contain a valuerhsdoes not contain a valuedirect-non-list-initializes the contained value ofrhs.val
withstd::move(val, followed by*(*this))val.;val->T::~T()
postcondition is that*thisdoes not contain a value andrhs
contains a valueno effect -4- Throws: […]
-5- Remarks: […] -6- If any exception is thrown, the results of the expressionsthis->has_value()andrhs.has_value()remain unchanged. If an exception is thrown during the call to functionswap, the state ofvaland*valis determined by the exception safety guarantee of*rhs.valvalswapfor lvalues ofT. If an exception is thrown during the call toT's move constructor, the state ofvaland*valis determined by the exception safety guarantee of*rhs.valvalT's move constructor.Modify 22.5.3.7 [optional.observe] as indicated:
constexpr const T* operator->() const noexcept; constexpr T* operator->() noexcept;-1- Preconditions:
-2- Returns:*thiscontains a value.addressof(val). -3- […]valconstexpr const T& operator*() const & noexcept; constexpr T& operator*() & noexcept;-4- Preconditions:
-5- Returns:*thiscontains a value.val. -6- […]*valconstexpr T&& operator*() && noexcept; constexpr const T&& operator*() const && noexcept;-7- Preconditions:
-8- Effects: Equivalent to:*thiscontains a value.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:
-12- Remarks: These functions arehas_val.trueif and only if*thiscontains a valueThis 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<class U> constexpr T value_or(U&& v) const &;-15- Mandates: […]
-16- Effects: Equivalent to:return has_value() ? val**this: static_cast<T>(std::forward<U>(v));template<class U> constexpr T value_or(U&& v) &&;-17- Mandates: […]
-18- Effects: Equivalent to:return has_value() ? std::move(val**this) : static_cast<T>(std::forward<U>(v));Modify 22.5.3.8 [optional.monadic] as indicated:
template<class F> constexpr auto and_then(F&& f) &; template<class F> constexpr auto and_then(F&& f) const &;-1- Let
-2- Mandates: […] -3- Effects: Equivalent to:Ubeinvoke_result_t<F, decltype((val).*val)>if (*this) { return invoke(std::forward<F>(f), val*val); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto and_then(F&& f) &&; template<class F> constexpr auto and_then(F&& f) const &&;-4- Let
-5- Mandates: […] -6- Effects: Equivalent to:Ubeinvoke_result_t<F, decltype(std::move(val.*val))>if (*this) { return invoke(std::forward<F>(f), std::move(val*val)); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto transform(F&& f) &; template<class F> constexpr auto transform(F&& f) const &;-7- Let
-8- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype((val).*val)>>Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward<F>(f), val*val));is well-formed for some invented variable
[…] -9- Returns: Ifu.*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), val; otherwise,*val)optional<U>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;-10- Let
-11- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype(std::move(val.*val))>>Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward<F>(f), std::move(val*val)));is well-formed for some invented variable
[…] -12- Returns: Ifu.*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(val; otherwise,*val))optional<U>().Modify 22.5.3.9 [optional.mod] as indicated:
constexpr void reset() noexcept;-1- Effects: If
-2- Postconditions:*thiscontains a value, callsto destroy the contained value and setsval->val.T::~T()has_valtofalse; otherwise no effect.*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(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 T> class optional { public: using value_type = T; […] private:*val // exposition only; union { remove_cv_t<T> val; // exposition only }; }; […] }Modify 22.5.3.1 [optional.optional.general] as indicated:
-1- When its member
valis active (11.5.1 [class.union.general]), an instance ofoptional<T>is said to contain a value, andvalis referred to as its contained value.Any instance ofAn optional object's contained 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 anoptional<T>object contains a value, membervalpoints to the contained value.Modify 22.5.3.2 [optional.ctor] as indicated:
constexpr optional(const optional& rhs);-4- Effects: If
-5- Postconditions:rhscontains a value, direct-non-list-initializesvalthe contained valuewith.*rhs.valrhs.has_value() == this->has_value(). […]constexpr optional(optional&& rhs) noexcept(see below);-8- Constraints: […]
-9- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe 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-initializesvalthe contained valuewithstd::forward<Args>(args).... -15- Postconditions:*thiscontains a value. […]template<class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);-18- Constraints: […]
-19- Effects: Direct-non-list-initializesvalthe contained valuewithil, std::forward<Args>(args).... -20- Postconditions:*thiscontains a value. […]template<class U = T> constexpr explicit(see below) optional(U&& v);-23- Constraints: […]
-24- Effects: Direct-non-list-initializesvalthe contained valuewithstd::forward<U>(v). -25- Postconditions:*thiscontains a value. […]template<class U> constexpr explicit(see below) optional(const optional<U>& rhs);-28- Constraints: […]
-29- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe contained valuewith. -30- Postconditions:*rhs.valrhs.has_value() == this->has_value(). […]template<class U> constexpr explicit(see below) optional(optional<U>&& rhs);-33- Constraints: […]
-34- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe contained valuewithstd::move(.*rhs.val)rhs.has_value()is unchanged. -35- Postconditions:rhs.has_value() == this->has_value(). […]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()Modify 22.5.3.4 [optional.assign] as indicated:
constexpr optional<T>& operator=(nullopt_t) noexcept;-1- Effects: If
-2- Postconditions:*thiscontains a value, callsto destroy the contained value; otherwise no effect.val->val.T::~T()*thisdoes not contain a value.constexpr optional<T>& operator=(const optional& rhs);-4- Effects: See Table 58.
Table 58 — optional::operator=(const optional&)effects [tab:optional.assign.copy]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns to*rhs.valvalthe contained valuedirect-non-list-initializes valthe contained valuewith*rhs.val
rhsdoes not contain a valuedestroys the contained value by calling val->val.T::~T()
no effect -5- Postconditions:
[…]rhs.has_value() == this->has_value().constexpr optional<T>& operator=(optional&& rhs) noexcept(see below);-8- Constraints: […]
-9- Effects: See Table 59. The result of the expressionrhs.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]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns std::move(to*rhs.val)valthe contained valuedirect-non-list-initializes valthe contained valuewithstd::move(*rhs.val)rhsdoes not contain a valuedestroys the contained value by calling
val->val.T::~T()no effect -12- Remarks: […]
-13- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's move constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's move constructor. If an exception is thrown during the call toT's move assignment, the statesstateofand*valvalare*rhs.valvalisdetermined by the exception safety guarantee ofT's move assignment.template<class U = T> constexpr optional<T>& operator=(U&& v);-14- Constraints: […]
-15- Effects: If*thiscontains a value, assignsstd::forward<U>(v)tovalthe contained value; otherwise direct-non-list-initializesvalthe contained valuewithstd::forward<U>(v). -16- Postconditions:*thiscontains a value. -17- Returns:*this. -18- Remarks: If any exception is thrown, the result of the expressionthis->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 statesstateofvaland*valvareisdetermined by the exception safety guarantee ofT's assignment.template<class U> constexpr optional<T>& operator=(const optional<U>& rhs);-19- Constraints: […]
-20- Effects: See Table 60.
Table 60 — optional::operator=(const optional<U>&)effects [tab:optional.assign.copy.templ]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns to*rhs.valvalthe contained valuedirect-non-list-initializes valthe contained valuewith*rhs.valrhsdoes not contain a valuedestroys the contained value by calling
val->val.T::~T()no effect -21- Postconditions:
-22- Returns:rhs.has_value() == this->has_value().*this. -23- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's constructor. If an exception is thrown during the call toT's assignment, the statesstateofvaland*valare*rhs.valvalisdetermined by the exception safety guarantee ofT's assignment.template<class U> constexpr optional<T>& operator=(optional<U>&& rhs);-24- Constraints: […]
-25- Effects: See Table 61. The result of the expressionrhs.has_value()remains unchanged.
Table 61 — optional::operator=(optional<U>&&)effects [tab:optional.assign.move.templ]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns std::move(to*rhs.val)valthe contained valuedirect-non-list-initializes valthe contained valuewith
std::move(*rhs.val)rhsdoes not contain a valuedestroys the contained value by calling
val->val.T::~T()no effect -26- Postconditions:
-27- Returns:rhs.has_value() == this->has_value().*this. -28- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's constructor. If an exception is thrown during the call toT's assignment, the statesstateofvaland*valare*rhs.valvalisdetermined 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-initializesvalthe contained valuewithstd::forward<Args>(args).... -31- Postconditions:*thiscontains a value. -32- Returns:valA reference to the new contained value. […] -34- Remarks: If an exception is thrown during the call toT's constructor,*thisdoes not contain a value, and the previousval(if any) has been destroyed.*valtemplate<class U, class... Args> constexpr T& emplace(initializer_list<U> il, Args&&... args);-35- Constraints: […]
-36- Effects: Calls*this = nullopt. Then direct-non-list-initializesvalthe contained valuewithil, std::forward<Args>(args).... -37- Postconditions:*thiscontains a value. -38- Returns:valA reference to the new contained value. […] -40- Remarks: If an exception is thrown during the call toT's constructor,*thisdoes not contain a value, and the previousval(if any) has been destroyed.*valModify 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]*thiscontains a value*thisdoes not contain a valuerhscontains a valuecalls swap(val*(*this),*rhs.val)direct-non-list-initializes valthe contained value of*this
withstd::move(, followed by*rhs.val)rhs.val.;val->T::~T()
postcondition is that*thiscontains a value andrhsdoes
not contain a valuerhsdoes not contain a valuedirect-non-list-initializes the contained value ofrhs.val
withstd::move(val, followed by*(*this))val.;val->T::~T()
postcondition is that*thisdoes not contain a value andrhs
contains a valueno effect -4- Throws: […]
-5- Remarks: […] -6- If any exception is thrown, the results of the expressionsthis->has_value()andrhs.has_value()remain unchanged. If an exception is thrown during the call to functionswap, the state ofvaland*valis determined by the exception safety guarantee of*rhs.valvalswapfor lvalues ofT. If an exception is thrown during the call toT's move constructor, the statesstateofvaland*valare*rhs.valvalisdetermined by the exception safety guarantee ofT's move constructor.Modify 22.5.3.7 [optional.observe] as indicated:
constexpr const T* operator->() const noexcept; constexpr T* operator->() noexcept;-1- Preconditions:
-2- Returns:*thiscontains a value.addressof(val). -3- […]valconstexpr const T& operator*() const & noexcept; constexpr T& operator*() & noexcept;-4- Preconditions:
-5- Returns:*thiscontains a value.val. -6- […]*valconstexpr T&& operator*() && noexcept; constexpr const T&& operator*() const && noexcept;-7- Preconditions:
-8- Effects: Equivalent to:*thiscontains a value.return std::move(val*val);constexpr explicit operator bool() const noexcept;-9- Returns:
-10- Remarks: This function is a constexpr function.trueif and only if*thiscontains a value.constexpr bool has_value() const noexcept;-11- Returns:
-12- Remarks: This function is a constexpr function.trueif and only if*thiscontains a value.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<class U> constexpr T value_or(U&& v) const &;-15- Mandates: […]
-16- Effects: Equivalent to:return has_value() ? val**this: static_cast<T>(std::forward<U>(v));template<class U> constexpr T value_or(U&& v) &&;-17- Mandates: […]
-18- Effects: Equivalent to:return has_value() ? std::move(val**this) : static_cast<T>(std::forward<U>(v));Modify 22.5.3.8 [optional.monadic] as indicated:
template<class F> constexpr auto and_then(F&& f) &; template<class F> constexpr auto and_then(F&& f) const &;-1- Let
-2- Mandates: […] -3- Effects: Equivalent to:Ubeinvoke_result_t<F, decltype((val).*val)>if (*this) { return invoke(std::forward<F>(f), val*val); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto and_then(F&& f) &&; template<class F> constexpr auto and_then(F&& f) const &&;-4- Let
-5- Mandates: […] -6- Effects: Equivalent to:Ubeinvoke_result_t<F, decltype(std::move(val.*val))>if (*this) { return invoke(std::forward<F>(f), std::move(val*val)); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto transform(F&& f) &; template<class F> constexpr auto transform(F&& f) const &;-7- Let
-8- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype((val).*val)>>Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward<F>(f), val*val));is well-formed for some invented variable
[…] -9- Returns: Ifu.*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), val; otherwise,*val)optional<U>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;-10- Let
-11- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype(std::move(val.*val))>>Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward<F>(f), std::move(val*val)));is well-formed for some invented variable
[…] -12- Returns: Ifu.*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(val; otherwise,*val))optional<U>().Modify 22.5.3.9 [optional.mod] as indicated:
constexpr void reset() noexcept;-1- Effects: If
-2- Postconditions:*thiscontains a value, callsto destroy the contained value; otherwise no effect.val->val.T::~T()*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.]
Proposed resolution:
This wording is relative to N5014.
Modify 22.5.3.1 [optional.optional.general], class template optional synopsis, as indicated:
namespace std {
template<class T>
class optional {
public:
using value_type = T;
[…]
private:
T* val; // exposition only
union {
remove_cv_t<T> val; // exposition only
};
};
[…]
}
Modify 22.5.3.1 [optional.optional.general] as indicated:
-1- An instance of
optional<T>is said to contain a value when and only when its membervalis active (11.5.1 [class.union.general]);valis referred to as its contained value.An object of typeAn optional object's contained 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 anoptional<T>object contains a value, membervalpoints to the contained value.
Modify 22.5.3.2 [optional.ctor] as indicated:
constexpr optional(const optional& rhs);-4- Effects: If
-5- Postconditions:rhscontains a value, direct-non-list-initializesvalthe contained valuewith.*rhs.valrhs.has_value() == this->has_value(). […]constexpr optional(optional&& rhs) noexcept(see below);-8- Constraints: […]
-9- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe 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-initializesvalthe contained valuewithstd::forward<Args>(args).... -15- Postconditions:*thiscontains a value. […]template<class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);-18- Constraints: […]
-19- Effects: Direct-non-list-initializesvalthe contained valuewithil, std::forward<Args>(args).... -20- Postconditions:*thiscontains a value. […]template<class U = T> constexpr explicit(see below) optional(U&& v);-23- Constraints: […]
-24- Effects: Direct-non-list-initializesvalthe contained valuewithstd::forward<U>(v). -25- Postconditions:*thiscontains a value. […]template<class U> constexpr explicit(see below) optional(const optional<U>& rhs);-28- Constraints: […]
-29- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe contained valuewith. -30- Postconditions:*rhs.operator*()rhs.has_value() == this->has_value(). […]template<class U> constexpr explicit(see below) optional(optional<U>&& rhs);-33- Constraints: […]
-34- Effects: Ifrhscontains a value, direct-non-list-initializesvalthe contained valuewith.*std::move(rhs).operator*()rhs.has_value()is unchanged. -35- Postconditions:rhs.has_value() == this->has_value(). […]
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()
Modify 22.5.3.4 [optional.assign] as indicated:
constexpr optional<T>& operator=(nullopt_t) noexcept;-1- Effects: If
-2- Postconditions:*thiscontains a value, callsto destroy the contained value; otherwise no effect.val->val.T::~T()*thisdoes not contain a value.constexpr optional<T>& operator=(const optional& rhs);-4- Effects: See Table 58.
Table 58 — optional::operator=(const optional&)effects [tab:optional.assign.copy]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns to*rhs.valvalthe contained valuedirect-non-list-initializes valthe contained valuewith*rhs.val
rhsdoes not contain a valuedestroys the contained value by calling val->val.T::~T()
no effect -5- Postconditions:
[…]rhs.has_value() == this->has_value().constexpr optional<T>& operator=(optional&& rhs) noexcept(see below);-8- Constraints: […]
-9- Effects: See Table 59. The result of the expressionrhs.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]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns std::move(to*rhs.val)valthe contained valuedirect-non-list-initializes valthe contained valuewithstd::move(*rhs.val)rhsdoes not contain a valuedestroys the contained value by calling
val->val.T::~T()no effect -12- Remarks: […]
-13- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's move constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's move constructor. If an exception is thrown during the call toT's move assignment, the statesstateofand*valvalare*rhs.valvalisdetermined by the exception safety guarantee ofT's move assignment.template<class U = T> constexpr optional<T>& operator=(U&& v);-14- Constraints: […]
-15- Effects: If*thiscontains a value, assignsstd::forward<U>(v)tovalthe contained value; otherwise direct-non-list-initializesvalthe contained valuewithstd::forward<U>(v). -16- Postconditions:*thiscontains a value. -17- Returns:*this. -18- Remarks: If any exception is thrown, the result of the expressionthis->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 statesstateofvaland*valvareisdetermined by the exception safety guarantee ofT's assignment.template<class U> constexpr optional<T>& operator=(const optional<U>& rhs);-19- Constraints: […]
-20- Effects: See Table 60.
Table 60 — optional::operator=(const optional<U>&)effects [tab:optional.assign.copy.templ]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns to*rhs.operator*()valthe contained valuedirect-non-list-initializes valthe contained valuewith*rhs.operator*()rhsdoes not contain a valuedestroys the contained value by calling
val->val.T::~T()no effect -21- Postconditions:
-22- Returns:rhs.has_value() == this->has_value().*this. -23- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's constructor. If an exception is thrown during the call toT's assignment, the statesstateofvaland*valare*rhs.valvalisdetermined by the exception safety guarantee ofT's assignment.template<class U> constexpr optional<T>& operator=(optional<U>&& rhs);-24- Constraints: […]
-25- Effects: See Table 61. The result of the expressionrhs.has_value()remains unchanged.
Table 61 — optional::operator=(optional<U>&&)effects [tab:optional.assign.move.templ]*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns to*std::move(rhs).operator*()valthe contained valuedirect-non-list-initializes valthe contained valuewith
*std::move(rhs).operator*()rhsdoes not contain a valuedestroys the contained value by calling
val->val.T::~T()no effect -26- Postconditions:
-27- Returns:rhs.has_value() == this->has_value().*this. -28- If any exception is thrown, the result of the expressionthis->has_value()remains unchanged. If an exception is thrown during the call toT's constructor, the state ofis determined by the exception safety guarantee of*rhs.valvalT's constructor. If an exception is thrown during the call toT's assignment, the statesstateofvaland*valare*rhs.valvalisdetermined 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-initializesvalthe contained valuewithstd::forward<Args>(args).... -31- Postconditions:*thiscontains a value. -32- Returns:valA reference to the new contained value. […] -34- Remarks: If an exception is thrown during the call toT's constructor,*thisdoes not contain a value, and the previousval(if any) has been destroyed.*valtemplate<class U, class... Args> constexpr T& emplace(initializer_list<U> il, Args&&... args);-35- Constraints: […]
-36- Effects: Calls*this = nullopt. Then direct-non-list-initializesvalthe contained valuewithil, std::forward<Args>(args).... -37- Postconditions:*thiscontains a value. -38- Returns:valA reference to the new contained value. […] -40- Remarks: If an exception is thrown during the call toT's constructor,*thisdoes not contain a value, and the previousval(if any) has been destroyed.*val
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]*thiscontains a value*thisdoes not contain a valuerhscontains a valuecalls swap(val*(*this),*rhs.val)direct-non-list-initializes valthe contained value of*this
withstd::move(, followed by*rhs.val)rhs.val.;val->T::~T()
postcondition is that*thiscontains a value andrhsdoes
not contain a valuerhsdoes not contain a valuedirect-non-list-initializes the contained value ofrhs.val
withstd::move(val, followed by*(*this))val.;val->T::~T()
postcondition is that*thisdoes not contain a value andrhs
contains a valueno effect -4- Throws: […]
-5- Remarks: […] -6- If any exception is thrown, the results of the expressionsthis->has_value()andrhs.has_value()remain unchanged. If an exception is thrown during the call to functionswap, the state ofvaland*valis determined by the exception safety guarantee of*rhs.valvalswapfor lvalues ofT. If an exception is thrown during the call toT's move constructor, the statesstateofvaland*valare*rhs.valvalisdetermined by the exception safety guarantee ofT's move constructor.
Modify 22.5.3.7 [optional.observe] as indicated:
constexpr const T* operator->() const noexcept; constexpr T* operator->() noexcept;-1- Preconditions:
-2- Returns:*thiscontains a value.addressof(val). -3- […]valconstexpr const T& operator*() const & noexcept; constexpr T& operator*() & noexcept;-4- Preconditions:
-5- Returns:*thiscontains a value.val. -6- […]*valconstexpr T&& operator*() && noexcept; constexpr const T&& operator*() const && noexcept;-7- Preconditions:
-8- Effects: Equivalent to:*thiscontains a value.return std::move(val*val);constexpr explicit operator bool() const noexcept;-9- Returns:
-10- Remarks: This function is a constexpr function.trueif and only if*thiscontains a value.constexpr bool has_value() const noexcept;-11- Returns:
-12- Remarks: This function is a constexpr function.trueif and only if*thiscontains a value.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<class U> constexpr T value_or(U&& v) const &;-15- Mandates: […]
-16- Effects: Equivalent to:return has_value() ? val**this: static_cast<T>(std::forward<U>(v));template<class U> constexpr T value_or(U&& v) &&;-17- Mandates: […]
-18- Effects: Equivalent to:return has_value() ? std::move(val**this) : static_cast<T>(std::forward<U>(v));
Modify 22.5.3.8 [optional.monadic] as indicated:
template<class F> constexpr auto and_then(F&& f) &; template<class F> constexpr auto and_then(F&& f) const &;-1- Let
-2- Mandates: […] -3- Effects: Equivalent to:Ubeinvoke_result_t<F, decltype((val).*val)>if (*this) { return invoke(std::forward<F>(f), val*val); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto and_then(F&& f) &&; template<class F> constexpr auto and_then(F&& f) const &&;-4- Let
-5- Mandates: […] -6- Effects: Equivalent to:Ubeinvoke_result_t<F, decltype(std::move(val.*val))>if (*this) { return invoke(std::forward<F>(f), std::move(val*val)); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto transform(F&& f) &; template<class F> constexpr auto transform(F&& f) const &;-7- Let
-8- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype((val).*val)>>Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward<F>(f), val*val));is well-formed for some invented variable
[…] -9- Returns: Ifu.*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), val; otherwise,*val)optional<U>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;-10- Let
-11- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype(std::move(val.*val))>>Uis a non-array object type other thanin_place_tornullopt_t. The declarationU u(invoke(std::forward<F>(f), std::move(val*val)));is well-formed for some invented variable
[…] -12- Returns: Ifu.*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(val; otherwise,*val))optional<U>().
Modify 22.5.3.9 [optional.mod] as indicated:
constexpr void reset() noexcept;-1- Effects: If
-2- Postconditions:*thiscontains a value, callsto destroy the contained value; otherwise no effect.val->val.T::~T()*thisdoes not contain a value.
Modify 22.5.4.2 [optional.ref.ctor] as indicated:
template<class U> constexpr explicit(!is_convertible_v<U&, T&>) optional(optional<U>& rhs) noexcept(is_nothrow_constructible_v<T&, U&>);
-8- Constraints: […] -9- Effects: Equivalent to:if (rhs.has_value()) convert-ref-init-val(-10- Remarks: […]*rhs.operator*());
template<class U> constexpr explicit(!is_convertible_v<const U&, T&>) optional(const optional<U>& rhs) noexcept(is_nothrow_constructible_v<T&, const U&>);
-11- Constraints: […] -12- Effects: Equivalent to:if (rhs.has_value()) convert-ref-init-val(-13- Remarks: […]*rhs.operator*());
template<class U> constexpr explicit(!is_convertible_v<U, T&>) optional(optional<U>&& rhs) noexcept(is_nothrow_constructible_v<T&, U>);
-14- Constraints: […] -15- Effects: Equivalent to:if (rhs.has_value()) convert-ref-init-val(-16- Remarks: […]*std::move(rhs).operator*());
template<class U> constexpr explicit(!is_convertible_v<const U, T&>) optional(const optional<U>&& rhs) noexcept(is_nothrow_constructible_v<T&, const U>);
-17- Constraints: […] -18- Effects: Equivalent to:if (rhs.has_value()) convert-ref-init-val(-19- Remarks: […]*std::move(rhs).operator*());