This implements the changes in P2231R1 which make std::optional fully constexpr in C++20.
libstdc++-v3/ChangeLog: * include/bits/stl_construct.h (_Construct): Use std::construct_at when constant evaluated. * include/std/optional (_Storage, _Optional_payload, optional): Add constexpr as specified by P2231R1. * include/std/version (__cpp_lib_optional): Update value for C++20. * testsuite/20_util/optional/requirements.cc: Check feature test macro. * testsuite/20_util/optional/constexpr/assign.cc: New test. * testsuite/20_util/optional/constexpr/cons/conv.cc: New test. * testsuite/20_util/optional/constexpr/modifiers.cc: New test. * testsuite/20_util/optional/constexpr/swap.cc: New test. * testsuite/20_util/optional/version.cc: New test. Tested powerpc64le-linux. Committed to trunk.
commit 476f305b6cf11deec79a55cd5d30e1c13fad5bc0 Author: Jonathan Wakely <jwak...@redhat.com> Date: Wed Oct 13 22:32:28 2021 libstdc++: Add missing constexpr to std::optional (P2231R1) This implements the changes in P2231R1 which make std::optional fully constexpr in C++20. libstdc++-v3/ChangeLog: * include/bits/stl_construct.h (_Construct): Use std::construct_at when constant evaluated. * include/std/optional (_Storage, _Optional_payload, optional): Add constexpr as specified by P2231R1. * include/std/version (__cpp_lib_optional): Update value for C++20. * testsuite/20_util/optional/requirements.cc: Check feature test macro. * testsuite/20_util/optional/constexpr/assign.cc: New test. * testsuite/20_util/optional/constexpr/cons/conv.cc: New test. * testsuite/20_util/optional/constexpr/modifiers.cc: New test. * testsuite/20_util/optional/constexpr/swap.cc: New test. * testsuite/20_util/optional/version.cc: New test. diff --git a/libstdc++-v3/include/bits/stl_construct.h b/libstdc++-v3/include/bits/stl_construct.h index ad75eca69c2..e53ed0d9f91 100644 --- a/libstdc++-v3/include/bits/stl_construct.h +++ b/libstdc++-v3/include/bits/stl_construct.h @@ -88,7 +88,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __location->~_Tp(); } -#if __cplusplus > 201703L +#if __cplusplus >= 202002L template<typename _Tp, typename... _Args> constexpr auto construct_at(_Tp* __location, _Args&&... __args) @@ -104,9 +104,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION */ #if __cplusplus >= 201103L template<typename _Tp, typename... _Args> + _GLIBCXX20_CONSTEXPR inline void _Construct(_Tp* __p, _Args&&... __args) - { ::new(static_cast<void*>(__p)) _Tp(std::forward<_Args>(__args)...); } + { +#if __cplusplus >= 202002L && __has_builtin(__builtin_is_constant_evaluated) + if (__builtin_is_constant_evaluated()) + { + // Allow std::_Construct to be used in constant expressions. + std::construct_at(__p, std::forward<_Args>(__args)...); + return; + } +#endif + ::new(static_cast<void*>(__p)) _Tp(std::forward<_Args>(__args)...); + } #else template<typename _T1, typename _T2> inline void diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index b6ebe12b3e1..b69268b3642 100644 --- a/libstdc++-v3/include/std/optional +++ b/libstdc++-v3/include/std/optional @@ -40,6 +40,7 @@ #include <bits/enable_special_members.h> #include <bits/exception_defines.h> #include <bits/functional_hash.h> +#include <bits/stl_construct.h> // _Construct #include <bits/utility.h> // in_place_t #if __cplusplus > 201703L # include <compare> @@ -54,7 +55,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * @{ */ -#define __cpp_lib_optional 201606L +#if __cplusplus == 201703L +# define __cpp_lib_optional 201606L +#else +# define __cpp_lib_optional 202106L +#endif template<typename _Tp> class optional; @@ -228,7 +233,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { } // User-provided destructor is needed when _Up has non-trivial dtor. - ~_Storage() { } + _GLIBCXX20_CONSTEXPR ~_Storage() { } _Empty_byte _M_empty; _Up _M_value; @@ -239,12 +244,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION 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(std::__addressof(this->_M_payload._M_value), + std::forward<_Args>(__args)...); this->_M_engaged = true; } @@ -393,7 +398,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Optional_payload& operator=(_Optional_payload&&) = default; // Destructor needs to destroy the contained value: - ~_Optional_payload() { this->_M_reset(); } + _GLIBCXX20_CONSTEXPR ~_Optional_payload() { this->_M_reset(); } }; // Common base class for _Optional_base<T> to avoid repeating these @@ -407,17 +412,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // 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)...); - static_cast<_Dp*>(this)->_M_payload._M_engaged = true; + static_cast<_Dp*>(this)->_M_payload._M_construct( + std::forward<_Args>(__args)...); } - void + constexpr void _M_destruct() noexcept { static_cast<_Dp*>(this)->_M_payload._M_destroy(); } @@ -782,7 +785,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Assignment operators. - optional& + _GLIBCXX20_CONSTEXPR optional& operator=(nullopt_t) noexcept { this->_M_reset(); @@ -790,6 +793,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } template<typename _Up = _Tp> + _GLIBCXX20_CONSTEXPR enable_if_t<__and_v<__not_self<_Up>, __not_<__and_<is_scalar<_Tp>, is_same<_Tp, decay_t<_Up>>>>, @@ -809,6 +813,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } template<typename _Up> + _GLIBCXX20_CONSTEXPR enable_if_t<__and_v<__not_<is_same<_Tp, _Up>>, is_constructible<_Tp, const _Up&>, is_assignable<_Tp&, const _Up&>, @@ -834,6 +839,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } template<typename _Up> + _GLIBCXX20_CONSTEXPR enable_if_t<__and_v<__not_<is_same<_Tp, _Up>>, is_constructible<_Tp, _Up>, is_assignable<_Tp&, _Up>, @@ -860,6 +866,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } template<typename... _Args> + _GLIBCXX20_CONSTEXPR enable_if_t<is_constructible_v<_Tp, _Args...>, _Tp&> emplace(_Args&&... __args) noexcept(is_nothrow_constructible_v<_Tp, _Args...>) @@ -870,6 +877,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } template<typename _Up, typename... _Args> + _GLIBCXX20_CONSTEXPR enable_if_t<is_constructible_v<_Tp, initializer_list<_Up>&, _Args...>, _Tp&> emplace(initializer_list<_Up> __il, _Args&&... __args) @@ -884,7 +892,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Destructor is implicit, implemented in _Optional_base. // Swap. - void + _GLIBCXX20_CONSTEXPR void swap(optional& __other) noexcept(is_nothrow_move_constructible_v<_Tp> && is_nothrow_swappable_v<_Tp>) @@ -994,7 +1002,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return static_cast<_Tp>(std::forward<_Up>(__u)); } - void reset() noexcept { this->_M_reset(); } + _GLIBCXX20_CONSTEXPR void reset() noexcept { this->_M_reset(); } }; template<typename _Tp> @@ -1251,6 +1259,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // _GLIBCXX_RESOLVE_LIB_DEFECTS // 2748. swappable traits for optionals template<typename _Tp> + _GLIBCXX20_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))) diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index 24b86e0fa63..a395c05db2d 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -157,7 +157,9 @@ #define __cpp_lib_node_extract 201606 #define __cpp_lib_nonmember_container_access 201411 #define __cpp_lib_not_fn 201603 -#define __cpp_lib_optional 201606L +#if __cplusplus == 201703L // N.B. updated value in C++20 +# define __cpp_lib_optional 201606L +#endif #define __cpp_lib_parallel_algorithm 201603L #define __cpp_lib_raw_memory_algorithms 201606L #define __cpp_lib_sample 201603 @@ -255,6 +257,7 @@ # define __cpp_lib_make_obj_using_allocator 201811L #endif #define __cpp_lib_math_constants 201907L +#define __cpp_lib_optional 202106L #define __cpp_lib_polymorphic_allocator 201902L #if __cpp_lib_concepts # define __cpp_lib_ranges 202106L diff --git a/libstdc++-v3/testsuite/20_util/optional/constexpr/assign.cc b/libstdc++-v3/testsuite/20_util/optional/constexpr/assign.cc new file mode 100644 index 00000000000..fb82233052d --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/constexpr/assign.cc @@ -0,0 +1,94 @@ +// { dg-options "-std=gnu++20" } +// { dg-do compile { target c++20 } } + +#include <optional> +#include <testsuite_hooks.h> + + +constexpr bool +test_assign() +{ + std::optional<int> oi(1); + std::optional<unsigned> ou(2u), ou3(3u); + + // optional& operator=(nullopt_t); + oi = std::nullopt; + VERIFY( ! oi.has_value() ); + oi = std::nullopt; + VERIFY( ! oi.has_value() ); + + struct S { + constexpr S() { } + constexpr S(char, int, unsigned) { } + }; + std::optional<S> os1, os2; + + // template<class U = T> optional& operator=(U&&); + os1 = {'0', 1, 2u}; + VERIFY( os1.has_value() ); + os2 = {'3', 4, 5u}; + VERIFY( os2.has_value() ); + oi = 0u; + VERIFY( *oi == 0 ); + oi = 1u; + VERIFY( *oi == 1 ); + + // template<class U> optional& operator=(const optional<U>&); + oi = ou; + VERIFY( *oi == 2 ); + oi = ou3; + VERIFY( *oi == 3 ); + + // template<class U> optional& operator=(optional<U>&&); + oi = std::move(ou); + VERIFY( *oi == 2 ); + oi = std::move(ou); + VERIFY( *oi == 2 ); + oi = std::move(ou3); + VERIFY( *oi == 3 ); + + return true; +} + +static_assert( test_assign() ); + +constexpr bool +test_emplace() +{ + struct S + { + constexpr S(int i) : val(i) { } + constexpr S(int i, int j) : val(i + j) { } + constexpr S(std::initializer_list<char> l, int i = 0) : val(i) + { + for (char c : l) + val -= c; + } + + int val; + + constexpr bool operator==(int i) const { return val == i; } + }; + + + std::optional<S> os; + + // template<class... Args> constexpr T& emplace(Args&&...); + os.emplace(1); + VERIFY( *os == 1 ); + os.emplace(2); + VERIFY( *os == 2 ); + os.emplace(2, 3); + VERIFY( *os == 5 ); + + // template<class U, class... Args> + // constexpr T& emplace(initializer_list<U>, Args&&...); + os.emplace({'3', '4', '5'}); + VERIFY( *os == -156 ); + os.emplace({'6', '7', '8'}, 25); + VERIFY( *os == -140 ); + + return true; +} + +static_assert( test_emplace() ); diff --git a/libstdc++-v3/testsuite/20_util/optional/constexpr/cons/conv.cc b/libstdc++-v3/testsuite/20_util/optional/constexpr/cons/conv.cc new file mode 100644 index 00000000000..cc638148d25 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/constexpr/cons/conv.cc @@ -0,0 +1,22 @@ +// { dg-options "-std=gnu++20" } +// { dg-do compile { target c++20 } } + +#include <optional> +#include <testsuite_hooks.h> + +constexpr bool +test_cons() +{ + std::optional<int> oi(1); + std::optional<long> ol(oi); + VERIFY( *ol == 1L ); + VERIFY( *oi == 1 ); + + std::optional<unsigned> ou(std::move(oi)); + VERIFY( *ou == 1u ); + VERIFY( oi.has_value() && *oi == 1 ); + + return true; +} + +static_assert( test_cons() ); diff --git a/libstdc++-v3/testsuite/20_util/optional/constexpr/modifiers.cc b/libstdc++-v3/testsuite/20_util/optional/constexpr/modifiers.cc new file mode 100644 index 00000000000..614607d0216 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/constexpr/modifiers.cc @@ -0,0 +1,19 @@ +// { dg-options "-std=gnu++20" } +// { dg-do compile { target c++20 } } + +#include <optional> +#include <testsuite_hooks.h> + +constexpr bool +test_reset() +{ + std::optional<int> oi(1); + oi.reset(); + VERIFY( ! oi.has_value() ); + oi.reset(); + VERIFY( ! oi.has_value() ); + + return true; +} + +static_assert( test_reset() ); diff --git a/libstdc++-v3/testsuite/20_util/optional/constexpr/swap.cc b/libstdc++-v3/testsuite/20_util/optional/constexpr/swap.cc new file mode 100644 index 00000000000..2d18a51106c --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/constexpr/swap.cc @@ -0,0 +1,29 @@ +// { dg-options "-std=gnu++20" } +// { dg-do compile { target c++20 } } + +#include <optional> +#include <testsuite_hooks.h> + +constexpr bool +test_swap() +{ + std::optional<int> o0, o1(1); + o0.swap(o1); + VERIFY( *o0 == 1 ); + VERIFY( ! o1.has_value() ); + o0.swap(o1); + VERIFY( ! o0.has_value() ); + VERIFY( *o1 == 1 ); + o0.swap(o0); + VERIFY( ! o0.has_value() ); + o1.swap(o1); + VERIFY( *o1 == 1 ); + std::optional<int> o2(2); + swap(o1, o2); + VERIFY( *o1 == 2 ); + VERIFY( *o2 == 1 ); + + return true; +} + +static_assert( test_swap() ); diff --git a/libstdc++-v3/testsuite/20_util/optional/requirements.cc b/libstdc++-v3/testsuite/20_util/optional/requirements.cc index 550c0c4eac0..c24bd140351 100644 --- a/libstdc++-v3/testsuite/20_util/optional/requirements.cc +++ b/libstdc++-v3/testsuite/20_util/optional/requirements.cc @@ -18,6 +18,15 @@ // <http://www.gnu.org/licenses/>. #include <optional> + +#ifndef __cpp_lib_optional +# error "Feature test macro for optional is missing in <optional>" +#elif __cpp_lib_optional < 201606L +# error "Feature test macro for optional has wrong value in <optional>" +#elif __cplusplus >= 202002L && __cpp_lib_optional < 202106L +# error "Feature test macro for optional has wrong value for C++20 in <optional>" +#endif + #include <testsuite_hooks.h> #include <tuple> diff --git a/libstdc++-v3/testsuite/20_util/optional/version.cc b/libstdc++-v3/testsuite/20_util/optional/version.cc new file mode 100644 index 00000000000..d8c9851f28f --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/version.cc @@ -0,0 +1,11 @@ +// { dg-do compile { target c++17 } } + +#include <version> + +#ifndef __cpp_lib_optional +# error "Feature test macro for optional is missing in <version>" +#elif __cpp_lib_optional < 201606L +# error "Feature test macro for optional has wrong value in <version>" +#elif __cplusplus >= 202002L && __cpp_lib_optional < 202106L +# error "Feature test macro for optional has wrong value for C++20 in <version>" +#endif