This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of TS status.

### 2451. [fund.ts.v2] `optional<T>` should 'forward' `T`'s implicit conversions

Section: 5.3 [fund.ts.v2::optional.object] Status: TS Submitter: Geoffrey Romer Opened: 2014-10-31 Last modified: 2018-07-08

Priority: Not Prioritized

View all other issues in [fund.ts.v2::optional.object].

View all issues with TS status.

Discussion:

Addresses: fund.ts.v2

Code such as the following is currently ill-formed (thanks to STL for the compelling example):

>     optional<string> opt_str = "meow";

This is because it would require two user-defined conversions (from `const char*` to `string`, and from `string` to `optional<string>`) where the language permits only one. This is likely to be a surprise and an inconvenience for users.

`optional<T>` should be implicitly convertible from any `U` that is implicitly convertible to `T`. This can be implemented as a non-explicit constructor template `optional(U&&)`, which is enabled via SFINAE only if `is_convertible_v<U, T>` and `is_constructible_v<T, U>`, plus any additional conditions needed to avoid ambiguity with other constructors (see N4064, particularly the "Odd" example, for why `is_convertible` and `is_constructible` are both needed; thanks to Howard Hinnant for spotting this).

In addition, we may want to support explicit construction from `U`, which would mean providing a corresponding explicit constructor with a complementary SFINAE condition (this is the single-argument case of the "perfect initialization" pattern described in N4064).

[2015-10, Kona Saturday afternoon]

STL: This has status LEWG, but it should be priority 1, since we cannot ship an IS without this.

TK: We assigned our own priorities to LWG-LEWG issues, but haven't actually processed any issues yet.

MC: This is important.

[2016-02-17, Ville comments and provides concrete wording]

I have prototype-implemented this wording in libstdc++. I didn't edit the copy/move-assignment operator tables into the new `operator=` templates that take `optional`s of a different type; there's a drafting note that suggests copying them from the existing tables.

[LEWG: 2016-03, Jacksonville]

Discussion of whether `variant` supports this. We think it does.

Take it for C++17.

Unanimous yes.

Proposed resolution:

This wording is relative to N4562.

1. Edit 22.5.3 [optional.optional] as indicated:

   >     template <class T>
   >     class optional
   >     {
   >     public:
   >       typedef T value_type;

   >       // 5.3.1, Constructors
   >       constexpr optional() noexcept;
   >       constexpr optional(nullopt_t) noexcept;
   >       optional(const optional&);
   >       optional(optional&&) noexcept(see below);
   >       constexpr optional(const T&);
   >       constexpr optional(T&&);
   >       template <class... Args> constexpr explicit optional(in_place_t, Args&&...);
   >       template <class U, class... Args>
   >         constexpr explicit optional(in_place_t, initializer_list<U>, Args&&...);
   >       template <class U> constexpr optional(U&&);
   >       template <class U> constexpr optional(const optional<U>&);
   >       template <class U> constexpr optional(optional<U>&&);

   >       […]

   >       // 5.3.3, Assignment
   >       optional& operator=(nullopt_t) noexcept;
   >       optional& operator=(const optional&);
   >       optional& operator=(optional&&) noexcept(see below);
   >       template <class U> optional& operator=(U&&);
   >       template <class U> optional& operator=(const optional<U>&);
   >       template <class U> optional& operator=(optional<U>&&);
   >       template <class... Args> void emplace(Args&&...);
   >       template <class U, class... Args>
   >         void emplace(initializer_list<U>, Args&&...);

   >       […]

   >     };

2. In 5.3.1 [fund.ts.v2::optional.object.ctor], insert new signature specifications after p33:

   > [Note: The following constructors are conditionally specified as `explicit`. This is typically implemented by declaring two such constructors, of which at most one participates in overload resolution. — end note]

   >     template <class U>
   >     constexpr optional(U&& v);

   > > -?- Effects: Initializes the contained value as if direct-non-list-initializing an object of type `T` with 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 <class U>
   >     constexpr optional(const optional<U>& 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: 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, 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 `explicit` if and only if `is_convertible_v<const U&, T>` is `false`.

   >     template <class U>
   >     constexpr optional(optional<U>&& 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: 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`, `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 5.3.3 [fund.ts.v2::optional.object.assign], change as indicated:

   >     template <class U> optional<T>& operator=(U&& v);

   > > -22- Remarks: If any exception is thrown, the result of the expression `bool(*this)` remains unchanged. If an exception is thrown during the call 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 <class U> optional<T>& operator=(const optional<U>& 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<U>&)` 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 <class U> optional<T>& operator=(optional<U>&& 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<U>&&)` 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_same_v<decay_t<U>, T>` is `false`.