diff mbox series

c++: constexpr std::construct_at on empty field [PR101663]

Message ID 20210803200401.1170877-1-ppalka@redhat.com
State New
Headers show
Series c++: constexpr std::construct_at on empty field [PR101663] | expand

Commit Message

Patrick Palka Aug. 3, 2021, 8:04 p.m. UTC
Here during constexpr evaluation of

  std::construct_at(&a._M_value)

we find ourselves in cxx_eval_store_expression where the target object
is 'a._M_value' and the initializer is {}.  Since _M_value is an empty
[[no_unique_address]] member we don't create a sub-CONSTRUCTOR for it,
so we end up in the early exit code path for empty stores with mismatched
types and we trip over the assert therein

  gcc_assert (is_empty_class (TREE_TYPE (init)) && !lval);

because lval is true.  The reason it's true is because the INIT_EXPR in
question is the LHS of a COMPOUND_EXPR, and evaluation of the LHS is
always performed with lval=true for some reason.  This is the case ever
since r5-5900, before which we used to do the evaluation with
lval=false.

I'm not sure why we evaluate the LHS of a COMPOUND_EXPR with lval=true
(changing it to false survives bootstrap+regtest and is sufficient to
fix the PR), but regardless it's also straightforward enough to make the
relevant code path in cxx_eval_store_expression handle lval=true, which
is the approach this patch takes.

This patch also consolidates the duplicate implementations of
std::construct_at/destroy_at from some of the C++20 constexpr tests into
a common header file.

Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
trunk/11?

	PR c++/101663

gcc/cp/ChangeLog:

	* constexpr.c (cxx_eval_store_expression): In the early exit
	code path for mismatched types,
	Pass false instead of true for lval when evaluating the LHS.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/construct_at.h: New convenience header that
	defines minimal implementations of std::construct_at/destroy_at,
	split out from ...
	* g++.dg/cpp2a/constexpr-new5.C: ... here.
	* g++.dg/cpp2a/constexpr-new6.C: Use the header.
	* g++.dg/cpp2a/constexpr-new14.C: Likewise.
	* g++.dg/cpp2a/constexpr-new20.C: New test.
---
 gcc/cp/constexpr.c                           |  4 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C | 60 +-----------------
 gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C | 18 ++++++
 gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C  | 60 +-----------------
 gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C  | 64 +-------------------
 gcc/testsuite/g++.dg/cpp2a/construct_at.h    | 62 +++++++++++++++++++
 6 files changed, 85 insertions(+), 183 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/construct_at.h

Comments

Jason Merrill Aug. 11, 2021, 10:47 p.m. UTC | #1
On 8/3/21 4:04 PM, Patrick Palka wrote:
> Here during constexpr evaluation of
> 
>    std::construct_at(&a._M_value)
> 
> we find ourselves in cxx_eval_store_expression where the target object
> is 'a._M_value' and the initializer is {}.  Since _M_value is an empty
> [[no_unique_address]] member we don't create a sub-CONSTRUCTOR for it,
> so we end up in the early exit code path for empty stores with mismatched
> types and we trip over the assert therein
> 
>    gcc_assert (is_empty_class (TREE_TYPE (init)) && !lval);
> 
> because lval is true.  The reason it's true is because the INIT_EXPR in
> question is the LHS of a COMPOUND_EXPR, and evaluation of the LHS is
> always performed with lval=true for some reason.  This is the case ever
> since r5-5900, before which we used to do the evaluation with
> lval=false.
> 
> I'm not sure why we evaluate the LHS of a COMPOUND_EXPR with lval=true

Because there's no lvalue-rvalue conversion. We could change that bool 
to be a tri-value enum that also includes discarded-value expressions 
such as this, but that hasn't seemed necessary.

