`const`

overloads of `std::optional`

monadic operations**Section:** 22.5.3.7 [optional.monadic] **Status:** New
**Submitter:** Jonathan Wakely **Opened:** 2023-11-24 **Last modified:** 2023-11-29 16:22:35 UTC

**Priority: **Not Prioritized

**View all issues with** New status.

**Discussion:**

The resolution of LWG 3973 (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;~~bool has_val;// exposition only// exposition onlyunion { T val;// exposition only}; };

For example, in 22.5.3.8 [optional.mod]:

-1-

Effects: If`*this`

contains a value, calls`val`

to destroy the contained value and sets~~->~~.T::~T()`has_val`

to`false`

; 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 ` has_val` to

**Proposed resolution:**

This wording is relative to N4964 *after* application of the wording of LWG 3973.

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: bool

*has_val*; //*exposition only*union { T*val*~~*val~~; //*exposition only*}; }; […] }Modify 22.5.3.1 [optional.optional.general] as indicated:

-2- Member

indicates whether an*has_val*`optional<T>`object contains a value~~When an~~.`optional<T>`object contains a value, member`val`points 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 "[…]initializeswith" as we do in 22.8.6.2 [expected.object.cons]. I intentionally did not add extra "and sets*val*to*has_val*`true/false`" since those effects are already guaranteed by the postconditions]constexpr optional(const optional& rhs);

-4-

*Effects*: If`rhs`contains a value, direct-non-list-initializes*val*~~the contained value~~with.~~*~~rhs.*val*-5-

*Postconditions*:`rhs.has_value() == this->has_value()`.[…]

constexpr optional(optional&& rhs) noexcept(

*see below*);-8-

*Constraints*: […]-9-

