Message ID | 20210803200401.1170877-1-ppalka@redhat.com |
---|---|
State | New |
Headers | show |
Series | c++: constexpr std::construct_at on empty field [PR101663] | expand |
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 --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; }