optional<T>
should 'forward' T
's implicit conversionsSection: 22.5.3 [optional.optional] Status: C++17 Submitter: Casey Carter Opened: 2016-07-26 Last modified: 2017-07-30
Priority: 1
View all other issues in [optional.optional].
View all issues with C++17 status.
Discussion:
LWG 2451 adds converting constructors and assignment operators to optional
. The committee
voted to apply it to the Library Fundamentals 2 TS WP in Oulu as part of LWG Motion 3. In both LWG and LEWG
discussion of this issue, it was considered to be critical to apply to the specification of optional
before shipping C++17 — it was an oversight on the part of LWG that there was no motion brought to apply
this resolution to the C++ WP.
constexpr
specifier from the declarations of the
converting constructors from const optional<U>&
and optional<U>&&
since they are not implementable as constexpr
constructors in C++17.
This issue proposes application of the resolution of LWG 2451 as amended by LWG 2745
to the specification of optional
in the C++ WP.
[2016-07 — Chicago]
Monday: Priority set to 1; will re-review later in the week
[2016-08-03, Tomasz comments]
Value forwarding constructor (template<typename U> optional(U&&)
) is underspecified.
For the following use code:
optional<T> o1; optional<T> o2(o1);
The second constructor will invoke value forwarding (U = optional<T>&)
constructor (better match)
instead of the optional<T>
copy constructor, in case if T
can be constructed from
optional<T>
. This happens for any type T
that has unconstrained perfect forwarding constructor,
especially optional<any>
.
The behavior of the construction of the optional<T> ot
from optional<U> ou
is
inconsistent for classes T
than can be constructed both from optional<U>
and U
.
There are two possible semantics for such operation:
unwrapping: if ou
is initialized (bool(ou)
), initialize contained value of ot
from *ou
, otherwise make ot
uninitialized (!bool(ot)
)
value forwarding: initialize contained value of ot
from ou
, ot
is always
initialized (bool(ot)
).
For example, if we consider following class:
struct Widget { Widget(int); Widget(optional<int>); };
Notice, that such set of constructor is pretty common in situation when the construction of the
Widget
from known value is common and usage of optional
version is rare. In such situation,
presence of Widget(int)
construction is an optimization used to avoid unnecessary empty checks and
construction optional<int>
.
optional<Widget> w1(make_optional(10)); optional<Widget> w2; w2 = make_optional(10);
The w1
will contain a value created using Widget(optional<int>)
constructor,
as corresponding unwrapping constructor (optional<U> const&
) is eliminated by
is_constructible_v<T, const optional<U>&>
(is_constructible_v<Widget, const optional<int>&>
) having a true
value.
In contrast w2
will contain a value created using Widget(int)
constructor, as corresponding
value forwarding assignment (U&&
) is eliminated by the fact that
std::decay_t<U>
(optional<int>
) is specialization of optional
.
struct Thingy { Thingy(optional<string>); }; optional<Thingy> t1(optional<string>("test"));
The t1
has a contained value constructed from using Thingy(optional<std::string>)
,
as unwrapping constructor (optional<U> const&
) are eliminated by the
is_constructible<T, U const&>
(is_constructible<Thingy, std::string const&>
)
being false
. However the following:
t1 = optional<std::string>("test2");
will not compile, because, the value forwarding assignment (U&&
) is eliminated by
std::decay_t<U>
(optional<std::string>
) being optional specialization and the
unwrapping assignment (optional<U> const&
) is ill-formed because
is_constructible<T, U const&>
(is_constructible<Thingy, std::string const&>
)
is false
.
The semantics of construction and assignment, of optional<optional<V>>
from
optional<U>
where U
is convertible to/ same as T
is also inconsistent. Firstly,
in this situation the optional<V>
is a type that can be constructed both from
optional<U>
and U
so it fails into set of problem described above. However in
addition we have following non-template constructor in optional<T>
:
optional(T const&);
Which for optional<optional<V>>
is equivalent to:
optional(optional<V> const&);
While there is no corresponding non-template assignment from T const&
, to make sure that
o = {};
always clear an optional o
.
optional<int> oi; optional<short> os; optional<optional<int>> ooi1(oi); optional<optional<int>> ooi2(os);
The ooi1
uses from-T
constructor, while ooi2
uses value forwarding constructor.
In this case both ooi1
and ooi2
are initialized and their contained values *ooi1
,
*ooi2
are uninitialized optional
s. However, if we will attempt to make construction consistent
with assignment, by preferring unwrapping (optional<U> const&
), then ooi2
will end up being uninitialized.
T
construction/assignment is to subtle to being handled as defect report and requires
a full paper analyzing possible design and their pros/cons.
Tuesday PM: Ville and Eric to implement and report back in Issaquah. Moved to Open
Ville notes that 2746, 2754 and 2756 all go together.
Previous resolution [SUPERSEDED]:
This wording is relative to N4606.
Modify 22.5.3 [optional.optional] as indicated:
template <class T> class optional { public: using value_type = T; // 20.6.3.1, Constructors constexpr optional() noexcept; constexpr optional(nullopt_t) noexcept; optional(const optional &); optional(optional &&) noexcept(see below); constexpr optional(const T &); constexpr optional(T &&); template <class... Args> constexpr explicit optional(in_place_t, Args &&...); template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args &&...); template <class U> constexpr optional(U &&); template <class U> optional(const optional<U> &); template <class U> optional(optional<U> &&); […] // 20.6.3.3, Assignment optional &operator=(nullopt_t) noexcept; optional &operator=(const optional &); optional &operator=(optional &&) noexcept(see below); template <class U> optional &operator=(U &&); template <class U> optional& operator=(const optional<U> &); template <class U> optional& operator=(optional<U> &&); template <class... Args> void emplace(Args &&...); template <class U, class... Args> void emplace(initializer_list<U>, Args &&...); […] };In [optional.object.ctor], insert new signature specifications after p31:
[Note: The following constructors are conditionally specified as
explicit
. This is typically implemented by declaring two such constructors, of which at most one participates in overload resolution. — end note]template <class U> constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
-?- Postconditions:T
with the expressionstd::forward<U>(v)
.*this
contains a value. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
andU
is not the same type asT
. The constructor isexplicit
if and only ifis_convertible_v<U&&, T>
isfalse
.template <class U> optional(const optional<U>& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expression*rhs
.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, const U&>
istrue
,is_same<decay_t<U>, T>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
andis_convertible_v<const optional<U>&, T>
isfalse
. The constructor isexplicit
if and only ifis_convertible_v<const U&, T>
isfalse
.template <class U> optional(optional<U>&& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(*rhs)
.bool(rhs)
is unchanged.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
,is_same<decay_t<U>, T>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
andis_convertible_v<optional<U>&&, T>
isfalse
andU
is not the same type asT
. The constructor isexplicit
if and only ifis_convertible_v<U&&, T>
isfalse
.In [optional.object.assign], change as indicated:
template <class U> optional<T>& operator=(U&& v);-22- Remarks: If any exception is thrown, the result of the expression
bool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state ofv
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
andv
is determined by the exception safety guarantee ofT
's assignment. The function shall not participate in overload resolution unlessdecay_t<U>
is notnullopt_t
anddecay_t<U>
is not a specialization ofoptional
.is_same_v<decay_t<U>, T>
istrue
-23- Notes: The reason for providing such generic assignment and then constraining it so that effectivelyT == U
is to guarantee that assignment of the formo = {}
is unambiguous.template <class U> optional<T>& operator=(const optional<U>& rhs);-?- Requires:
-?- Effects:is_constructible_v<T, const U&>
istrue
andis_assignable_v<T&, const U&>
istrue
.
Table ? — optional::operator=(const optional<U>&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns *rhs
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
with*rhs
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. The function shall not participate in overload resolution unlessis_same_v<decay_t<U>, T>
isfalse
.template <class U> optional<T>& operator=(optional<U>&& rhs);-?- Requires:
-?- Effects: The result of the expressionis_constructible_v<T, U>
istrue
andis_assignable_v<T&, U>
istrue
.bool(rhs)
remains unchanged.
Table ? — optional::operator=(optional<U>&&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns std::move(*rhs)
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
withstd::move(*rhs)
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. The function shall not participate in overload resolution unlessis_same_v<decay_t<U>, T>
isfalse
.
[2016-08-05 Chicago LWG]
Ville provides revised wording, that also fixes LWG 2753.
Rationale:The resolution of LWG 2753 makes special member functions defined as deleted in case the desired constraints aren't met.
There is no decay for the converting
constructor optional(U&&)
, there is a remove_reference
instead.
The target type may hold a cv-qualified type, and the incoming
type may hold a cv-qualified type, but neither can hold a reference.
Thus, remove_reference
is what we need, remove_cv
would
be wrong, and decay
would be wrong.
There is no decay
or remove_reference
for converting constructors like
optional(optional<U>)
, because none is needed.
For optional(U&&)
, an added constraint is that U
is
not a specialization of optional
[2016-08, Chicago]
Fri PM: Move to Tentatively Ready
Previous resolution [SUPERSEDED]:
This wording is relative to N4606.
Modify 22.5.3 [optional.optional] as indicated:
template <class T> class optional { public: using value_type = T; // 20.6.3.1, Constructors constexpr optional() noexcept; constexpr optional(nullopt_t) noexcept; optional(const optional &); optional(optional &&) noexcept(see below); constexpr optional(const T &); constexpr optional(T &&); template <class... Args> constexpr explicit optional(in_place_t, Args &&...); template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args &&...); template <class U> EXPLICIT constexpr optional(U &&); template <class U> EXPLICIT optional(const optional<U> &); template <class U> EXPLICIT optional(optional<U> &&); […] // 20.6.3.3, Assignment optional &operator=(nullopt_t) noexcept; optional &operator=(const optional &); optional &operator=(optional &&) noexcept(see below); template <class U> optional &operator=(U &&); template <class U> optional& operator=(const optional<U> &); template <class U> optional& operator=(optional<U> &&); template <class... Args> void emplace(Args &&...); template <class U, class... Args> void emplace(initializer_list<U>, Args &&...); […] };Change [optional.object.ctor] as indicated:
optional(const optional<T>& rhs);[…] -?- Remarks: This constructor shall be defined as deleted unless
-3- Requires:is_copy_constructible_v<T>
istrue
.is_copy_constructible_v<T>
istrue
.optional(optional<T>&& rhs) noexcept(see below);[…] -11- Remarks: The expression inside
-7- Requires:is_move_constructible_v<T>
istrue
.noexcept
is equivalent tois_nothrow_move_constructible_v<T>
. This constructor shall be defined as deleted unlessis_move_constructible_v<T>
istrue
.constexpr optional(const T& v);[…] -16- Remarks: If
-12- Requires:is_copy_constructible_v<T>
istrue
.T
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_copy_constructible_v<T>
istrue
.constexpr optional(T&& v);[…] -21- Remarks: If
-17- Requires:is_move_constructible_v<T>
istrue
.T
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
.template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);[…] -26- Remarks: If
-22- Requires:is_constructible_v<T, Args&&...>
istrue
.T
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, Args...>
istrue
.template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);[…] -31- Remarks: The
-27- Requires:is_constructible_v<T, initializer_list<U>&, Args&&...>
istrue
.functionconstructor shall not participate in overload resolution unlessis_constructible_v<T, initializer_list<U>&, Args&&...>
istrue
. IfT
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.[Note: The following constructors are conditionally specified as explicit. This is typically implemented by declaring two such constructors, of which at most one participates in overload resolution. — end note]
template <class U> EXPLICIT constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
-?- Postconditions:T
with the expressionstd::forward<U>(v)
.*this
contains a value. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
,remove_reference_t<U>
is not the same type asT
, andU
is not a specialization ofoptional
. The constructor is explicit if and only ifis_convertible_v<U&&, T>
isfalse
.template <class U> EXPLICIT optional(const optional<U>& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expression*rhs
.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, const U&>
istrue
andis_same_v<U, T>
isfalse
. The constructor is explicit if and only ifis_convertible_v<const U&, T>
isfalse
.template <class U> EXPLICIT optional(optional<U>&& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(*rhs)
.bool(rhs)
is unchanged.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
andis_same_v<U, T>
isfalse
. The constructor is explicit if and only ifis_convertible_v<U&&, T>
isfalse
.Change [optional.object.assign] as indicated:
optional<T>& operator=(const optional<T>& rhs);[…] -8- Remarks: If any exception is thrown, the result of the expression
-4- Requires:is_copy_constructible_v<T>
istrue
andis_copy_assignable_v<T>
istrue
.bool(*this)
remains unchanged. If an exception is thrown during the call toT
's copy constructor, no effect. If an exception is thrown during the call toT
's copy assignment, the state of its contained value is as defined by the exception safety guarantee ofT
's copy assignment. This operator shall be defined as deleted unlessis_copy_constructible_v<T>
istrue
andis_copy_assignable_v<T>
istrue
.optional<T>& operator=(optional<T>&& rhs) noexcept(see below);[…] -13- Remarks: The expression inside
-9- Requires:is_move_constructible_v<T>
istrue
andis_move_assignable_v<T>
istrue
.noexcept
is equivalent to:is_nothrow_move_assignable_v<T> && is_nothrow_move_constructible_v<T>-14- If any exception is thrown, the result of the expression
bool(*this)
remains unchanged. If an exception is thrown during the call toT
's move constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's move assignment. This operator shall be defined as deleted unlessis_move_constructible_v<T>
istrue
andis_move_assignable_v<T>
istrue
.template <class U> optional<T>& operator=(U&& v);[…] -19- Remarks: If any exception is thrown, the result of the expression
-15- Requires:is_constructible_v<T, U>
istrue
andis_assignable_v<T&, U>
istrue
.bool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state ofv
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
andv
is determined by the exception safety guarantee ofT
's assignment.TheThis function shall not participate in overload resolution unlessis_same_v<decay_t<U>, T>
decay_t<U>
is notnullopt_t
,decay_t<U>
is not a specialization ofoptional
,is_constructible_v<T, U>
istrue
andis_assignable_v<T&, U>
istrue
.-20- Notes: The reason for providing such generic assignment and then constraining it so that effectivelyT == U
is to guarantee that assignment of the formo = {}
is unambiguous.template <class U> optional<T>& operator=(const optional<U>& rhs);-?- Effects:
Table ? — optional::operator=(const optional<U>&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns *rhs
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
with*rhs
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. The function shall not participate in overload resolution unlessis_constructible_v<T, const U&>
istrue
andis_assignable_v<T&, const U&>
istrue
andis_same_v<U, T>
isfalse
.template <class U> optional<T>& operator=(optional<U>&& rhs);-?- Effects: The result of the expression
bool(rhs)
remains unchanged.
Table ? — optional::operator=(optional<U>&&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns std::move(*rhs)
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
withstd::move(*rhs)
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. The function shall not participate in overload resolution unlessis_constructible_v<T, U>
istrue
andis_assignable_v<T&, U>
istrue
andis_same_v<U, T>
isfalse
.
[2016-08-08 Ville reopens and provides improved wording]
This alternative proposed wording also resolves 2753.
The constructors that take a constT&
or T&&
are replaced by
a constructor template that takes a U&&
and defaults U = T
. This
allows copy-list-initialization with empty braces to still work:
optional<whatever> o = {}; // equivalent to initializingoptional
withnullopt
This resolution makes converting constructors and assignments have the same capabilities, including
using arguments that can't be deduced. That is achieved by using a perfect-forwarding constructor
and an assignment operator that default their argument to T
. We don't need separate
overloads for T
, the overload for U
does the job:
optional<vector<int>> ovi{{1, 2, 3}}; // still works ovi = {4, 5, 6, 7}; // now works, didn't work before
Furthermore, this proposed wording makes optional
"always unwrap". That is, the result
of the following initializations is the same:
optional<optional<int>> oi = optional<int>(); optional<optional<int>> oi = optional<short>();
Both of those initializations initialize the optional
wrapping
another optional
as if initializing with nullopt
. Assignments
do the same. These changes solve the issues pointed out by Tomasz Kamiński.
optional
.
[2016-08-08 Ville and Tomasz collaborate and improve wording]
The suggested wording retains optional
's converting constructors and assignment
operators, but provides sane results for the types Tomasz Kaminski depicts
in previous discussions.
[2016-09-08 Casey Carter finetunes existing resolution for move members]
[2016-09-09 Issues Resolution Telecon]
Move to Tentatively Ready
[2016-10-06 Ville Voutilainen finetunes the resolution for assignment from scalars]
Previous resolution [SUPERSEDED]:
This wording is relative to N4606.
Modify 22.5.3 [optional.optional] as indicated:
template <class T> class optional { public: using value_type = T; // 20.6.3.1, Constructors constexpr optional() noexcept; constexpr optional(nullopt_t) noexcept; optional(const optional &); optional(optional &&) noexcept(see below);constexpr optional(const T &);constexpr optional(T &&);template <class... Args> constexpr explicit optional(in_place_t, Args &&...); template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args &&...); template <class U = T> EXPLICIT constexpr optional(U &&); template <class U> EXPLICIT optional(const optional<U> &); template <class U> EXPLICIT optional(optional<U> &&); […] // 20.6.3.3, Assignment optional &operator=(nullopt_t) noexcept; optional &operator=(const optional &); optional &operator=(optional &&) noexcept(see below); template <class U = T> optional &operator=(U &&); template <class U> optional& operator=(const optional<U> &); template <class U> optional& operator=(optional<U> &&); template <class... Args> void emplace(Args &&...); template <class U, class... Args> void emplace(initializer_list<U>, Args &&...); […] };Change [optional.object.ctor] as indicated:
optional(const optional<T>& rhs);[…] -?- Remarks: This constructor shall be defined as deleted unless
-3- Requires:is_copy_constructible_v<T>
istrue
.is_copy_constructible_v<T>
istrue
.optional(optional<T>&& rhs) noexcept(see below);[…] -11- Remarks: The expression inside
-7- Requires:is_move_constructible_v<T>
istrue
.noexcept
is equivalent tois_nothrow_move_constructible_v<T>
. This constructor shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
.constexpr optional(const T& v);
-12- Requires:is_copy_constructible_v<T>
istrue
.-13- Effects: Initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionv
.-14- Postcondition:*this
contains a value.-15- Throws: Any exception thrown by the selected constructor ofT
.-16- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.constexpr optional(T&& v);
-17- Requires:is_move_constructible_v<T>
istrue
.-18- Effects: Initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(v)
.-19- Postcondition:*this
contains a value.-20- Throws: Any exception thrown by the selected constructor ofT
.-21- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);[…] -26- Remarks: If
-22- Requires:is_constructible_v<T, Args&&...>
istrue
.T
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, Args...>
istrue
.template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);[…] -31- Remarks:
-27- Requires:is_constructible_v<T, initializer_list<U>&, Args&&...>
istrue
.The functionThis constructor shall not participate in overload resolution unlessis_constructible_v<T, initializer_list<U>&, Args&&...>
istrue
. IfT
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.[Note: The following constructors are conditionally specified as explicit. This is typically implemented by declaring two such constructors, of which at most one participates in overload resolution. — end note]
template <class U = T> EXPLICIT constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
-?- Postconditions:T
with the expressionstd::forward<U>(v)
.*this
contains a value. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
,is_same_v<U, in_place_t>
isfalse
, andis_same_v<optional<T>, decay_t<U>>
isfalse
. The constructor is explicit if and only ifis_convertible_v<U&&, T>
isfalse
.template <class U> EXPLICIT optional(const optional<U>& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expression*rhs
.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, const U&>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&&, T>
isfalse
, andis_convertible_v<optional<U>&&, T>
isfalse
. The constructor is explicit if and only ifis_convertible_v<const U&, T>
isfalse
.template <class U> EXPLICIT optional(optional<U>&& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(*rhs)
.bool(rhs)
is unchanged.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&&, T>
isfalse
, andis_convertible_v<optional<U>&&, T>
isfalse
. The constructor is explicit if and only ifis_convertible_v<U&&, T>
isfalse
.Change [optional.object.assign] as indicated:
optional<T>& operator=(const optional<T>& rhs);[…] -8- Remarks: If any exception is thrown, the result of the expression
-4- Requires:is_copy_constructible_v<T>
istrue
andis_copy_assignable_v<T>
istrue
.bool(*this)
remains unchanged. If an exception is thrown during the call toT
's copy constructor, no effect. If an exception is thrown during the call toT
's copy assignment, the state of its contained value is as defined by the exception safety guarantee ofT
's copy assignment. This operator shall be defined as deleted unlessis_copy_constructible_v<T>
istrue
andis_copy_assignable_v<T>
istrue
.optional<T>& operator=(optional<T>&& rhs) noexcept(see below);[…] -13- Remarks: The expression inside
-9- Requires:is_move_constructible_v<T>
istrue
andis_move_assignable_v<T>
istrue
.noexcept
is equivalent to:is_nothrow_move_assignable_v<T> && is_nothrow_move_constructible_v<T>-14- If any exception is thrown, the result of the expression
bool(*this)
remains unchanged. If an exception is thrown during the call toT
's move constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's move assignment. This operator shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
andis_move_assignable_v<T>
istrue
.template <class U = T> optional<T>& operator=(U&& v);[…] -19- Remarks: If any exception is thrown, the result of the expression
-15- Requires:is_constructible_v<T, U>
istrue
andis_assignable_v<T&, U>
istrue
.bool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state ofv
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
andv
is determined by the exception safety guarantee ofT
's assignment.TheThis function shall not participate in overload resolution unlessis_same_v<decay_t<U>, T>
is_same_v<optional<T>, decay_t<U>>
isfalse
,is_constructible_v<T, U>
istrue
, andis_assignable_v<T&, U>
istrue
.-20- Notes: The reason for providing such generic assignment and then constraining it so that effectivelyT == U
is to guarantee that assignment of the formo = {}
is unambiguous.template <class U> optional<T>& operator=(const optional<U>& rhs);-?- Effects: See Table ?.
Table ? — optional::operator=(const optional<U>&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns *rhs
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
with*rhs
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. This function shall not participate in overload resolution unlessis_constructible_v<T, const U&>
istrue
,is_assignable_v<T&, const U&>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&&, T>
isfalse
,is_convertible_v<optional<U>&&, T>
isfalse
,is_assignable_v<T&, optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&&>
isfalse
, andis_assignable_v<T&, optional<U>&&>
isfalse
.template <class U> optional<T>& operator=(optional<U>&& rhs);-?- Effects: See Table ?. The result of the expression
bool(rhs)
remains unchanged.
Table ? — optional::operator=(optional<U>&&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns std::move(*rhs)
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
withstd::move(*rhs)
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. This function shall not participate in overload resolution unlessis_constructible_v<T, U>
istrue
,is_assignable_v<T&, U>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible<const optional<U>&&, T>
isfalse
,is_convertible<optional<U>&&, T>
isfalse
,is_assignable_v<T&, optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&&>
isfalse
, andis_assignable_v<T&, optional<U>&&>
isfalse
.
Proposed resolution:
This wording is relative to N4606.
Modify 22.5.3 [optional.optional] as indicated:
template <class T> class optional { public: using value_type = T; // 20.6.3.1, Constructors constexpr optional() noexcept; constexpr optional(nullopt_t) noexcept; optional(const optional &); optional(optional &&) noexcept(see below);constexpr optional(const T &);constexpr optional(T &&);template <class... Args> constexpr explicit optional(in_place_t, Args &&...); template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args &&...); template <class U = T> EXPLICIT constexpr optional(U &&); template <class U> EXPLICIT optional(const optional<U> &); template <class U> EXPLICIT optional(optional<U> &&); […] // 20.6.3.3, Assignment optional &operator=(nullopt_t) noexcept; optional &operator=(const optional &); optional &operator=(optional &&) noexcept(see below); template <class U = T> optional &operator=(U &&); template <class U> optional& operator=(const optional<U> &); template <class U> optional& operator=(optional<U> &&); template <class... Args> void emplace(Args &&...); template <class U, class... Args> void emplace(initializer_list<U>, Args &&...); […] };
Change [optional.object.ctor] as indicated:
optional(const optional<T>& rhs);[…] -?- Remarks: This constructor shall be defined as deleted unless
-3- Requires:is_copy_constructible_v<T>
istrue
.is_copy_constructible_v<T>
istrue
.optional(optional<T>&& rhs) noexcept(see below);[…] -11- Remarks: The expression inside
-7- Requires:is_move_constructible_v<T>
istrue
.noexcept
is equivalent tois_nothrow_move_constructible_v<T>
. This constructor shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
.constexpr optional(const T& v);
-12- Requires:is_copy_constructible_v<T>
istrue
.-13- Effects: Initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionv
.-14- Postcondition:*this
contains a value.-15- Throws: Any exception thrown by the selected constructor ofT
.-16- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.constexpr optional(T&& v);
-17- Requires:is_move_constructible_v<T>
istrue
.-18- Effects: Initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(v)
.-19- Postcondition:*this
contains a value.-20- Throws: Any exception thrown by the selected constructor ofT
.-21- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);[…] -26- Remarks: If
-22- Requires:is_constructible_v<T, Args&&...>
istrue
.T
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, Args...>
istrue
.template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);[…] -31- Remarks:
-27- Requires:is_constructible_v<T, initializer_list<U>&, Args&&...>
istrue
.The functionThis constructor shall not participate in overload resolution unlessis_constructible_v<T, initializer_list<U>&, Args&&...>
istrue
. IfT
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.[Note: The following constructors are conditionally specified as explicit. This is typically implemented by declaring two such constructors, of which at most one participates in overload resolution. — end note]
template <class U = T> EXPLICIT constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
-?- Postconditions:T
with the expressionstd::forward<U>(v)
.*this
contains a value. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
,is_same_v<U, in_place_t>
isfalse
, andis_same_v<optional<T>, decay_t<U>>
isfalse
. The constructor is explicit if and only ifis_convertible_v<U&&, T>
isfalse
.template <class U> EXPLICIT optional(const optional<U>& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expression*rhs
.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, const U&>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&&, T>
isfalse
, andis_convertible_v<optional<U>&&, T>
isfalse
. The constructor is explicit if and only ifis_convertible_v<const U&, T>
isfalse
.template <class U> EXPLICIT optional(optional<U>&& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(*rhs)
.bool(rhs)
is unchanged.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&&, T>
isfalse
, andis_convertible_v<optional<U>&&, T>
isfalse
. The constructor is explicit if and only ifis_convertible_v<U&&, T>
isfalse
.
Change [optional.object.assign] as indicated:
optional<T>& operator=(const optional<T>& rhs);[…] -8- Remarks: If any exception is thrown, the result of the expression
-4- Requires:is_copy_constructible_v<T>
istrue
andis_copy_assignable_v<T>
istrue
.bool(*this)
remains unchanged. If an exception is thrown during the call toT
's copy constructor, no effect. If an exception is thrown during the call toT
's copy assignment, the state of its contained value is as defined by the exception safety guarantee ofT
's copy assignment. This operator shall be defined as deleted unlessis_copy_constructible_v<T>
istrue
andis_copy_assignable_v<T>
istrue
.optional<T>& operator=(optional<T>&& rhs) noexcept(see below);[…] -13- Remarks: The expression inside
-9- Requires:is_move_constructible_v<T>
istrue
andis_move_assignable_v<T>
istrue
.noexcept
is equivalent to:is_nothrow_move_assignable_v<T> && is_nothrow_move_constructible_v<T>-14- If any exception is thrown, the result of the expression
bool(*this)
remains unchanged. If an exception is thrown during the call toT
's move constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's move assignment. This operator shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
andis_move_assignable_v<T>
istrue
.template <class U = T> optional<T>& operator=(U&& v);[…] -19- Remarks: If any exception is thrown, the result of the expression
-15- Requires:is_constructible_v<T, U>
istrue
andis_assignable_v<T&, U>
istrue
.bool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state ofv
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
andv
is determined by the exception safety guarantee ofT
's assignment.TheThis function shall not participate in overload resolution unlessis_same_v<decay_t<U>, T>
is_same_v<optional<T>, decay_t<U>>
isfalse
, conjunction_v<is_scalar<T>, is_same<T, decay_t<U>>> isfalse
,is_constructible_v<T, U>
istrue
, andis_assignable_v<T&, U>
istrue
.-20- Notes: The reason for providing such generic assignment and then constraining it so that effectivelyT == U
is to guarantee that assignment of the formo = {}
is unambiguous.template <class U> optional<T>& operator=(const optional<U>& rhs);-?- Effects: See Table ?.
Table ? — optional::operator=(const optional<U>&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns *rhs
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
with*rhs
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. This function shall not participate in overload resolution unlessis_constructible_v<T, const U&>
istrue
,is_assignable_v<T&, const U&>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&&, T>
isfalse
,is_convertible_v<optional<U>&&, T>
isfalse
,is_assignable_v<T&, optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&&>
isfalse
, andis_assignable_v<T&, optional<U>&&>
isfalse
.template <class U> optional<T>& operator=(optional<U>&& rhs);-?- Effects: See Table ?. The result of the expression
bool(rhs)
remains unchanged.
Table ? — optional::operator=(optional<U>&&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns std::move(*rhs)
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
withstd::move(*rhs)
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.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
and*rhs.val
is determined by the exception safety guarantee ofT
's assignment. This function shall not participate in overload resolution unlessis_constructible_v<T, U>
istrue
,is_assignable_v<T&, U>
istrue
,is_constructible_v<T, optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&>
isfalse
,is_constructible_v<T, const optional<U>&&>
isfalse
,is_constructible_v<T, optional<U>&&>
isfalse
,is_convertible_v<optional<U>&, T>
isfalse
,is_convertible_v<const optional<U>&, T>
isfalse
,is_convertible<const optional<U>&&, T>
isfalse
,is_convertible<optional<U>&&, T>
isfalse
,is_assignable_v<T&, optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&>
isfalse
,is_assignable_v<T&, const optional<U>&&>
isfalse
, andis_assignable_v<T&, optional<U>&&>
isfalse
.