> (changing it to false survives bootstrap+regtest and is sufficient to
> fix the PR), but regardless it's also straightforward enough to make the
> relevant code path in cxx_eval_store_expression handle lval=true, which
> is the approach this patch takes.
> 
> This patch also consolidates the duplicate implementations of
> std::construct_at/destroy_at from some of the C++20 constexpr tests into
> a common header file.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk/11?
> 
> 	PR c++/101663
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.c (cxx_eval_store_expression): In the early exit
> 	code path for mismatched types,
> 	Pass false instead of true for lval when evaluating the LHS.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/construct_at.h: New convenience header that
> 	defines minimal implementations of std::construct_at/destroy_at,
> 	split out from ...
> 	* g++.dg/cpp2a/constexpr-new5.C: ... here.
> 	* g++.dg/cpp2a/constexpr-new6.C: Use the header.
> 	* g++.dg/cpp2a/constexpr-new14.C: Likewise.
> 	* g++.dg/cpp2a/constexpr-new20.C: New test.
> ---
>   gcc/cp/constexpr.c                           |  4 +-
>   gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C | 60 +-----------------
>   gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C | 18 ++++++
>   gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C  | 60 +-----------------
>   gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C  | 64 +-------------------
>   gcc/testsuite/g++.dg/cpp2a/construct_at.h    | 62 +++++++++++++++++++
>   6 files changed, 85 insertions(+), 183 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/construct_at.h
> 
> diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
> index 1af365d47b9..25d84a377d8 100644
> --- a/gcc/cp/constexpr.c
> +++ b/gcc/cp/constexpr.c
> @@ -5588,8 +5588,8 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
>   	   argument, which has the derived type rather than the base type.  In
>   	   this situation, just evaluate the initializer and return, since
>   	   there's no actual data to store.  */
> -	  gcc_assert (is_empty_class (TREE_TYPE (init)) && !lval);
> -	  return init;
> +	  gcc_assert (is_empty_class (TREE_TYPE (init)));
> +	  return lval ? target : init;
>   	}
>         CONSTRUCTOR_ELTS (*valp) = CONSTRUCTOR_ELTS (init);
>         TREE_CONSTANT (*valp) = TREE_CONSTANT (init);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C
> index fd6f6075ef0..26037397b1d 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C
> @@ -1,65 +1,7 @@
>   // PR c++/97195
>   // { dg-do compile { target c++20 } }
>   
> -namespace std
> -{
> -  typedef __SIZE_TYPE__ size_t;
> -
> -  template <typename T>
> -  struct allocator
> -  {
> -    constexpr allocator () noexcept {}
> -
> -    constexpr T *allocate (size_t n)
> -    { return static_cast<T *> (::operator new (n * sizeof(T))); }
> -
> -    constexpr void
> -    deallocate (T *p, size_t n)
> -    { ::operator delete (p); }
> -  };
> -
> -  template <typename T, typename U = T &&>
> -  U __declval (int);
> -  template <typename T>
> -  T __declval (long);
> -  template <typename T>
> -  auto declval () noexcept -> decltype (__declval<T> (0));
> -
> -  template <typename T>
> -  struct remove_reference
> -  { typedef T type; };
> -  template <typename T>
> -  struct remove_reference<T &>
> -  { typedef T type; };
> -  template <typename T>
> -  struct remove_reference<T &&>
> -  { typedef T type; };
> -
> -  template <typename T>
> -  constexpr T &&
> -  forward (typename std::remove_reference<T>::type &t) noexcept
> -  { return static_cast<T&&> (t); }
> -
> -  template<typename T>
> -  constexpr T &&
> -  forward (typename std::remove_reference<T>::type &&t) noexcept
> -  { return static_cast<T&&> (t); }
> -
> -  template <typename T, typename... A>
> -  constexpr auto
> -  construct_at (T *l, A &&... a)
> -  noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
> -  -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
> -  { return ::new ((void *) l) T (std::forward<A> (a)...); }
> -
> -  template <typename T>
> -  constexpr inline void
> -  destroy_at (T *l)
> -  { l->~T (); }
> -}
> -
> -inline void *operator new (std::size_t, void *p) noexcept
> -{ return p; }
> +#include "construct_at.h"
>   
>   constexpr bool
>   foo ()
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C
> new file mode 100644
> index 00000000000..88bc4429a8a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C
> @@ -0,0 +1,18 @@
> +// PR c++/101663
> +// { dg-do compile { target c++20 } }
> +
> +#include "construct_at.h"
> +
> +template <typename _Tp> struct __box {
> +  [[no_unique_address]] _Tp _M_value;
> +};
> +
> +struct Empty {};
> +
> +constexpr bool test() {
> +  __box<Empty> a;
> +  std::construct_at(&a._M_value);
> +  return true;
> +}
> +
> +static_assert(test());
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C
> index 2bb407a4b51..eeaee969266 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C
> @@ -1,65 +1,7 @@
>   // P0784R7
>   // { dg-do compile { target c++20 } }
>   
> -namespace std
> -{
> -  typedef __SIZE_TYPE__ size_t;
> -
> -  template <typename T>
> -  struct allocator
> -  {
> -    constexpr allocator () noexcept {}
> -
> -    constexpr T *allocate (size_t n)
> -    { return static_cast<T *> (::operator new (n * sizeof(T))); }
> -
> -    constexpr void
> -    deallocate (T *p, size_t n)
> -    { ::operator delete (p); }
> -  };
> -
> -  template <typename T, typename U = T &&>
> -  U __declval (int);
> -  template <typename T>
> -  T __declval (long);
> -  template <typename T>
> -  auto declval () noexcept -> decltype (__declval<T> (0));
> -
> -  template <typename T>
> -  struct remove_reference
> -  { typedef T type; };
> -  template <typename T>
> -  struct remove_reference<T &>
> -  { typedef T type; };
> -  template <typename T>
> -  struct remove_reference<T &&>
> -  { typedef T type; };
> -
> -  template <typename T>
> -  constexpr T &&
> -  forward (typename std::remove_reference<T>::type &t) noexcept
> -  { return static_cast<T&&> (t); }
> -
> -  template<typename T>
> -  constexpr T &&
> -  forward (typename std::remove_reference<T>::type &&t) noexcept
> -  { return static_cast<T&&> (t); }
> -
> -  template <typename T, typename... A>
> -  constexpr auto
> -  construct_at (T *l, A &&... a)
> -  noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
> -  -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
> -  { return ::new ((void *) l) T (std::forward<A> (a)...); }
> -
> -  template <typename T>
> -  constexpr inline void
> -  destroy_at (T *l)
> -  { l->~T (); }
> -}
> -
> -inline void *operator new (std::size_t, void *p) noexcept
> -{ return p; }
> +#include "construct_at.h"
>   
>   constexpr bool
>   foo ()
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C
> index d51bdbb8269..eeaee969266 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C
> @@ -1,69 +1,7 @@
>   // P0784R7
>   // { dg-do compile { target c++20 } }
>   
> -namespace std
> -{
> -  inline namespace _8 { }
> -  namespace _8 {
> -
> -    typedef __SIZE_TYPE__ size_t;
> -
> -    template <typename T>
> -    struct allocator
> -    {
> -      constexpr allocator () noexcept {}
> -
> -      constexpr T *allocate (size_t n)
> -      { return static_cast<T *> (::operator new (n * sizeof(T))); }
> -
> -      constexpr void
> -      deallocate (T *p, size_t n)
> -      { ::operator delete (p); }
> -    };
> -
> -    template <typename T, typename U = T &&>
> -    U __declval (int);
> -    template <typename T>
> -    T __declval (long);
> -    template <typename T>
> -    auto declval () noexcept -> decltype (__declval<T> (0));
> -
> -    template <typename T>
> -    struct remove_reference
> -    { typedef T type; };
> -    template <typename T>
> -    struct remove_reference<T &>
> -    { typedef T type; };
> -    template <typename T>
> -    struct remove_reference<T &&>
> -    { typedef T type; };
> -
> -    template <typename T>
> -    constexpr T &&
> -    forward (typename std::remove_reference<T>::type &t) noexcept
> -    { return static_cast<T&&> (t); }
> -
> -    template<typename T>
> -    constexpr T &&
> -    forward (typename std::remove_reference<T>::type &&t) noexcept
> -    { return static_cast<T&&> (t); }
> -
> -    template <typename T, typename... A>
> -    constexpr auto
> -    construct_at (T *l, A &&... a)
> -    noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
> -    -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
> -    { return ::new ((void *) l) T (std::forward<A> (a)...); }
> -
> -    template <typename T>
> -    constexpr inline void
> -    destroy_at (T *l)
> -    { l->~T (); }
> -  }
> -}
> -
> -inline void *operator new (std::size_t, void *p) noexcept
> -{ return p; }
> +#include "construct_at.h"
>   
>   constexpr bool
>   foo ()
> diff --git a/gcc/testsuite/g++.dg/cpp2a/construct_at.h b/gcc/testsuite/g++.dg/cpp2a/construct_at.h
> new file mode 100644
> index 00000000000..dcc4f5b2532
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/construct_at.h
> @@ -0,0 +1,62 @@
> +// A minimal conforming implementation of std::construct_at/destroy_at.
> +// some C++20 constexpr tests to avoid including all of <memory>.

