Issue 2756: C++ WP optional should 'forward' T's implicit conversions (original) (raw)
This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++17 status.
2756. C++ WP optional<T> should 'forward' T's implicit conversions
Section: 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(i) 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 optionalbefore 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.
LWG 2745(i) proposes removal of the 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(i) as amended by LWG 2745(i)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 o1;
optional o2(o1);
The second constructor will invoke value forwarding(U = optional<T>&)constructor (better match) instead of theoptional<T>copy constructor, in case ifTcan be constructed fromoptional<T>. This happens for any typeTthat has unconstrained perfect forwarding constructor, especiallyoptional<any>. - The behavior of the construction of the
optional<T> otfromoptional<U> ouis inconsistent for classesTthan can be constructed both fromoptional<U>andU. There are two possible semantics for such operation:- unwrapping: if
ouis initialized (bool(ou)), initialize contained value ofotfrom*ou, otherwise makeotuninitialized (!bool(ot)) - value forwarding: initialize contained value of
otfromou,otis always initialized (bool(ot)).
With the proposed resolution, construction is preferring value forwarding, while assignment is always using unwrapping.
For example, if we consider following class:struct Widget
{
Widget(int);
Widget(optional);
};
Notice, that such set of constructor is pretty common in situation when the construction of theWidgetfrom known value is common and usage ofoptionalversion is rare. In such situation, presence ofWidget(int)construction is an optimization used to avoid unnecessary empty checks and constructionoptional<int>.
For the following declarations:
optional w1(make_optional(10));
optional w2;
w2 = make_optional(10);
Thew1will contain a value created usingWidget(optional<int>)constructor, as corresponding unwrapping constructor (optional<U> const&) is eliminated byis_constructible_v<T, const optional<U>&>(is_constructible_v<Widget, const optional<int>&>) having atruevalue. In contrastw2will contain a value created usingWidget(int)constructor, as corresponding value forwarding assignment (U&&) is eliminated by the fact thatstd::decay_t<U>(optional<int>) is specialization ofoptional.
In addition, the construction is having a preference for value forwarding and assignment is always using unwrapping. That means that for the following class:
struct Thingy
{
Thingy(optional);
};optional t1(optional("test"));
Thet1has a contained value constructed from usingThingy(optional<std::string>), as unwrapping constructor (optional<U> const&) are eliminated by theis_constructible<T, U const&>(is_constructible<Thingy, std::string const&>) beingfalse. However the following:
t1 = optionalstd::string("test2");
will not compile, because, the value forwarding assignment (U&&) is eliminated bystd::decay_t<U>(optional<std::string>) being optional specialization and the unwrapping assignment (optional<U> const&) is ill-formed becauseis_constructible<T, U const&>(is_constructible<Thingy, std::string const&>) isfalse.
- unwrapping: if
- The semantics of construction and assignment, of
optional<optional<V>>fromoptional<U>whereUis convertible to/ same asTis also inconsistent. Firstly, in this situation theoptional<V>is a type that can be constructed both fromoptional<U>andUso it fails into set of problem described above. However in addition we have following non-template constructor inoptional<T>:optional(T const&);
Which foroptional<optional<V>>is equivalent to:
optional(optional const&);
While there is no corresponding non-template assignment fromT const&, to make sure thato = {};always clear anoptional o.
So for the following declarations:
optional oi;
optional os;optional<optional> ooi1(oi);
optional<optional> ooi2(os);
Theooi1uses from-Tconstructor, whileooi2uses value forwarding constructor. In this case bothooi1andooi2are initialized and their contained values*ooi1,*ooi2are uninitializedoptionals. However, if we will attempt to make construction consistent with assignment, by preferring unwrapping (optional<U> const&), thenooi2will end up being uninitialized.
In summary, I believe that relation between unwrapping, value forwarding and from-Tconstruction/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(i), 2754(i) and 2756(i) all go together.
Previous resolution [SUPERSEDED]:
This wording is relative to N4606.
- Modify 22.5.3 [optional.optional] as indicated:
template 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, Args &&...);
template constexpr optional(U &&);
template optional(const optional &);
template optional(optional &&);[…]
// 20.6.3.3, Assignment
optional &operator=(nullopt_t) noexcept;
optional &operator=(const optional &);
optional &operator=(optional &&) noexcept(see below);
template optional &operator=(U &&);
template optional& operator=(const optional &);
template optional& operator=(optional &&);
template <class... Args> void emplace(Args &&...);
template <class U, class... Args>
void emplace(initializer_list, 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
constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
Twith the expressionstd::forward<U>(v).-?- Postconditions:
*thiscontains a value.-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: If
T's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>istrueandUis not the same type asT. The constructor isexplicitif and only ifis_convertible_v<U&&, T>isfalse.template
optional(const optional& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expression*rhs.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_constructible_v<T, const U&>istrue,is_same<decay_t<U>, T>isfalse,is_constructible_v<T, const optional<U>&>isfalseandis_convertible_v<const optional<U>&, T>isfalse. The constructor isexplicitif and only ifis_convertible_v<const U&, T>isfalse.template
optional(optional&& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expressionstd::move(*rhs).bool(rhs)is unchanged.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_constructible_v<T, U&&>istrue,is_same<decay_t<U>, T>isfalse,is_constructible_v<T, optional<U>&&>isfalseandis_convertible_v<optional<U>&&, T>isfalseandUis not the same type asT. The constructor isexplicitif and only ifis_convertible_v<U&&, T>isfalse.- In [optional.object.assign], change as indicated:
template optional& 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 ofvis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valandvis determined by the exception safety guarantee ofT's assignment. The function shall not participate in overload resolution unlessdecay_t<U>is notnullopt_tanddecay_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 == Uis to guarantee that assignment of the formo = {}is unambiguous.template optional& operator=(const optional& rhs);
-?- Requires:
is_constructible_v<T, const U&>istrueandis_assignable_v<T&, const U&>istrue.-?- Effects:
Table ? — optional::operator=(const optional&) effects
*this contains a value *this does not contain a value rhs contains a value assigns *rhs to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with *rhs rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis 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 optional& operator=(optional&& rhs);
-?- Requires:
is_constructible_v<T, U>istrueandis_assignable_v<T&, U>istrue.-?- Effects: The result of the expression
bool(rhs)remains unchanged.Table ? — optional::operator=(optional&&) effects
*this contains a value *this does not contain a value rhs contains a value assigns std::move(*rhs) to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with std::move(*rhs) rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis 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(i).
Rationale:
- The resolution of LWG 2753(i) 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 aremove_referenceinstead. 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_referenceis what we need,remove_cvwould be wrong, anddecaywould be wrong. - There is no
decayorremove_referencefor converting constructors likeoptional(optional<U>), because none is needed. - For
optional(U&&), an added constraint is thatUis not a specialization ofoptional
[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 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, Args &&...);
template EXPLICIT constexpr optional(U &&);
template EXPLICIT optional(const optional &);
template EXPLICIT optional(optional &&);[…]
// 20.6.3.3, Assignment
optional &operator=(nullopt_t) noexcept;
optional &operator=(const optional &);
optional &operator=(optional &&) noexcept(see below);
template optional &operator=(U &&);
template optional& operator=(const optional &);
template optional& operator=(optional &&);
template <class... Args> void emplace(Args &&...);
template <class U, class... Args>
void emplace(initializer_list, Args &&...);[…]
};
- Change [optional.object.ctor] as indicated:
optional(const optional& rhs);
-3- Requires:is_copy_constructible_v<T>istrue.[…]
-?- Remarks: This constructor shall be defined as deleted unless
is_copy_constructible_v<T>istrue.optional(optional&& rhs) noexcept(see below);
-7- Requires:is_move_constructible_v<T>istrue.[…]
-11- Remarks: The expression inside
noexceptis 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);
-12- Requires:is_copy_constructible_v<T>istrue.[…]
-16- Remarks: If
T's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor. This constructor shall not participate in overload resolution unlessis_copy_constructible_v<T>istrue.constexpr optional(T&& v);
-17- Requires:is_move_constructible_v<T>istrue.[…]
-21- Remarks: If
T's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor. 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);
-22- Requires:is_constructible_v<T, Args&&...>istrue.[…]
-26- Remarks: If
T's constructor selected for the initialization is aconstexprconstructor, this constructor shall be aconstexprconstructor. 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 il, Args&&... args);
-27- Requires:is_constructible_v<T, initializer_list<U>&, Args&&...>istrue.[…]
-31- Remarks: The
functionconstructor shall not participate in overload resolution unlessis_constructible_v<T, initializer_list<U>&, Args&&...>istrue. IfT's constructor selected for the initialization is aconstexprconstructor, this constructor shall be aconstexprconstructor.[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
EXPLICIT constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
Twith the expressionstd::forward<U>(v).-?- Postconditions:
*thiscontains a value.-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: If
T's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>istrue,remove_reference_t<U>is not the same type asT, andUis not a specialization ofoptional. The constructor is explicit if and only ifis_convertible_v<U&&, T>isfalse.template
EXPLICIT optional(const optional& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expression*rhs.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_constructible_v<T, const U&>istrueandis_same_v<U, T>isfalse. The constructor is explicit if and only ifis_convertible_v<const U&, T>isfalse.template
EXPLICIT optional(optional&& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expressionstd::move(*rhs).bool(rhs)is unchanged.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_constructible_v<T, U&&>istrueandis_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& operator=(const optional& rhs);
-4- Requires:is_copy_constructible_v<T>istrueandis_copy_assignable_v<T>istrue.[…]
-8- 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 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>istrueandis_copy_assignable_v<T>istrue.optional& operator=(optional&& rhs) noexcept(see below);
-9- Requires:is_move_constructible_v<T>istrueandis_move_assignable_v<T>istrue.[…]
-13- Remarks: The expression inside
noexceptis equivalent to:is_nothrow_move_assignable_v && is_nothrow_move_constructible_v
-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.valis 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*valand*rhs.valis determined by the exception safety guarantee ofT's move assignment. This operator shall be defined as deleted unlessis_move_constructible_v<T>istrueandis_move_assignable_v<T>istrue.template optional& operator=(U&& v);
-15- Requires:is_constructible_v<T, U>istrueandis_assignable_v<T&, U>istrue.[…]
-19- 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 ofvis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valandvis 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>istrueandis_assignable_v<T&, U>istrue.
-20- Notes: The reason for providing such generic assignment and then constraining it so that effectivelyT == Uis to guarantee that assignment of the formo = {}is unambiguous.template optional& operator=(const optional& rhs);
-?- Effects:
Table ? — optional::operator=(const optional&) effects
*this contains a value *this does not contain a value rhs contains a value assigns *rhs to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with *rhs rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis determined by the exception safety guarantee ofT's assignment. The function shall not participate in overload resolution unlessis_constructible_v<T, const U&>istrueandis_assignable_v<T&, const U&>istrueandis_same_v<U, T>isfalse.template optional& operator=(optional&& rhs);
-?- Effects: The result of the expression
bool(rhs)remains unchanged.Table ? — optional::operator=(optional&&) effects
*this contains a value *this does not contain a value rhs contains a value assigns std::move(*rhs) to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with std::move(*rhs) rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis determined by the exception safety guarantee ofT's assignment. The function shall not participate in overload resolution unlessis_constructible_v<T, U>istrueandis_assignable_v<T&, U>istrueandis_same_v<U, T>isfalse.
[2016-08-08 Ville reopens and provides improved wording]
This alternative proposed wording also resolves 2753(i).
The constructors that take a const T& 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 initializing
optionalwithnullopt
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> 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> oi = optional(); optional<optional> oi = optional();
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.
This P/R has been implemented and tested as a modification on top of libstdc++'s 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.
As opposed to the current P/R of this issue, which does "always unwrap", this P/R does "always value-forward unless the incoming type is exactly a type that a special member function takes by reference, and don't unwrap if a value-forwarder can take an optional by any kind of reference".
I and Tomasz believe this is the best compromise between the different desires, and thus the best outcome so far.
This P/R has been implemented and tested on libstdc++.
[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 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, Args &&...);
template EXPLICIT constexpr optional(U &&);
template EXPLICIT optional(const optional &);
template EXPLICIT optional(optional &&);[…]
// 20.6.3.3, Assignment
optional &operator=(nullopt_t) noexcept;
optional &operator=(const optional &);
optional &operator=(optional &&) noexcept(see below);
template optional &operator=(U &&);
template optional& operator=(const optional &);
template optional& operator=(optional &&);
template <class... Args> void emplace(Args &&...);
template <class U, class... Args>
void emplace(initializer_list, Args &&...);[…]
};
- Change [optional.object.ctor] as indicated:
optional(const optional& rhs);
-3- Requires:is_copy_constructible_v<T>istrue.[…]
-?- Remarks: This constructor shall be defined as deleted unless
is_copy_constructible_v<T>istrue.optional(optional&& rhs) noexcept(see below);
-7- Requires:is_move_constructible_v<T>istrue.[…]
-11- Remarks: The expression inside
noexceptis 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 typeTwith the expressionv.
-14- Postcondition:*thiscontains a value.
-15- Throws: Any exception thrown by the selected constructor ofT.
-16- Remarks: IfT's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.
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 typeTwith the expressionstd::move(v).
-19- Postcondition:*thiscontains a value.
-20- Throws: Any exception thrown by the selected constructor ofT.
-21- Remarks: IfT's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);
-22- Requires:is_constructible_v<T, Args&&...>istrue.[…]
-26- Remarks: If
T's constructor selected for the initialization is aconstexprconstructor, this constructor shall be aconstexprconstructor. 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 il, Args&&... args);
-27- Requires:is_constructible_v<T, initializer_list<U>&, Args&&...>istrue.[…]
-31- Remarks:
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 aconstexprconstructor, this constructor shall be aconstexprconstructor.[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
EXPLICIT constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
Twith the expressionstd::forward<U>(v).-?- Postconditions:
*thiscontains a value.-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: If
T's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor. 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
EXPLICIT optional(const optional& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expression*rhs.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_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
EXPLICIT optional(optional&& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expressionstd::move(*rhs).bool(rhs)is unchanged.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_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& operator=(const optional& rhs);
-4- Requires:is_copy_constructible_v<T>istrueandis_copy_assignable_v<T>istrue.[…]
-8- 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 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>istrueandis_copy_assignable_v<T>istrue.optional& operator=(optional&& rhs) noexcept(see below);
-9- Requires:is_move_constructible_v<T>istrueandis_move_assignable_v<T>istrue.[…]
-13- Remarks: The expression inside
noexceptis equivalent to:is_nothrow_move_assignable_v && is_nothrow_move_constructible_v
-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.valis 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*valand*rhs.valis determined by the exception safety guarantee ofT's move assignment. This operator shall not participate in overload resolution unlessis_move_constructible_v<T>istrueandis_move_assignable_v<T>istrue.template optional& operator=(U&& v);
-15- Requires:is_constructible_v<T, U>istrueandis_assignable_v<T&, U>istrue.[…]
-19- 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 ofvis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valandvis 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 == Uis to guarantee that assignment of the formo = {}is unambiguous.template optional& operator=(const optional& rhs);
-?- Effects: See Table ?.
Table ? — optional::operator=(const optional&) effects
*this contains a value *this does not contain a value rhs contains a value assigns *rhs to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with *rhs rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis 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 optional& operator=(optional&& rhs);
-?- Effects: See Table ?. The result of the expression
bool(rhs)remains unchanged.Table ? — optional::operator=(optional&&) effects
*this contains a value *this does not contain a value rhs contains a value assigns std::move(*rhs) to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with std::move(*rhs) rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis 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 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, Args &&...);
template EXPLICIT constexpr optional(U &&);
template EXPLICIT optional(const optional &);
template EXPLICIT optional(optional &&);[…]
// 20.6.3.3, Assignment
optional &operator=(nullopt_t) noexcept;
optional &operator=(const optional &);
optional &operator=(optional &&) noexcept(see below);
template optional &operator=(U &&);
template optional& operator=(const optional &);
template optional& operator=(optional &&);
template <class... Args> void emplace(Args &&...);
template <class U, class... Args>
void emplace(initializer_list, Args &&...);[…]
};
- Change [optional.object.ctor] as indicated:
optional(const optional& rhs);
-3- Requires:is_copy_constructible_v<T>istrue.[…]
-?- Remarks: This constructor shall be defined as deleted unless
is_copy_constructible_v<T>istrue.optional(optional&& rhs) noexcept(see below);
-7- Requires:is_move_constructible_v<T>istrue.[…]
-11- Remarks: The expression inside
noexceptis 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 typeTwith the expressionv.-14- Postcondition:*thiscontains a value.-15- Throws: Any exception thrown by the selected constructor ofT.-16- Remarks: IfT's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.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 typeTwith the expressionstd::move(v).-19- Postcondition:*thiscontains a value.-20- Throws: Any exception thrown by the selected constructor ofT.-21- Remarks: IfT's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);
-22- Requires:is_constructible_v<T, Args&&...>istrue.[…]
-26- Remarks: If
T's constructor selected for the initialization is aconstexprconstructor, this constructor shall be aconstexprconstructor. 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 il, Args&&... args);-27- Requires:is_constructible_v<T, initializer_list<U>&, Args&&...>istrue.[…]
-31- Remarks:
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 aconstexprconstructor, this constructor shall be aconstexprconstructor.[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
EXPLICIT constexpr optional(U&& v);-?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type
Twith the expressionstd::forward<U>(v).-?- Postconditions:
*thiscontains a value.-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: If
T's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor. 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
EXPLICIT optional(const optional& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expression*rhs.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_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
EXPLICIT optional(optional&& rhs);-?- Effects: If
rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expressionstd::move(*rhs).bool(rhs)is unchanged.-?- Postconditions:
bool(rhs) == bool(*this).-?- Throws: Any exception thrown by the selected constructor of
T.-?- Remarks: This constructor shall not participate in overload resolution unless
is_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& operator=(const optional& rhs);
-4- Requires:is_copy_constructible_v<T>istrueandis_copy_assignable_v<T>istrue.[…]
-8- 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 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>istrueandis_copy_assignable_v<T>istrue.optional& operator=(optional&& rhs) noexcept(see below);
-9- Requires:is_move_constructible_v<T>istrueandis_move_assignable_v<T>istrue.[…]
-13- Remarks: The expression inside
noexceptis equivalent to:is_nothrow_move_assignable_v && is_nothrow_move_constructible_v
-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.valis 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*valand*rhs.valis determined by the exception safety guarantee ofT's move assignment. This operator shall not participate in overload resolution unlessis_move_constructible_v<T>istrueandis_move_assignable_v<T>istrue.template optional& operator=(U&& v);
-15- Requires:is_constructible_v<T, U>istrueandis_assignable_v<T&, U>istrue.[…]
-19- 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 ofvis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valandvis 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, is_same<T, decay_t>> 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 == Uis to guarantee that assignment of the formo = {}is unambiguous.template optional& operator=(const optional& rhs);
-?- Effects: See Table ?.
Table ? — optional::operator=(const optional&) effects
*this contains a value *this does not contain a value rhs contains a value assigns *rhs to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with *rhs rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis 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 optional& operator=(optional&& rhs);
-?- Effects: See Table ?. The result of the expression
bool(rhs)remains unchanged.Table ? — optional::operator=(optional&&) effects
*this contains a value *this does not contain a value rhs contains a value assigns std::move(*rhs) to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with std::move(*rhs) rhs does not contain a value destroys the contained value by calling val->T::~T() no effect -?- Returns:
*this.-?- Postconditions:
bool(rhs) == bool(*this).-?- 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 of*rhs.valis determined by the exception safety guarantee ofT's constructor. If an exception is thrown during the call toT's assignment, the state of*valand*rhs.valis 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.