From: Patrick Palka Date: Thu, 12 Aug 2021 00:59:53 +0000 (-0400) Subject: c++: constexpr std::construct_at on empty field [PR101663] X-Git-Tag: basepoints/gcc-13~5445 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=21fd62e5ca9967bba8f97fd6244a8c6a564c2146;p=thirdparty%2Fgcc.git c++: constexpr std::construct_at on empty field [PR101663] 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 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 (to indicate there's no lvalue-to-rvalue conversion). This patch makes the code path in question handle the lval=true case appropriately rather than asserting. In passing, it also consolidates the duplicate implementations of std::construct_at/destroy_at in some of the C++20 constexpr tests into a common header file. PR c++/101663 gcc/cp/ChangeLog: * constexpr.c (cxx_eval_store_expression): Handle the lval=true case in the early exit code path for empty stores with mismatched types. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/construct_at.h: New convenience header file 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. --- diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 1af365d47b93..25d84a377d8a 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 fd6f6075ef0c..26037397b1d4 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 - struct allocator - { - constexpr allocator () noexcept {} - - constexpr T *allocate (size_t n) - { return static_cast (::operator new (n * sizeof(T))); } - - constexpr void - deallocate (T *p, size_t n) - { ::operator delete (p); } - }; - - template - U __declval (int); - template - T __declval (long); - template - auto declval () noexcept -> decltype (__declval (0)); - - template - struct remove_reference - { typedef T type; }; - template - struct remove_reference - { typedef T type; }; - template - struct remove_reference - { typedef T type; }; - - template - constexpr T && - forward (typename std::remove_reference::type &t) noexcept - { return static_cast (t); } - - template - constexpr T && - forward (typename std::remove_reference::type &&t) noexcept - { return static_cast (t); } - - template - constexpr auto - construct_at (T *l, A &&... a) - noexcept (noexcept (::new ((void *) 0) T (std::declval ()...))) - -> decltype (::new ((void *) 0) T (std::declval ()...)) - { return ::new ((void *) l) T (std::forward (a)...); } - - template - 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 000000000000..88bc4429a8ad --- /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 struct __box { + [[no_unique_address]] _Tp _M_value; +}; + +struct Empty {}; + +constexpr bool test() { + __box 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 2bb407a4b517..eeaee969266a 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 - struct allocator - { - constexpr allocator () noexcept {} - - constexpr T *allocate (size_t n) - { return static_cast (::operator new (n * sizeof(T))); } - - constexpr void - deallocate (T *p, size_t n) - { ::operator delete (p); } - }; - - template - U __declval (int); - template - T __declval (long); - template - auto declval () noexcept -> decltype (__declval (0)); - - template - struct remove_reference - { typedef T type; }; - template - struct remove_reference - { typedef T type; }; - template - struct remove_reference - { typedef T type; }; - - template - constexpr T && - forward (typename std::remove_reference::type &t) noexcept - { return static_cast (t); } - - template - constexpr T && - forward (typename std::remove_reference::type &&t) noexcept - { return static_cast (t); } - - template - constexpr auto - construct_at (T *l, A &&... a) - noexcept (noexcept (::new ((void *) 0) T (std::declval ()...))) - -> decltype (::new ((void *) 0) T (std::declval ()...)) - { return ::new ((void *) l) T (std::forward (a)...); } - - template - 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 d51bdbb8269d..eeaee969266a 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 - struct allocator - { - constexpr allocator () noexcept {} - - constexpr T *allocate (size_t n) - { return static_cast (::operator new (n * sizeof(T))); } - - constexpr void - deallocate (T *p, size_t n) - { ::operator delete (p); } - }; - - template - U __declval (int); - template - T __declval (long); - template - auto declval () noexcept -> decltype (__declval (0)); - - template - struct remove_reference - { typedef T type; }; - template - struct remove_reference - { typedef T type; }; - template - struct remove_reference - { typedef T type; }; - - template - constexpr T && - forward (typename std::remove_reference::type &t) noexcept - { return static_cast (t); } - - template - constexpr T && - forward (typename std::remove_reference::type &&t) noexcept - { return static_cast (t); } - - template - constexpr auto - construct_at (T *l, A &&... a) - noexcept (noexcept (::new ((void *) 0) T (std::declval ()...))) - -> decltype (::new ((void *) 0) T (std::declval ()...)) - { return ::new ((void *) l) T (std::forward (a)...); } - - template - 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 000000000000..27e92cbb28c5 --- /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, +// used by some C++20 constexpr tests to avoid including all of . + +namespace std +{ + typedef __SIZE_TYPE__ size_t; + + template + struct allocator + { + constexpr allocator () noexcept {} + + constexpr T *allocate (size_t n) + { return static_cast (::operator new (n * sizeof(T))); } + + constexpr void + deallocate (T *p, size_t n) + { ::operator delete (p); } + }; + + template + U __declval (int); + template + T __declval (long); + template + auto declval () noexcept -> decltype (__declval (0)); + + template + struct remove_reference + { typedef T type; }; + template + struct remove_reference + { typedef T type; }; + template + struct remove_reference + { typedef T type; }; + + template + constexpr T && + forward (typename std::remove_reference::type &t) noexcept + { return static_cast (t); } + + template + constexpr T && + forward (typename std::remove_reference::type &&t) noexcept + { return static_cast (t); } + + template + constexpr auto + construct_at (T *l, A &&... a) + noexcept (noexcept (::new ((void *) 0) T (std::declval ()...))) + -> decltype (::new ((void *) 0) T (std::declval ()...)) + { return ::new ((void *) l) T (std::forward (a)...); } + + template + constexpr inline void + destroy_at (T *l) + { l->~T (); } +} + +inline void *operator new (std::size_t, void *p) noexcept +{ return p; }