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]

  1. 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 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>.

  2. 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 otfrom *ou, otherwise make ot uninitialized (!bool(ot))
    • value forwarding: initialize contained value of ot from ou, ot is 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 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>.
      For the following declarations:
      optional w1(make_optional(10));
      optional 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.
      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"));
      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 = optionalstd::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.

  3. 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 const&);
    While there is no corresponding non-template assignment from T const&, to make sure that o = {}; always clear an optional o.
    So for the following declarations:
    optional oi;
    optional os;

    optional<optional> ooi1(oi);
    optional<optional> 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 optionals. However, if we will attempt to make construction consistent with assignment, by preferring unwrapping (optional<U> const&), then ooi2will end up being uninitialized.
    In summary, I believe that relation between unwrapping, value forwarding and from-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(i), 2754(i) and 2756(i) all go together.

Previous resolution [SUPERSEDED]:

This wording is relative to N4606.

  1. 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 &&...);

    […]

    };

  2. 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 expression std::forward<U>(v).

    -?- Postconditions: *this contains a value.

    -?- Throws: Any exception thrown by the selected constructor of T.

    -?- Remarks: If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible_v<T, U&&> is true and U is not the same type as T. The constructor is explicit if and only if is_convertible_v<U&&, T> is false.

    template
    optional(const optional& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type T with 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&> is true, is_same<decay_t<U>, T> is false, is_constructible_v<T, const optional<U>&> is false and is_convertible_v<const optional<U>&, T> is false. The constructor is explicitif and only if is_convertible_v<const U&, T> is false.

    template
    optional(optional&& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type T with the expression std::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&&> is true, is_same<decay_t<U>, T> is false, is_constructible_v<T, optional<U>&&> is false and is_convertible_v<optional<U>&&, T> is false and U is not the same type as T. The constructor is explicit if and only if is_convertible_v<U&&, T>is false.

  3. 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 to T's constructor, the state of v is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and v is determined by the exception safety guarantee of T's assignment. The function shall not participate in overload resolution unless decay_t<U> is not nullopt_t and decay_t<U> is not a specialization of optional is_same_v<decay_t<U>, T> is true.

    -23- Notes: The reason for providing such generic assignment and then constraining it so that effectively T == U is to guarantee that assignment of the form o = {} is unambiguous.

    template optional& operator=(const optional& rhs);

    -?- Requires: is_constructible_v<T, const U&> is true and is_assignable_v<T&, const U&> is true.

    -?- 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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. The function shall not participate in overload resolution unless is_same_v<decay_t<U>, T> is false.

    template optional& operator=(optional&& rhs);

    -?- Requires: is_constructible_v<T, U> is true and is_assignable_v<T&, U> is true.

    -?- 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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. The function shall not participate in overload resolution unlessis_same_v<decay_t<U>, T> is false.

[2016-08-05 Chicago LWG]

Ville provides revised wording, that also fixes LWG 2753(i).

Rationale:

  1. The resolution of LWG 2753(i) makes special member functions defined as deleted in case the desired constraints aren't met.
  2. 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.
  3. There is no decay or remove_reference for converting constructors likeoptional(optional<U>), because none is needed.
  4. 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.

  1. 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 &&...);

    […]

    };

  2. Change [optional.object.ctor] as indicated:

    optional(const optional& rhs);

    -3- Requires: is_copy_constructible_v<T> is true.

    […]

    -?- Remarks: This constructor shall be defined as deleted unless is_copy_constructible_v<T> is true.

    optional(optional&& rhs) noexcept(see below);

    -7- Requires: is_move_constructible_v<T> is true.

    […]

    -11- Remarks: The expression inside noexcept is equivalent to is_nothrow_move_constructible_v<T>.This constructor shall be defined as deleted unless is_move_constructible_v<T> is true.

    constexpr optional(const T& v);

    -12- Requires: is_copy_constructible_v<T> is true.

    […]

    -16- Remarks: If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_copy_constructible_v<T> is true.

    constexpr optional(T&& v);

    -17- Requires: is_move_constructible_v<T> is true.

    […]

    -21- Remarks: If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_move_constructible_v<T> is true.

    template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);

    -22- Requires: is_constructible_v<T, Args&&...> is true.

    […]

    -26- Remarks: If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible_v<T, Args...> is true.

    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&&...> is true.

    […]

    -31- Remarks: The functionconstructor shall not participate in overload resolution unless is_constructible_v<T, initializer_list<U>&, Args&&...> is true. If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr 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
    EXPLICIT constexpr optional(U&& v);

    -?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the expressionstd::forward<U>(v).

    -?- Postconditions: *this contains a value.

    -?- Throws: Any exception thrown by the selected constructor of T.

    -?- Remarks: If T's selected constructor is a constexprconstructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&> is true, remove_reference_t<U>is not the same type as T, and U is not a specialization of optional. The constructor is explicit if and only if is_convertible_v<U&&, T> isfalse.

    template
    EXPLICIT optional(const optional& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type Twith 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&> is true and is_same_v<U, T> is false. The constructor is explicit if and only if is_convertible_v<const U&, T> is false.

    template
    EXPLICIT optional(optional&& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type Twith the expression std::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&&> is true and is_same_v<U, T> is false. The constructor is explicit if and only if is_convertible_v<U&&, T> is false.

  3. Change [optional.object.assign] as indicated:

    optional& operator=(const optional& rhs);

    -4- Requires: is_copy_constructible_v<T> is true and is_copy_assignable_v<T> is true.

    […]

    -8- Remarks: If any exception is thrown, the result of the expression bool(*this) remains unchanged. If an exception is thrown during the call to T's copy constructor, no effect. If an exception is thrown during the call to T's copy assignment, the state of its contained value is as defined by the exception safety guarantee of T's copy assignment. This operator shall be defined as deleted unlessis_copy_constructible_v<T> is true and is_copy_assignable_v<T> istrue.

    optional& operator=(optional&& rhs) noexcept(see below);

    -9- Requires: is_move_constructible_v<T> is true and is_move_assignable_v<T> is true.

    […]

    -13- Remarks: The expression inside noexcept is 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 to T's move constructor, the state of *rhs.valis determined by the exception safety guarantee of T's move constructor. If an exception is thrown during the call to T's move assignment, the state of *val and *rhs.valis determined by the exception safety guarantee of T's move assignment. This operator shall be defined as deleted unless is_move_constructible_v<T> is true and is_move_assignable_v<T> is true.

    template optional& operator=(U&& v);

    -15- Requires: is_constructible_v<T, U> is true and is_assignable_v<T&, U> is true.

    […]

    -19- Remarks: If any exception is thrown, the result of the expression bool(*this)remains unchanged. If an exception is thrown during the call to T's constructor, the state of v is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and vis determined by the exception safety guarantee of T's assignment. TheThis function shall not participate in overload resolution unless is_same_v<decay_t<U>, T> decay_t<U> is not nullopt_t, decay_t<U> is not a specialization of optional, is_constructible_v<T, U> is true and is_assignable_v<T&, U> is true.

    -20- Notes: The reason for providing such generic assignment and then constraining it so that effectively T == U is to guarantee that assignment of the form o = {} 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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. The function shall not participate in overload resolution unless is_constructible_v<T, const U&> is true and is_assignable_v<T&, const U&> is true and is_same_v<U, T>is false.

    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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. The function shall not participate in overload resolution unless is_constructible_v<T, U> is true and is_assignable_v<T&, U> is true and is_same_v<U, T> is false.

[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 optional with nullopt

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.

  1. 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 &&...);

    […]

    };

  2. Change [optional.object.ctor] as indicated:

    optional(const optional& rhs);

    -3- Requires: is_copy_constructible_v<T> is true.

    […]

    -?- Remarks: This constructor shall be defined as deleted unless is_copy_constructible_v<T> is true.

    optional(optional&& rhs) noexcept(see below);

    -7- Requires: is_move_constructible_v<T> is true.

    […]

    -11- Remarks: The expression inside noexcept is equivalent to is_nothrow_move_constructible_v<T>.This constructor shall not participate in overload resolution unless is_move_constructible_v<T> is true.

    constexpr optional(const T& v);

    -12- Requires: is_copy_constructible_v<T> is true.

    -13- Effects: Initializes the contained value as if direct-non-list-initializing an object of type Twith the expression v.

    -14- Postcondition: *this contains a value.

    -15- Throws: Any exception thrown by the selected constructor of T.

    -16- Remarks: If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.

    constexpr optional(T&& v);

    -17- Requires: is_move_constructible_v<T> is true.

    -18- Effects: Initializes the contained value as if direct-non-list-initializing an object of type Twith the expression std::move(v).

    -19- Postcondition: *this contains a value.

    -20- Throws: Any exception thrown by the selected constructor of T.

    -21- Remarks: If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.

    template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);

    -22- Requires: is_constructible_v<T, Args&&...> is true.

    […]

    -26- Remarks: If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible_v<T, Args...> is true.

    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&&...> is true.

    […]

    -31- Remarks: The functionThis constructor shall not participate in overload resolution unless is_constructible_v<T, initializer_list<U>&, Args&&...> is true. If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr 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
    EXPLICIT constexpr optional(U&& v);

    -?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the expressionstd::forward<U>(v).

    -?- Postconditions: *this contains a value.

    -?- Throws: Any exception thrown by the selected constructor of T.

    -?- Remarks: If T's selected constructor is a constexprconstructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&> is true, is_same_v<U, in_place_t> is false, and is_same_v<optional<T>, decay_t<U>>is false. The constructor is explicit if and only if is_convertible_v<U&&, T> is false.

    template
    EXPLICIT optional(const optional& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type Twith 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&> is true, is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible_v<const optional<U>&&, T> is false, and is_convertible_v<optional<U>&&, T> is false. The constructor is explicit if and only if is_convertible_v<const U&, T> is false.

    template
    EXPLICIT optional(optional&& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type Twith the expression std::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&&> is true,is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible_v<const optional<U>&&, T> is false, and is_convertible_v<optional<U>&&, T> is false. The constructor is explicit if and only if is_convertible_v<U&&, T> is false.

  3. Change [optional.object.assign] as indicated:

    optional& operator=(const optional& rhs);

    -4- Requires: is_copy_constructible_v<T> is true and is_copy_assignable_v<T> is true.

    […]

    -8- Remarks: If any exception is thrown, the result of the expression bool(*this) remains unchanged. If an exception is thrown during the call to T's copy constructor, no effect. If an exception is thrown during the call to T's copy assignment, the state of its contained value is as defined by the exception safety guarantee of T's copy assignment. This operator shall be defined as deleted unlessis_copy_constructible_v<T> is true and is_copy_assignable_v<T> istrue.

    optional& operator=(optional&& rhs) noexcept(see below);

    -9- Requires: is_move_constructible_v<T> is true and is_move_assignable_v<T> is true.

    […]

    -13- Remarks: The expression inside noexcept is 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 to T's move constructor, the state of *rhs.valis determined by the exception safety guarantee of T's move constructor. If an exception is thrown during the call to T's move assignment, the state of *val and *rhs.valis determined by the exception safety guarantee of T's move assignment. This operator shall not participate in overload resolution unless is_move_constructible_v<T> is true and is_move_assignable_v<T> is true.

    template optional& operator=(U&& v);

    -15- Requires: is_constructible_v<T, U> is true and is_assignable_v<T&, U> is true.

    […]

    -19- Remarks: If any exception is thrown, the result of the expression bool(*this)remains unchanged. If an exception is thrown during the call to T's constructor, the state of v is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and vis determined by the exception safety guarantee of T's assignment. TheThis function shall not participate in overload resolution unless is_same_v<decay_t<U>, T> is_same_v<optional<T>, decay_t<U>> is false, is_constructible_v<T, U> is true, and is_assignable_v<T&, U> is true.

    -20- Notes: The reason for providing such generic assignment and then constraining it so that effectively T == U is to guarantee that assignment of the form o = {} 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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. This function shall not participate in overload resolution unless is_constructible_v<T, const U&> is true, is_assignable_v<T&, const U&> is true,is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible_v<const optional<U>&&, T> is false,is_convertible_v<optional<U>&&, T> is false,is_assignable_v<T&, optional<U>&> is false,is_assignable_v<T&, const optional<U>&> is false,is_assignable_v<T&, const optional<U>&&> is false, and is_assignable_v<T&, optional<U>&&> is false.

    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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. This function shall not participate in overload resolution unless is_constructible_v<T, U> is true, is_assignable_v<T&, U> is true,is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible<const optional<U>&&, T> is false,is_convertible<optional<U>&&, T> is false,is_assignable_v<T&, optional<U>&> is false,is_assignable_v<T&, const optional<U>&> is false,is_assignable_v<T&, const optional<U>&&> is false, and is_assignable_v<T&, optional<U>&&> is false.

Proposed resolution:

This wording is relative to N4606.

  1. 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 &&...);

    […]

    };

  2. Change [optional.object.ctor] as indicated:

    optional(const optional& rhs);

    -3- Requires: is_copy_constructible_v<T> is true.

    […]

    -?- Remarks: This constructor shall be defined as deleted unless is_copy_constructible_v<T> is true.

    optional(optional&& rhs) noexcept(see below);

    -7- Requires: is_move_constructible_v<T> is true.

    […]

    -11- Remarks: The expression inside noexcept is equivalent to is_nothrow_move_constructible_v<T>.This constructor shall not participate in overload resolution unless is_move_constructible_v<T> is true.

    constexpr optional(const T& v);

    -12- Requires: is_copy_constructible_v<T> is true.

    -13- Effects: Initializes the contained value as if direct-non-list-initializing an object of type Twith the expression v.

    -14- Postcondition: *this contains a value.

    -15- Throws: Any exception thrown by the selected constructor of T.

    -16- Remarks: If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.

    constexpr optional(T&& v);

    -17- Requires: is_move_constructible_v<T> is true.

    -18- Effects: Initializes the contained value as if direct-non-list-initializing an object of type Twith the expression std::move(v).

    -19- Postcondition: *this contains a value.

    -20- Throws: Any exception thrown by the selected constructor of T.

    -21- Remarks: If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.

    template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);

    -22- Requires: is_constructible_v<T, Args&&...> is true.

    […]

    -26- Remarks: If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible_v<T, Args...> is true.

    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&&...> is true.

    […]

    -31- Remarks: The functionThis constructor shall not participate in overload resolution unless is_constructible_v<T, initializer_list<U>&, Args&&...> is true. If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr 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
    EXPLICIT constexpr optional(U&& v);

    -?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the expressionstd::forward<U>(v).

    -?- Postconditions: *this contains a value.

    -?- Throws: Any exception thrown by the selected constructor of T.

    -?- Remarks: If T's selected constructor is a constexprconstructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&> is true, is_same_v<U, in_place_t> is false, and is_same_v<optional<T>, decay_t<U>>is false. The constructor is explicit if and only if is_convertible_v<U&&, T> is false.

    template
    EXPLICIT optional(const optional& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type Twith 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&> is true, is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible_v<const optional<U>&&, T> is false, and is_convertible_v<optional<U>&&, T> is false. The constructor is explicit if and only if is_convertible_v<const U&, T> is false.

    template
    EXPLICIT optional(optional&& rhs);

    -?- Effects: If rhs contains a value, initializes the contained value as if direct-non-list-initializing an object of type Twith the expression std::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&&> is true,is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible_v<const optional<U>&&, T> is false, and is_convertible_v<optional<U>&&, T> is false. The constructor is explicit if and only if is_convertible_v<U&&, T> is false.

  3. Change [optional.object.assign] as indicated:

    optional& operator=(const optional& rhs);

    -4- Requires: is_copy_constructible_v<T> is true and is_copy_assignable_v<T> is true.

    […]

    -8- Remarks: If any exception is thrown, the result of the expression bool(*this) remains unchanged. If an exception is thrown during the call to T's copy constructor, no effect. If an exception is thrown during the call to T's copy assignment, the state of its contained value is as defined by the exception safety guarantee of T's copy assignment. This operator shall be defined as deleted unlessis_copy_constructible_v<T> is true and is_copy_assignable_v<T> istrue.

    optional& operator=(optional&& rhs) noexcept(see below);

    -9- Requires: is_move_constructible_v<T> is true and is_move_assignable_v<T> is true.

    […]

    -13- Remarks: The expression inside noexcept is 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 to T's move constructor, the state of *rhs.valis determined by the exception safety guarantee of T's move constructor. If an exception is thrown during the call to T's move assignment, the state of *val and *rhs.valis determined by the exception safety guarantee of T's move assignment. This operator shall not participate in overload resolution unless is_move_constructible_v<T> is true and is_move_assignable_v<T> is true.

    template optional& operator=(U&& v);

    -15- Requires: is_constructible_v<T, U> is true and is_assignable_v<T&, U> is true.

    […]

    -19- Remarks: If any exception is thrown, the result of the expression bool(*this)remains unchanged. If an exception is thrown during the call to T's constructor, the state of v is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and vis determined by the exception safety guarantee of T's assignment. TheThis function shall not participate in overload resolution unless is_same_v<decay_t<U>, T> is_same_v<optional<T>, decay_t<U>> is false, conjunction_v<is_scalar, is_same<T, decay_t>> is false,is_constructible_v<T, U> is true, and is_assignable_v<T&, U> is true.

    -20- Notes: The reason for providing such generic assignment and then constraining it so that effectively T == U is to guarantee that assignment of the form o = {} 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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. This function shall not participate in overload resolution unless is_constructible_v<T, const U&> is true, is_assignable_v<T&, const U&> is true,is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible_v<const optional<U>&&, T> is false,is_convertible_v<optional<U>&&, T> is false,is_assignable_v<T&, optional<U>&> is false,is_assignable_v<T&, const optional<U>&> is false,is_assignable_v<T&, const optional<U>&&> is false, and is_assignable_v<T&, optional<U>&&> is false.

    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 to T's constructor, the state of *rhs.val is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's assignment. This function shall not participate in overload resolution unless is_constructible_v<T, U> is true, is_assignable_v<T&, U> is true,is_constructible_v<T, optional<U>&> is false,is_constructible_v<T, const optional<U>&> is false,is_constructible_v<T, const optional<U>&&> is false,is_constructible_v<T, optional<U>&&> is false,is_convertible_v<optional<U>&, T> is false,is_convertible_v<const optional<U>&, T> is false,is_convertible<const optional<U>&&, T> is false,is_convertible<optional<U>&&, T> is false,is_assignable_v<T&, optional<U>&> is false,is_assignable_v<T&, const optional<U>&> is false,is_assignable_v<T&, const optional<U>&&> is false, and is_assignable_v<T&, optional<U>&&> is false.