aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Palka <ppalka@redhat.com>2021-08-11 20:59:53 -0400
committerPatrick Palka <ppalka@redhat.com>2021-08-11 20:59:53 -0400
commit21fd62e5ca9967bba8f97fd6244a8c6a564c2146 (patch)
tree80d182ca1748b98e17fe080b965816a359436027
parent58f87503427e27bb069bd1841100f3c53440d51a (diff)
downloadgcc-21fd62e5ca9967bba8f97fd6244a8c6a564c2146.zip
gcc-21fd62e5ca9967bba8f97fd6244a8c6a564c2146.tar.gz
gcc-21fd62e5ca9967bba8f97fd6244a8c6a564c2146.tar.bz2
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.
-rw-r--r--gcc/cp/constexpr.c4
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-new14.C60
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-new20.C18
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C60
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C64
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/construct_at.h62
6 files changed, 85 insertions, 183 deletions
diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index 1af365d..25d84a3 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 fd6f607..2603739 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 0000000..88bc442
--- /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 2bb407a..eeaee96 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 d51bdbb..eeaee96 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 0000000..27e92cb
--- /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 <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; }