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.
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 optionals 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.
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&&...); […] };
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.
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.