The second line seems to be missing "Used by" or some such.

OK with that fix.

> +namespace std
> +{
> +  typedef __SIZE_TYPE__ size_t;
> +
> +  template <typename T>
> +  struct allocator
> +  {
> +    constexpr allocator () noexcept {}
> +
> +    constexpr T *allocate (size_t n)
> +    { return static_cast<T *> (::operator new (n * sizeof(T))); }
> +
> +    constexpr void
> +    deallocate (T *p, size_t n)
> +    { ::operator delete (p); }
> +  };
> +
> +  template <typename T, typename U = T &&>
> +  U __declval (int);
> +  template <typename T>
> +  T __declval (long);
> +  template <typename T>
> +  auto declval () noexcept -> decltype (__declval<T> (0));
> +
> +  template <typename T>
> +  struct remove_reference
> +  { typedef T type; };
> +  template <typename T>
> +  struct remove_reference<T &>
> +  { typedef T type; };
> +  template <typename T>
> +  struct remove_reference<T &&>
> +  { typedef T type; };
> +
> +  template <typename T>
> +  constexpr T &&
> +  forward (typename std::remove_reference<T>::type &t) noexcept
> +  { return static_cast<T&&> (t); }
> +
> +  template<typename T>
> +  constexpr T &&
> +  forward (typename std::remove_reference<T>::type &&t) noexcept
> +  { return static_cast<T&&> (t); }
> +
> +  template <typename T, typename... A>
> +  constexpr auto
> +  construct_at (T *l, A &&... a)
> +  noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
> +  -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
> +  { return ::new ((void *) l) T (std::forward<A> (a)...); }
> +
> +  template <typename T>
> +  constexpr inline void
> +  destroy_at (T *l)
> +  { l->~T (); }
> +}
> +
> +inline void *operator new (std::size_t, void *p) noexcept
> +{ return p; }
>
diff mbox series

