optional<T>
should 'forward' T
's implicit conversionsSection: 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.
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
-?- Postconditions:T
with the expressionstd::forward<U>(v)
.*this
contains a value. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. This constructor shall not participate in overload resolution unlessis_constructible_v<T, U&&>
istrue
andU
is not the same type asT
. The constructor isexplicit
if and only ifis_convertible_v<U&&, T>
isfalse
.template <class U> constexpr optional(const optional<U>& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expression*rhs
.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. 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>&>
isfalse
andis_convertible_v<const optional<U>&, T>
isfalse
. The constructor isexplicit
if and only ifis_convertible_v<const U&, T>
isfalse
.template <class U> constexpr optional(optional<U>&& rhs);-?- Effects: If
-?- Postconditions:rhs
contains a value, initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(*rhs)
.bool(rhs)
is unchanged.bool(rhs) == bool(*this)
. -?- Throws: Any exception thrown by the selected constructor ofT
. -?- Remarks: IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor. 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>&&>
isfalse
andis_convertible_v<optional<U>&&, T>
isfalse
andU
is not the same type asT
. The constructor isexplicit
if and only ifis_convertible_v<U&&, T>
isfalse
.
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 toT
's constructor, the state ofv
is determined by the exception safety guarantee ofT
's constructor. If an exception is thrown during the call toT
's assignment, the state of*val
andv
is determined by the exception safety guarantee ofT
's assignment. The function shall not participate in overload resolution unlessdecay_t<U>
is notnullopt_t
anddecay_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 == U
is to guarantee that assignment of the formo = {}
is unambiguous.template <class U> optional<T>& operator=(const optional<U>& rhs);-?- Requires:
-?- Effects:is_constructible_v<T, const U&>
istrue
andis_assignable_v<T&, const U&>
istrue
.
Table ? — optional::operator=(const optional<U>&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns *rhs
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
with*rhs
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.val
is determined by the exception safety guarantee ofT
's constructor. If an exception is thrown during the call toT
's assignment, the state of*val
and*rhs.val
is 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 <class U> optional<T>& operator=(optional<U>&& rhs);-?- Requires:
-?- Effects: The result of the expressionis_constructible_v<T, U>
istrue
andis_assignable_v<T&, U>
istrue
.bool(rhs)
remains unchanged.
Table ? — optional::operator=(optional<U>&&)
effects*this
contains a value*this
does not contain a valuerhs
contains a valueassigns std::move(*rhs)
to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type T
withstd::move(*rhs)
rhs
does not contain a valuedestroys the contained value by calling val->T::~T()
no effect -?- Returns:
-?- Postconditions:*this
.bool(rhs) == bool(*this)
. -?- Remarks: If any exception is thrown, the result of the expressionbool(*this)
remains unchanged. If an exception is thrown during the call toT
's constructor, the state of*rhs.val
is determined by the exception safety guarantee ofT
's constructor. If an exception is thrown during the call toT
's assignment, the state of*val
and*rhs.val
is 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
.