*Effects*: If`rhs`contains a value, direct-non-list-initializes*val*~~the contained value~~with`std::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 value~~with`std::forward<Args>(args)...`.-15-

*Postconditions*:`*this`contains 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-initializes*val*~~the contained value~~with`il, std::forward<Args>(args)...`.-20-

*Postconditions*:`*this`contains a value.[…]

template<class U = T> constexpr explicit(

*see below*) optional(U&& v);-23-

*Constraints*: […]-24-

*Effects*: Direct-non-list-initializes*val*~~the contained value~~with`std::forward<U>(v)`.-25-

*Postconditions*:`*this`contains a value.[…]

template<class U> constexpr explicit(

*see below*) optional(const optional<U>& rhs);-28-

*Constraints*: […]-29-

*Effects*: If`rhs`contains a value, direct-non-list-initializes*val*~~the contained value~~with.~~*~~rhs.*val*-30-

*Postconditions*:`rhs.has_value() == this->has_value()`.[…]

template<class U> constexpr explicit(

*see below*) optional(optional<U>&& rhs);-33-

*Constraints*: […]-34-

*Effects*: If`rhs`contains a value, direct-non-list-initializes*val*~~the contained value~~with`std::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> != true`and`*this`contains 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`*this`contains a value, callsto destroy the contained value and sets~~val->~~*val*.T::~T()to*has_val*`false`; otherwise no effect.-2-

*Postconditions*:`*this`does 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]`*this`contains a value`*this`does not contain a value`rhs`contains a valueassigns to~~*~~rhs.*val**val*~~the contained value~~direct-non-list-initializes *val*~~the contained value~~with~~*~~rhs.*val*

and setsto*has_val*`true``rhs`does not contain a valuedestroys the contained value by calling ~~val->~~*val*.T::~T()

and setsto*has_val*`false`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 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 valueassigns `std::move(`to~~*~~rhs.*val*)*val*~~the contained value~~direct-non-list-initializes *val*~~the contained value~~with`std::move(`and sets~~*~~rhs.*val*)to*has_val*`true``rhs`does not contain a valuedestroys the contained value by calling

and sets~~val->~~*val*.T::~T()to*has_val*`false`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 to`T`'s move constructor, the state ofis determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`T`'s move constructor. If an exception is thrown during the call to`T`'s move assignment, the state ofand~~*~~*val*~~val~~is determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`T`'s move assignment.template<class U = T> constexpr optional<T>& operator=(U&& v);

-14-

*Constraints*: […]-15-

*Effects*: If`*this`contains a value, assigns`std::forward<U>(v)`to*val*~~the contained value~~; otherwise direct-non-list-initializes*val*~~the contained value~~with`std::forward<U>(v)`.-16-

*Postconditions*:`*this`contains 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 to`T`'s constructor, the state of`v`is determined by the exception safety guarantee of`T`'s constructor. If an exception is thrown during the call to`T`'s assignment, the state ofand*val*~~*val~~`v`is determined by the exception safety guarantee of`T`'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]`*this`contains a value`*this`does not contain a value`rhs`contains a valueassigns to~~*~~rhs.*val**val*~~the contained value~~direct-non-list-initializes *val*~~the contained value~~withand sets~~*~~rhs.*val*to*has_val*`true``rhs`does not contain a valuedestroys the contained value by calling

and sets~~val->~~*val*.T::~T()to*has_val*`false`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 to`T`'s constructor, the state ofis determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`T`'s constructor. If an exception is thrown during the call to`T`'s assignment, the state ofand*val*~~*val~~is determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`T`'s assignment.template<class U> constexpr optional<T>& operator=(optional<U>&& rhs);

-24-

*Constraints*: […]-25-

*Effects*: See Table 61. The result of the expression`rhs.has_value()`remains unchanged.Table 61 — `optional::operator=(optional<U>&&)`effects [tab:optional.assign.move.templ]`*this`contains a value`*this`does not contain a value`rhs`contains a valueassigns `std::move(`to~~*~~rhs.*val*)*val*~~the contained value~~direct-non-list-initializes *val*~~the contained value~~with

`std::move(`and sets~~*~~rhs.*val*)to*has_val*`true``rhs`does not contain a valuedestroys the contained value by calling

and sets~~val->~~*val*.T::~T()to*has_val*`false`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 to`T`'s constructor, the state ofis determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`T`'s constructor. If an exception is thrown during the call to`T`'s assignment, the state ofand*val*~~*val~~is determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`T`'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 value~~with`std::forward<Args>(args)...`.-31-

*Postconditions*:`*this`contains 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,`*this`does not contain a value, and the previous(if any) has been destroyed.*val*~~*val~~template<class U, class... Args> constexpr T& emplace(initializer_list<U> il, Args&&... args);

-35-

*Constraints*: […]-36-

*Effects*: Calls`*this = nullopt`. Then direct-non-list-initializes*val*~~the contained value~~with`il, std::forward<Args>(args)...`.-37-

*Postconditions*:`*this`contains 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,`*this`does not contain a value, and the previous(if any) has been destroyed.*val*~~*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]`*this`contains a value`*this`does not contain a value`rhs`contains a valuecalls `swap(`*val*~~*(*this)~~,~~*~~rhs.*val*)direct-non-list-initializes *val*~~the contained value of~~`*this`

with`std::move(`, followed by~~*~~rhs.*val*)`rhs.`;*val*.~~val->~~T::~T()

postcondition is that`*this`contains a value and`rhs`does

not contain a value`rhs`does not contain a valuedirect-non-list-initializes ~~the contained value of~~`rhs.`*val*

with`std::move(`, followed by*val*~~*(*this)~~);*val*.~~val->~~T::~T()

postcondition is that`*this`does not contain a value and`rhs`

contains a valueno effect -4-

*Throws*: […]-5-

*Remarks*: […]-6- If any exception is thrown, the results of the expressions

`this->has_value()`and`rhs.has_value()`remain unchanged. If an exception is thrown during the call to function`swap`, the state ofand*val*~~*val~~is determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`swap`for lvalues of`T`. If an exception is thrown during the call to`T`'s move constructor, the state ofand*val*~~*val~~is determined by the exception safety guarantee of~~*~~rhs.*val*~~val~~`T`'s move constructor.Modify 22.5.3.6 [optional.observe] as indicated:

constexpr const T* operator->() const noexcept; constexpr T* operator->() noexcept;

-1-

*Preconditions*:`*this`contains a value.-2-

*Returns*:`addressof(`.*val*)~~val~~-3- […]

constexpr const T& operator*() const & noexcept; constexpr T& operator*() & noexcept;

-4-

*Preconditions*:`*this`contains a value.-5-

*Returns*:.*val*~~*val~~-6- […]

constexpr T&& operator*() && noexcept; constexpr const T&& operator*() const && noexcept;

-7-

*Preconditions*:`*this`contains a value.-8-

*Effects*: Equivalent to:`return std::move(`*val*~~*val~~);constexpr explicit operator bool() const noexcept;

~~-9-~~*Returns*:`true`if and only if`*this`contains a value.~~-10-~~*Remarks*: This function is a constexpr function.constexpr bool has_value() const noexcept;

-11-

*Returns*:*has_val*.`true`if and only if`*this`contains a value-12-

*Remarks*: These functions are~~This function is a~~constexpr 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.7 [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

`U`be`invoke_result_t<F, decltype((`.*val*)~~*val~~)>-2-

*Mandates*: […]-3-

*Effects*: Equivalent to: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

`U`be`invoke_result_t<F, decltype(std::move(`.*val*~~*val~~))>-5-

*Mandates*: […]-6-

*Effects*: Equivalent to: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

`U`be`remove_cv_t<invoke_result_t<F, decltype((`.*val*)~~*val~~)>>-8-

*Mandates*:`U`is a non-array object type other than`in_place_t`or`nullopt_t`. The declarationU u(invoke(std::forward<F>(f),

*val*~~*val~~));is well-formed for some invented variable

`u`.[…]

-9-

*Returns*: If`*this`contains a value, an`optional<U>`object whose contained value is direct-non-list-initialized with`invoke(std::forward<F>(f),`; otherwise,*val*~~*val~~)`optional<U>()`.template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;

-10- Let

`U`be`remove_cv_t<invoke_result_t<F, decltype(std::move(`.*val*~~*val~~))>>-11-

*Mandates*:`U`is a non-array object type other than`in_place_t`or`nullopt_t`. The declarationU u(invoke(std::forward<F>(f), std::move(

*val*~~*val~~)));is well-formed for some invented variable

`u`.[…]

-12-

*Returns*: If`*this`contains a value, an`optional<U>`object whose contained value is direct-non-list-initialized with`invoke(std::forward<F>(f), std::move(`; otherwise,*val*~~*val~~))`optional<U>()`.Modify 22.5.3.8 [optional.mod] as indicated:

constexpr void reset() noexcept;

-1-

*Effects*: If`*this`contains a value, callsto destroy the contained value and sets~~val->~~*val*.T::~T()to*has_val*`false`; otherwise no effect.-2-

*Postconditions*:`*this`does not contain a value.