constexpr
in std::optional
and std::variant
Document #: | P2231R0 |
Date: | 2020-10-08 |
Project: | Programming Language C++ |
Audience: |
LEWG |
Reply-to: |
Barry Revzin <[email protected]> |
Each new language standard has increased the kinds of operations that we can do during constant evaluation time. C++20 was no different. With the adoption of [P1330R0], C++20 added the ability to change the active member of a union inside constexpr (the paper specifically mentions std::optional
). And with the adoption of [P0784R7], C++20 added the ability to do placement new inside constexpr (by way of std::construct_at
).
But even though the language provided the tools to make std::optional
and std::variant
completely constexpr-able, there was no such update to the library. This paper seeks to remedy that omission by simply adding constexpr
to all the relevant places.
std::optional
I updated libstdc++’s implementation of std::optional
to add these constexpr
s (and replace the placement new
calls with calls to std::construct_at
) as follows, which compiles with both gcc and clang (demo):
-13,6 +11,7
#include <bits/exception_defines.h>
#include <bits/functional_hash.h>
#include <bits/enable_special_members.h>
+#include <bits/stl_construct.h>
#if __cplusplus > 201703L
# include <compare>
#endif
-206,7 +205,7
{ }
// User-provided destructor is needed when _Up has non-trivial dtor.
- ~_Storage() { }
+ constexpr ~_Storage() { }
_Empty_byte _M_empty;
_Up _M_value;
-217,12 +216,12
bool _M_engaged = false;
template<typename... _Args>
- void
+ constexpr void
_M_construct(_Args&&... __args)
noexcept(is_nothrow_constructible_v<_Stored_type, _Args...>)
{
- ::new ((void *) std::__addressof(this->_M_payload))
- _Stored_type(std::forward<_Args>(__args)...);
+ std::construct_at(std::__addressof(this->_M_payload),
+ std::forward<_Args>(__args)...);
this->_M_engaged = true;
}
-371,7 +370,7
_Optional_payload& operator=(_Optional_payload&&) = default;
// Destructor needs to destroy the contained value:
- ~_Optional_payload()
+ constexpr ~_Optional_payload()
{
this->_M_reset();
}
-388,17 +387,16
// The _M_construct operation has !_M_engaged as a precondition
// while _M_destruct has _M_engaged as a precondition.
template<typename... _Args>
- void
+ constexpr void
_M_construct(_Args&&... __args)
noexcept(is_nothrow_constructible_v<_Stored_type, _Args...>)
{
- ::new
- (std::__addressof(static_cast<_Dp*>(this)->_M_payload._M_payload))
- _Stored_type(std::forward<_Args>(__args)...);
+ std::construct_at(std::__addressof(static_cast<_Dp*>(this)->_M_payload._M_payload._M_value),
+ std::forward<_Args>(__args)...);
static_cast<_Dp*>(this)->_M_payload._M_engaged = true;
}
- void
+ constexpr void
_M_destruct() noexcept
{
static_cast<_Dp*>(this)->_M_payload._M_destroy();
-754,7 +752,7
: _Base(std::in_place, __il, std::forward<_Args>(__args)...) { }
// Assignment operators.
- optional&
+ constexpr optional&
operator=(nullopt_t) noexcept
{
this->_M_reset();
-762,6 +760,7
}
template<typename _Up = _Tp>
+ constexpr
enable_if_t<__and_v<__not_self<_Up>,
__not_<__and_<is_scalar<_Tp>,
is_same<_Tp, decay_t<_Up>>>>,
-780,6 +779,7
}
template<typename _Up>
+ constexpr
enable_if_t<__and_v<__not_<is_same<_Tp, _Up>>,
is_constructible<_Tp, const _Up&>,
is_assignable<_Tp&, _Up>,
-801,6 +801,7
}
template<typename _Up>
+ constexpr
enable_if_t<__and_v<__not_<is_same<_Tp, _Up>>,
is_constructible<_Tp, _Up>,
is_assignable<_Tp&, _Up>,
-823,6 +824,7
}
template<typename... _Args>
+ constexpr
enable_if_t<is_constructible_v<_Tp, _Args&&...>, _Tp&>
emplace(_Args&&... __args)
{
-832,6 +834,7
}
template<typename _Up, typename... _Args>
+ constexpr
enable_if_t<is_constructible_v<_Tp, initializer_list<_Up>&,
_Args&&...>, _Tp&>
emplace(initializer_list<_Up> __il, _Args&&... __args)
-844,7 +847,7
// Destructor is implicit, implemented in _Optional_base.
// Swap.
- void
+ constexpr void
swap(optional& __other)
noexcept(is_nothrow_move_constructible_v<_Tp>
&& is_nothrow_swappable_v<_Tp>)
-957,7 +960,7
: static_cast<_Tp>(std::forward<_Up>(__u));
}
- void reset() noexcept
+ constexpr void reset() noexcept
{
this->_M_reset();
}
-1187,7 +1190,7
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2748. swappable traits for optionals
template<typename _Tp>
-inline enable_if_t<is_move_constructible_v<_Tp> && is_swappable_v<_Tp>>
+constexpr inline enable_if_t<is_move_constructible_v<_Tp> && is_swappable_v<_Tp>>
swap(optional<_Tp>& __lhs, optional<_Tp>& __rhs)
noexcept(noexcept(__lhs.swap(__rhs))) {
__lhs.swap(__rhs);
std::variant
And likewise, here is a diff against libstdc++’s implementation of std::variant
for the changes proposed in this paper, which also compiles on both gcc and clang (demo). This is slightly more complicated as we have to take more care with constructing and accessing the recursive union:
-121,7 +121,7
__do_visit(_Visitor&& __visitor, _Variants&&... __variants);
template <typename... _Types, typename _Tp>
-decltype(auto)
+constexpr decltype(auto)
__variant_cast(_Tp&& __rhs) {
if constexpr (is_lvalue_reference_v<_Tp>) {
if constexpr (is_const_v<remove_reference_t<_Tp>>) {
-329,6 +329,11
: _M_rest(in_place_index<_Np-1>, std::forward<_Args>(__args)...)
{ }
+ constexpr ~_Variadic_union() {}
+ constexpr ~_Variadic_union()
+ requires (std::is_trivially_destructible_v<_First> && ... &&
+ std::is_trivially_destructible_v<_Rest>) = default;
+
_Uninitialized<_First> _M_first;
_Variadic_union<_Rest...> _M_rest;
};
-380,7 +385,7
_M_index(_Np)
{ }
- void _M_reset()
+ constexpr void _M_reset()
{
if (!_M_valid()) [[unlikely]]
return;
-392,7 +397,7
_M_index = variant_npos;
}
- ~_Variant_storage()
+ constexpr ~_Variant_storage()
{
_M_reset();
}
-420,6 +425,10
template<typename... _Types>
struct _Variant_storage<true, _Types...> {
+ template<typename _Tp>
+ static constexpr size_t __index_of =
+ __detail::__variant::__index_of_v<_Tp, _Types...>;
+
constexpr _Variant_storage() : _M_index(variant_npos) { }
template<size_t _Np, typename... _Args>
-428,7 +437,7
_M_index(_Np)
{ }
- void _M_reset() noexcept
+ constexpr void _M_reset() noexcept
{
_M_index = variant_npos;
}
-459,13 +468,16
_Variant_storage<_Traits<_Types...>::_S_trivial_dtor, _Types...>;
template<typename _Tp, typename _Up>
-void __variant_construct_single(_Tp&& __lhs, _Up&& __rhs_mem)
+constexpr void __variant_construct_single(_Tp&& __lhs, _Up&& __rhs_mem)
{
- void* __storage = std::addressof(__lhs._M_u);
using _Type = remove_reference_t<decltype(__rhs_mem)>;
- if constexpr (!is_same_v<_Type, __variant_cookie>)
- ::new (__storage)
- _Type(std::forward<decltype(__rhs_mem)>(__rhs_mem));
+ constexpr auto index = remove_reference_t<_Tp>::template __index_of<_Type>;
+
+ if constexpr (!is_same_v<_Type, __variant_cookie>) {
+ std::construct_at(std::addressof(__lhs._M_u),
+ in_place_index<index>,
+ _Type(std::forward<decltype(__rhs_mem)>(__rhs_mem)));
+ }
}
template<typename... _Types, typename _Tp, typename _Up>
-519,7 +531,7
}
template<typename _Up>
- void _M_destructive_move(unsigned short __rhs_index, _Up&& __rhs)
+ constexpr void _M_destructive_move(unsigned short __rhs_index, _Up&& __rhs)
{
this->_M_reset();
__variant_construct_single(*this, std::forward<_Up>(__rhs));
-527,7 +539,7
}
template<typename _Up>
- void _M_destructive_copy(unsigned short __rhs_index, const _Up& __rhs)
+ constexpr void _M_destructive_copy(unsigned short __rhs_index, const _Up& __rhs)
{
this->_M_reset();
__variant_construct_single(*this, __rhs);
-545,7 +557,7
using _Base::_Base;
template<typename _Up>
- void _M_destructive_move(unsigned short __rhs_index, _Up&& __rhs)
+ constexpr void _M_destructive_move(unsigned short __rhs_index, _Up&& __rhs)
{
this->_M_reset();
__variant_construct_single(*this, std::forward<_Up>(__rhs));
-553,7 +565,7
}
template<typename _Up>
- void _M_destructive_copy(unsigned short __rhs_index, const _Up& __rhs)
+ constexpr void _M_destructive_copy(unsigned short __rhs_index, const _Up& __rhs)
{
this->_M_reset();
__variant_construct_single(*this, __rhs);
-570,7 +582,7
using _Base = _Move_ctor_alias<_Types...>;
using _Base::_Base;
- _Copy_assign_base&
+ constexpr _Copy_assign_base&
operator=(const _Copy_assign_base& __rhs)
noexcept(_Traits<_Types...>::_S_nothrow_copy_assign)
{
-625,7 +637,7
using _Base = _Copy_assign_alias<_Types...>;
using _Base::_Base;
- _Move_assign_base&
+ constexpr _Move_assign_base&
operator=(_Move_assign_base&& __rhs)
noexcept(_Traits<_Types...>::_S_nothrow_move_assign)
{
-1033,12 +1045,10
} // namespace __detail
template<size_t _Np, typename _Variant, typename... _Args>
-void __variant_construct_by_index(_Variant& __v, _Args&&... __args) {
- __v._M_index = _Np;
- auto&& __storage = __detail::__variant::__get<_Np>(__v);
- ::new ((void*)std::addressof(__storage))
- remove_reference_t<decltype(__storage)>
- (std::forward<_Args>(__args)...);
+constexpr void __variant_construct_by_index(_Variant& __v, _Args&&... __args) {
+ std::construct_at(std::addressof(__v),
+ in_place_index<_Np>,
+ std::forward<_Args>(__args)...);
}
template<typename _Tp, typename... _Types>
-1220,6 +1230,7
constexpr decltype(auto) visit(_Visitor&&, _Variants&&...);
template<typename... _Types>
+constexpr
inline enable_if_t<(is_move_constructible_v<_Types> && ...)
&& (is_swappable_v<_Types> && ...)>
swap(variant<_Types...>& __lhs, variant<_Types...>& __rhs)
-1281,9 +1292,9
{
private:
template <typename... _UTypes, typename _Tp>
- friend decltype(auto) __variant_cast(_Tp&&);
+ friend constexpr decltype(auto) __variant_cast(_Tp&&);
template<size_t _Np, typename _Variant, typename... _Args>
- friend void __variant_construct_by_index(_Variant& __v,
+ friend constexpr void __variant_construct_by_index(_Variant& __v,
_Args&&... __args);
static_assert(sizeof...(_Types) > 0,
-1397,6 +1408,7
{ }
template<typename _Tp>
+ constexpr
enable_if_t<__exactly_once<__accepted_type<_Tp&&>>
&& is_constructible_v<__accepted_type<_Tp&&>, _Tp>
&& is_assignable_v<__accepted_type<_Tp&&>&, _Tp>,
-1421,6 +1433,7
}
template<typename _Tp, typename... _Args>
+ constexpr
enable_if_t<is_constructible_v<_Tp, _Args...> && __exactly_once<_Tp>,
_Tp&>
emplace(_Args&&... __args)
-1430,6 +1443,7
}
template<typename _Tp, typename _Up, typename... _Args>
+ constexpr
enable_if_t<is_constructible_v<_Tp, initializer_list<_Up>&, _Args...>
&& __exactly_once<_Tp>,
_Tp&>
-1440,6 +1454,7
}
template<size_t _Np, typename... _Args>
+ constexpr
enable_if_t<is_constructible_v<variant_alternative_t<_Np, variant>,
_Args...>,
variant_alternative_t<_Np, variant>&>
-1484,6 +1499,7
}
template<size_t _Np, typename _Up, typename... _Args>
+ constexpr
enable_if_t<is_constructible_v<variant_alternative_t<_Np, variant>,
initializer_list<_Up>&, _Args...>,
variant_alternative_t<_Np, variant>&>
-1540,7 +1556,7
}
}
- void
+ constexpr void
swap(variant& __rhs)
noexcept((__is_nothrow_swappable<_Types>::value && ...)
&& is_nothrow_move_constructible_v<variant>)
[ Editor's note: The wording here just shows the added constexpr
s in the synopses. They all need to be repeated in the specific wording for each function. ]
Add constexpr
to the swap
in 20.6.2 [optional.syn] (and in 20.6.9 [optional.specalg]):
namespace std {
// [optional.optional], class template optional
template<class T>
class optional;
[...]
// [optional.specalg], specialized algorithms
template<class T>
- void swap(optional<T>&, optional<T>&) noexcept(see below);
+ constexpr void swap(optional<T>&, optional<T>&) noexcept(see below);
[...]
}
Add constexpr
to the rest of the functions in 20.6.3.1 [optional.optional.general] (and likewise in 20.6.3.2 [optional.ctor], 20.6.3.3 [optional.dtor], 20.6.3.4 [optional.assign], 20.6.3.5 [optional.swap], and 20.6.3.7 [optional.mod]):
namespace std {
template<class T>
class optional {
public:
using value_type = T;
// [optional.ctor], constructors
constexpr optional() noexcept;
constexpr optional(nullopt_t) noexcept;
constexpr optional(const optional&);
constexpr optional(optional&&) noexcept(see below);
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 = T>
constexpr explicit(see below) optional(U&&);
template<class U>
- explicit(see below) optional(const optional<U>&);
+ constexpr explicit(see below) optional(const optional<U>&);
template<class U>
- explicit(see below) optional(optional<U>&&);
+ constexpr explicit(see below) optional(optional<U>&&);
// [optional.dtor], destructor
- ~optional();
+ constexpr ~optional();
// [optional.assign], assignment
- optional& operator=(nullopt_t) noexcept;
+ constexpr optional& operator=(nullopt_t) noexcept;
constexpr optional& operator=(const optional&);
constexpr optional& operator=(optional&&) noexcept(see below);
- template<class U = T> optional& operator=(U&&);
- template<class U> optional& operator=(const optional<U>&);
- template<class U> optional& operator=(optional<U>&&);
- template<class... Args> T& emplace(Args&&...);
- template<class U, class... Args> T& emplace(initializer_list<U>, Args&&...);
+ template<class U = T> constexpr optional& operator=(U&&);
+ template<class U> constexpr optional& operator=(const optional<U>&);
+ template<class U> constexpr optional& operator=(optional<U>&&);
+ template<class... Args> constexpr T& emplace(Args&&...);
+ template<class U, class... Args> constexpr T& emplace(initializer_list<U>, Args&&...);
// [optional.swap], swap
- void swap(optional&) noexcept(see below);
+ constexpr void swap(optional&) noexcept(see below);
// [optional.observe], observers
constexpr const T* operator->() const;
constexpr T* operator->();
constexpr const T& operator*() const&;
constexpr T& operator*() &;
constexpr T&& operator*() &&;
constexpr const T&& operator*() const&&;
constexpr explicit operator bool() const noexcept;
constexpr bool has_value() const noexcept;
constexpr const T& value() const&;
constexpr T& value() &;
constexpr T&& value() &&;
constexpr const T&& value() const&&;
template<class U> constexpr T value_or(U&&) const&;
template<class U> constexpr T value_or(U&&) &&;
// [optional.mod], modifiers
- void reset() noexcept;
+ constexpr void reset() noexcept;
private:
T *val; // exposition only
};
template<class T>
optional(T) -> optional<T>;
}
Add constexpr
to swap
in 20.7.2 [variant.syn] (and likewise in 20.7.10 [variant.specalg]):
namespace std {
// [variant.variant], class template variant
template<class... Types>
class variant;
[...]
// [variant.specalg], specialized algorithms
template<class... Types>
- void swap(variant<Types...>&, variant<Types...>&) noexcept(see below);
+ constexpr void swap(variant<Types...>&, variant<Types...>&) noexcept(see below);
[...]
}
Add constexpr
to the rest of the functions in 20.7.3.1 [variant.variant.general] (and likewise in 20.7.3.3 [variant.dtor], 20.7.3.4 [variant.assign], 20.7.3.5 [variant.mod], and 20.7.3.7 [variant.swap]):
namespace std {
template<class... Types>
class variant {
public:
// [variant.ctor], constructors
constexpr variant() noexcept(see below);
constexpr variant(const variant&);
constexpr variant(variant&&) noexcept(see below);
template<class T>
constexpr variant(T&&) noexcept(see below);
template<class T, class... Args>
constexpr explicit variant(in_place_type_t<T>, Args&&...);
template<class T, class U, class... Args>
constexpr explicit variant(in_place_type_t<T>, initializer_list<U>, Args&&...);
template<size_t I, class... Args>
constexpr explicit variant(in_place_index_t<I>, Args&&...);
template<size_t I, class U, class... Args>
constexpr explicit variant(in_place_index_t<I>, initializer_list<U>, Args&&...);
// [variant.dtor], destructor
- ~variant();
+ constexpr ~variant();
// [variant.assign], assignment
constexpr variant& operator=(const variant&);
constexpr variant& operator=(variant&&) noexcept(see below);
- template<class T> variant& operator=(T&&) noexcept(see below);
+ template<class T> constexpr variant& operator=(T&&) noexcept(see below);
// [variant.mod], modifiers
- template<class T, class... Args>
- T& emplace(Args&&...);
- template<class T, class U, class... Args>
- T& emplace(initializer_list<U>, Args&&...);
- template<size_t I, class... Args>
- variant_alternative_t<I, variant<Types...>>& emplace(Args&&...);
- template<size_t I, class U, class... Args>
- variant_alternative_t<I, variant<Types...>>& emplace(initializer_list<U>, Args&&...);
+ template<class T, class... Args>
+ constexpr T& emplace(Args&&...);
+ template<class T, class U, class... Args>
+ constexpr T& emplace(initializer_list<U>, Args&&...);
+ template<size_t I, class... Args>
+ constexpr variant_alternative_t<I, variant<Types...>>& emplace(Args&&...);
+ template<size_t I, class U, class... Args>
+ constexpr variant_alternative_t<I, variant<Types...>>& emplace(initializer_list<U>, Args&&...);
// [variant.status], value status
constexpr bool valueless_by_exception() const noexcept;
constexpr size_t index() const noexcept;
// [variant.swap], swap
- void swap(variant&) noexcept(see below);
+ constexpr void swap(variant&) noexcept(see below);
};
}
Thanks to Tim Song for all the help. Thanks to Jonathan Wakely for looking over the paper, pointing out how repetitive the introduction was, and pointing out how repetitive the introduction was.
[P0784R7] Daveed Vandevoorde, Peter Dimov,Louis Dionne, Nina Ranns, Richard Smith, Daveed Vandevoorde. 2019. More constexpr containers.
https://wg21.link/p0784r7
[P1330R0] Louis Dionne, David Vandevoorde. 2018. Changing the active member of a union inside constexpr.
https://wg21.link/p1330r0