Patch

diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index 1af365d47b9..25d84a377d8 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -5588,8 +5588,8 @@  cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
 	   argument, which has the derived type rather than the base type.  In
 	   this situation, just evaluate the initializer and return, since
 	   there's no actual data to store.  */
-	  gcc_assert (is_empty_class (TREE_TYPE (init)) && !lval);
-	  return init;
+	  gcc_assert (is_empty_class (TREE_TYPE (init)));
+	  return lval ? target : init;
 	}
       CONSTRUCTOR_ELTS (*valp) = CONSTRUCTOR_ELTS (init);
       TREE_CONSTANT (*valp) = TREE_CONSTANT (init);
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C
index fd6f6075ef0..26037397b1d 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C
@@ -1,65 +1,7 @@ 
 // PR c++/97195
 // { dg-do compile { target c++20 } }
 
-namespace std
-{
-  typedef __SIZE_TYPE__ size_t;
-
-  template <typename T>
-  struct allocator
-  {
-    constexpr allocator () noexcept {}
-
-    constexpr T *allocate (size_t n)
-    { return static_cast<T *> (::operator new (n * sizeof(T))); }
-
-    constexpr void
-    deallocate (T *p, size_t n)
-    { ::operator delete (p); }
-  };
-
-  template <typename T, typename U = T &&>
-  U __declval (int);
-  template <typename T>
-  T __declval (long);
-  template <typename T>
-  auto declval () noexcept -> decltype (__declval<T> (0));
-
-  template <typename T>
-  struct remove_reference
-  { typedef T type; };
-  template <typename T>
-  struct remove_reference<T &>
-  { typedef T type; };
-  template <typename T>
-  struct remove_reference<T &&>
-  { typedef T type; };
-
-  template <typename T>
-  constexpr T &&
-  forward (typename std::remove_reference<T>::type &t) noexcept
-  { return static_cast<T&&> (t); }
-
-  template<typename T>
-  constexpr T &&
-  forward (typename std::remove_reference<T>::type &&t) noexcept
-  { return static_cast<T&&> (t); }
-
-  template <typename T, typename... A>
-  constexpr auto
-  construct_at (T *l, A &&... a)
-  noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
-  -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
-  { return ::new ((void *) l) T (std::forward<A> (a)...); }
-
-  template <typename T>
-  constexpr inline void
-  destroy_at (T *l)
-  { l->~T (); }
-}
-
-inline void *operator new (std::size_t, void *p) noexcept
-{ return p; }
+#include "construct_at.h"
 
 constexpr bool
 foo ()
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C
new file mode 100644
index 00000000000..88bc4429a8a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C
@@ -0,0 +1,18 @@ 
+// PR c++/101663
+// { dg-do compile { target c++20 } }
+
+#include "construct_at.h"
+
+template <typename _Tp> struct __box {
+  [[no_unique_address]] _Tp _M_value;
+};
+
+struct Empty {};
+
+constexpr bool test() {
+  __box<Empty> a;
+  std::construct_at(&a._M_value);
+  return true;
+}
+
+static_assert(test());
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C
index 2bb407a4b51..eeaee969266 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C
@@ -1,65 +1,7 @@ 
 // P0784R7
 // { dg-do compile { target c++20 } }
 
