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.
(cherry picked from commit
21fd62e5ca9967bba8f97fd6244a8c6a564c2146)
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);
// 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 ()
--- /dev/null
+// 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());
// 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 ()
// 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 ()
--- /dev/null
+// 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; }