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.
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 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.
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:Twith the expressionstd::forward<U>(v).*thiscontains a value. -?- Throws: Any exception thrown by the selected constructor ofT. -?- Remarks: IfT'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 <class U> constexpr optional(const optional<U>& rhs);-?- Effects: If
-?- Postconditions:rhscontains a value, initializes the contained value as if direct-non-list-initializing an object of typeTwith the expression*rhs.bool(rhs) == bool(*this). -?- Throws: Any exception thrown by the selected constructor ofT. -?- Remarks: IfT'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 <class U> constexpr optional(optional<U>&& rhs);-?- Effects: If
-?- Postconditions: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.bool(rhs) == bool(*this). -?- Throws: Any exception thrown by the selected constructor ofT. -?- Remarks: IfT'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 <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 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 <class U> optional<T>& operator=(const optional<U>& rhs);-?- Requires:
-?- Effects:is_constructible_v<T, const U&>istrueandis_assignable_v<T&, const U&>istrue.
Table ? — optional::operator=(const optional<U>&)effects*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns *rhsto the contained valueinitializes the contained value as if direct-non-list-initializing an object of type Twith*rhsrhsdoes 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.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 <class U> optional<T>& operator=(optional<U>&& rhs);-?- Requires:
-?- Effects: The result of the expressionis_constructible_v<T, U>istrueandis_assignable_v<T&, U>istrue.bool(rhs)remains unchanged.
Table ? — optional::operator=(optional<U>&&)effects*thiscontains a value*thisdoes not contain a valuerhscontains a valueassigns std::move(*rhs)to the contained valueinitializes the contained value as if direct-non-list-initializing an object of type Twithstd::move(*rhs)rhsdoes 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.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.