optional
are not constexpr
Section: 22.5.3 [optional.optional] Status: C++17 Submitter: United States Opened: 2017-02-03 Last modified: 2020-09-06
Priority: Not Prioritized
View all other issues in [optional.optional].
View all issues with C++17 status.
Discussion:
Addresses US 111The copy and move constructors of optional
are not constexpr
. However, the constructors taking a
const T&
or T&&
are constexpr
, and there is a precedent for having a constexpr
copy constructor in 29.4.3 [complex]. The defaulted copy and move constructors of pair
and tuple
are also conditionally constexpr
(see 20.4.2 [pairs.pair] p2 and 20.5.2.1 [tuple.cnstr] p2).
A strong motivating use-case is constexpr
functions returning optional values. This issue was discovered while
working on a library making heavy use of such.
Proposed change: Add constexpr to:
optional(const optional &); optional(optional &&) noexcept(see below);
[2017-02-23, Casey comments and suggests wording]
This issue corresponds to NB comment US 111, which requests that the move and copy constructors of
std::optional
be declared constexpr
. The PR simply suggests adding the constexpr
specifier to the declarations of the constructors. The PR fails to specify the most important thing —
and this has been a failing of Library in general — under what conditions is the thing that we've
declared constexpr actually expected to be usable in constant expression context? (I think the proper
standardese here is "under what conditions is the full expression of an initialization that would invoke
these constructors a constant subexpression?")
optional<T>
must store a T
in a union to provide constexpr constructors that either do [optional(T const&)
] or do not
[optional()
] initialize the contained T
. A general implementation of optional
's
copy/move constructors must statically choose which union member, if any, to activate in each constructor.
Since there is no way to change the active member of a union in a constant expression, and a constructor must
statically choose a union member to activate (i.e., without being affected by the runtime state of the
copy/move constructor's argument) it's not possible to implement a general constexpr copy/move constructor
for optional
.
It is, however, possible to copy/move construct a trivially copy constructible/trivially move constructible union
in constexpr
context, which effectively copies the union's object representation, resulting in a union
whose active member is the same as the source union's. Indeed, at least two major implementations of optional
(MSVC and libc++) already provide that behavior as a conforming optimization when T
is a trivially
copyable type. If we are to declare optional<T>
's copy and move constructors constexpr
,
we should additionally specify that those constructors are only required to have the "constexpr mojo" when T
is trivially copyable. (Note that I suggest "trivially copyable" here rather than "trivially copy constructible or
trivially move constructible" since the simpler requirement is simpler to implement, and I don't believe the more
complicated requirement provides any additional benefit: I've never seen a trivially copy constructible or
trivially move constructible type outside of a test suite that was not also trivially copyable.)
Previous resolution [SUPERSEDED]:
This wording is relative to N4618.
Edit 22.5.3 [optional.optional] as indicated:
constexpr optional(const optional &);
constexpr optional(optional &&) noexcept(see below);
Edit 22.5.3.2 [optional.ctor] paragraph as indicated:
constexpr optional(const optional &);
and
constexpr optional(optional &&) noexcept(see below);
[2017-02-23, Marshall comments]
This is related to LWG 2745.
[2017-02-28, Kona, Casey comments and improves wording]
Amended PR per LWG discussion in Kona: replace the "is trivially copyable" requirement with the more specific
is_trivially_copy/move_constructible<T>
. LWG was concerned that tuple
is a counter-example
to the assumption that all three traits are equivalent for real-world types.
Previous resolution [SUPERSEDED]:
This wording is relative to N4640.
Change the synopsis of class template
optional
in 22.5.3 [optional.optional] as follows:[…] // 20.6.3.1, constructors constexpr optional() noexcept; constexpr optional(nullopt_t) noexcept; constexpr optional(const optional&); constexpr optional(optional&&) noexcept(see below); […]Modify 22.5.3.2 [optional.ctor] as indicated:
constexpr optional(const optional& rhs);[…]
-6- Remarks: This constructor shall be defined as deleted unlessis_copy_constructible_v<T>
istrue
. IfT
is a trivially copyable type, this constructor shall be aconstexpr
constructor.constexpr optional(optional&& rhs) noexcept(see below);[…]
-10- Remarks: The expression insidenoexcept
is equivalent tois_nothrow_move_constructible_v<T>
. This constructor shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
. IfT
is a trivially copyable type, this constructor shall be aconstexpr
constructor.
[Kona 2017-02-27]
Accepted as Immediate to resolve NB comment.
Proposed resolution:
This wording is relative to N4640.
Change the synopsis of class template optional
in 22.5.3 [optional.optional] as follows:
[…] // 20.6.3.1, constructors constexpr optional() noexcept; constexpr optional(nullopt_t) noexcept; constexpr optional(const optional&); constexpr optional(optional&&) noexcept(see below); […]
Modify 22.5.3.2 [optional.ctor] as indicated:
constexpr optional(const optional& rhs);[…]
-6- Remarks: This constructor shall be defined as deleted unlessis_copy_constructible_v<T>
istrue
. Ifis_trivially_copy_constructible_v<T>
istrue
, this constructor shall be aconstexpr
constructor.constexpr optional(optional&& rhs) noexcept(see below);[…]
-10- Remarks: The expression insidenoexcept
is equivalent tois_nothrow_move_constructible_v<T>
. This constructor shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
. Ifis_trivially_move_constructible_v<T>
istrue
, this constructor shall be aconstexpr
constructor.