-namespace std
-{
-  typedef __SIZE_TYPE__ size_t;
-
-  template <typename T>
-  struct allocator
-  {
-    constexpr allocator () noexcept {}
-
-    constexpr T *allocate (size_t n)
-    { return static_cast<T *> (::operator new (n * sizeof(T))); }
-
-    constexpr void
-    deallocate (T *p, size_t n)
-    { ::operator delete (p); }
-  };
-
-  template <typename T, typename U = T &&>
-  U __declval (int);
-  template <typename T>
-  T __declval (long);
-  template <typename T>
-  auto declval () noexcept -> decltype (__declval<T> (0));
-
-  template <typename T>
-  struct remove_reference
-  { typedef T type; };
-  template <typename T>
-  struct remove_reference<T &>
-  { typedef T type; };
-  template <typename T>
-  struct remove_reference<T &&>
-  { typedef T type; };
-
-  template <typename T>
-  constexpr T &&
-  forward (typename std::remove_reference<T>::type &t) noexcept
-  { return static_cast<T&&> (t); }
-
-  template<typename T>
-  constexpr T &&
-  forward (typename std::remove_reference<T>::type &&t) noexcept
-  { return static_cast<T&&> (t); }
-
-  template <typename T, typename... A>
-  constexpr auto
-  construct_at (T *l, A &&... a)
-  noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
-  -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
-  { return ::new ((void *) l) T (std::forward<A> (a)...); }
-
-  template <typename T>
-  constexpr inline void
-  destroy_at (T *l)
-  { l->~T (); }
-}
-
-inline void *operator new (std::size_t, void *p) noexcept
-{ return p; }
+#include "construct_at.h"
 
 constexpr bool
 foo ()
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C
index d51bdbb8269..eeaee969266 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C
@@ -1,69 +1,7 @@ 
 // P0784R7
 // { dg-do compile { target c++20 } }
 
