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

Jason Merrill jason@redhat.com
Wed Aug 11 22:47:39 GMT 2021


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; }
> 



More information about the Gcc-patches mailing list