Issue 2451: [fund.ts.v2] 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 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 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 newoperator= 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 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, Args&&...);
template constexpr optional(U&&);
template constexpr optional(const optional&);
template constexpr optional(optional&&);[…]
// 5.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 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
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
constexpr 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: If
T's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, const U&>istrue,is_same<decay_t<U>, T>isfalse,is_constructible_v<T, const optional<U>&>isfalseandis_convertible_v<const optional<U>&, T>isfalse. The constructor isexplicitif and only ifis_convertible_v<const U&, T>isfalse.template
constexpr 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: 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<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 5.3.3 [fund.ts.v2::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.