-namespace std
-{
-  inline namespace _8 { }
-  namespace _8 {
-
-    typedef __SIZE_TYPE__ size_t;
-
-    template <typename T>
-    struct allocator
-    {
-      constexpr allocator () noexcept {}
-
-      constexpr T *allocate (size_t n)
-      { return static_cast<T *> (::operator new (n * sizeof(T))); }
-
-      constexpr void
-      deallocate (T *p, size_t n)
-      { ::operator delete (p); }
-    };
-
-    template <typename T, typename U = T &&>
-    U __declval (int);
-    template <typename T>
-    T __declval (long);
-    template <typename T>
-    auto declval () noexcept -> decltype (__declval<T> (0));
-
-    template <typename T>
-    struct remove_reference
-    { typedef T type; };
-    template <typename T>
-    struct remove_reference<T &>
-    { typedef T type; };
-    template <typename T>
-    struct remove_reference<T &&>
-    { typedef T type; };
-
-    template <typename T>
-    constexpr T &&
-    forward (typename std::remove_reference<T>::type &t) noexcept
-    { return static_cast<T&&> (t); }
-
-    template<typename T>
-    constexpr T &&
-    forward (typename std::remove_reference<T>::type &&t) noexcept
-    { return static_cast<T&&> (t); }
-
-    template <typename T, typename... A>
-    constexpr auto
-    construct_at (T *l, A &&... a)
-    noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
-    -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
-    { return ::new ((void *) l) T (std::forward<A> (a)...); }
-
-    template <typename T>
-    constexpr inline void
-    destroy_at (T *l)
-    { l->~T (); }
-  }
-}
-
-inline void *operator new (std::size_t, void *p) noexcept
-{ return p; }
+#include "construct_at.h"
 
 constexpr bool
 foo ()
diff --git a/gcc/testsuite/g++.dg/cpp2a/construct_at.h b/gcc/testsuite/g++.dg/cpp2a/construct_at.h
new file mode 100644
index 00000000000..dcc4f5b2532
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/construct_at.h
@@ -0,0 +1,62 @@ 
+// A minimal conforming implementation of std::construct_at/destroy_at.
+// some C++20 constexpr tests to avoid including all of <memory>.
+
+namespace std
+{
+  typedef __SIZE_TYPE__ size_t;
+
+  template <typename T>
+  struct allocator
+  {
+    constexpr allocator () noexcept {}
+
+    constexpr T *allocate (size_t n)
+    { return static_cast<T *> (::operator new (n * sizeof(T))); }
+
+    constexpr void
+    deallocate (T *p, size_t n)
+    { ::operator delete (p); }
+  };
+
+  template <typename T, typename U = T &&>
+  U __declval (int);
+  template <typename T>
+  T __declval (long);
+  template <typename T>
+  auto declval () noexcept -> decltype (__declval<T> (0));
+
+  template <typename T>
+  struct remove_reference
+  { typedef T type; };
+  template <typename T>
+  struct remove_reference<T &>
+  { typedef T type; };
+  template <typename T>
+  struct remove_reference<T &&>
+  { typedef T type; };
+
+  template <typename T>
+  constexpr T &&
+  forward (typename std::remove_reference<T>::type &t) noexcept
+  { return static_cast<T&&> (t); }
+
+  template<typename T>
+  constexpr T &&
+  forward (typename std::remove_reference<T>::type &&t) noexcept
+  { return static_cast<T&&> (t); }
+
+  template <typename T, typename... A>
+  constexpr auto
+  construct_at (T *l, A &&... a)
+  noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
+  -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
+  { return ::new ((void *) l) T (std::forward<A> (a)...); }
+
+  template <typename T>
+  constexpr inline void
+  destroy_at (T *l)
+  { l->~T (); }
+}
+
+inline void *operator new (std::size_t, void *p) noexcept
+{ return p; }