diff mbox series

[committed] libstdc++: Add missing constexpr to std::optional (P2231R1)

Message ID YWfleac75R5LrK91@redhat.com
State Committed
Commit 476f305b6cf11deec79a55cd5d30e1c13fad5bc0
Headers show
Series [committed] libstdc++: Add missing constexpr to std::optional (P2231R1) | expand

Commit Message

Jonathan Wakely Oct. 14, 2021, 8:08 a.m. UTC
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 <jwakely@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 mbox series

Patch

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