[WIP] c++, v2: Implement C++26 P3074R7 trivial unions

Message ID aftRmNkNYV26LkJc@tucnak
State New
Headers
Series [WIP] c++, v2: Implement C++26 P3074R7 trivial unions |

Commit Message

Jakub Jelinek May 6, 2026, 2:35 p.m. UTC
  Hi!

Here is an updated patch.  It implements the P3074R7 wording without the later                                                                                                                     
removed sentence in https://eel.is/c++draft/class.mem#class.default.ctor-4                                                                                                            
and https://eel.is/c++draft/class.dtor#7.1                                                                                                                                            
                                                                                                                                                                                      
<del>X is a non-union class and </del>any <del>non-variant </del>                                                                                                                     
potentially constructed subobject <ins>S </ins>has class type M (or possibly                                                                                                          
multidimensional array thereof) where M has a destructor that is                                                                                                                      
deleted or is inaccessible from the defaulted destructor,                                                                                                                             
<ins> and either S is non-variant or S has a default member initializer,</ins>                                                                                                        
                                                                                                                                                                                      
and                                                                                                                                                                                   
                                                                                                                                                                                      
https://eel.is/c++draft/class.dtor#7.2.1                                                                                                                                              
overload resolution to select a constructor to default-initialize an object                                                                                                           
of type X either fails or selects a constructor that is either deleted or                                                                                                             
<del>not trivial</del><ins>user-provided</ins>, or                                                                                                                                    

To make this work, I had to work around it in <optional> header, where
we have user-provided union ctor and defaulted dtor.

If we change the standard further to make that valid (e.g. with the
discussed rule that if all variant subobjects have accessible destructor
which is trivial, then we don't delete the defaulted dtor just because
default ctor is user-provided, further changes will be needed.

Note, I had to do this deletion because of user-provided default ctor
in two places, because sometimes in synthesized_method_walk it is too early,
the ctors might not have been cloned at all (and looking up ctor_identifier
and trying to call it with a bool argument doesn't work).

This doesn't implement anything from P3726R2 yet (and only tested on the
changed tests and reflect/* so far).

2026-05-06  Jakub Jelinek  <jakub@redhat.com>

gcc/c-family/
	* c-cppbuiltin.cc (c_cpp_builtins): Predefine __cpp_trivial_union
	202603L for C++26.
gcc/cp/
	* method.cc (walk_field_subobs): Don't check
	default_init_uninitialized_part for variant members.  Avoid
	locate_fn_flags/process_subob_fn for variant members for default ctor 
	or, if without member initializer, for dtor too.
	(synthesized_method_walk): Make dtor deleted for C++26 if a union
	in a complete class doesn't have usable default ctor or has it
	user-provided.
	* class.cc (check_field_decl): Don't adjust
	TYPE_HAS_NONTRIVIAL_DESTRUCTOR or TYPE_HAS_COMPLEX_DFLT for variant
	members in C++26.
	(clone_constructors_and_destructors): Make defaulted dtor deleted
	for C++26 if a union doesn't have usable default ctor or has it
	user-provided.
gcc/testsuite/
	* g++.dg/DRs/dr2581-1.C (__cpp_trivial_union): Expect a warning.
	* g++.dg/DRs/dr2581-2.C (__cpp_trivial_union): Likewise.
	* g++.dg/cpp26/feat-cxx26.C (__cpp_trivial_union): New test.
	* g++.dg/cpp26/trivial-union1.C: New test.
	* g++.dg/reflect/trivial-union1.C: New test.
	* g++.dg/reflect/type_trait6.C: Change 3 static_asserts.
	* g++.dg/reflect/is_constructible_type1.C: Change 1 static_assert.
libstdc++-v3/
	* include/std/optional
	(std::_Optional_payload_base<_Tp, bool>::_Storage <_Up>): For C++26
	make ctor defaulted instead of inline user provided and add member
	initializer for _M_empty member in that case.


	Jakub
  

Patch

--- gcc/c-family/c-cppbuiltin.cc.jj	2026-05-06 10:38:27.594915980 +0200
+++ gcc/c-family/c-cppbuiltin.cc	2026-05-06 12:27:41.897327631 +0200
@@ -1122,6 +1122,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	    cpp_define (pfile, "__cpp_impl_reflection=202603L");
 	  else
 	    cpp_warn (pfile, "__cpp_impl_reflection");
+	  cpp_define (pfile, "__cpp_trivial_union=202603L");
 	}
       if (flag_concepts && cxx_dialect > cxx14)
 	cpp_define (pfile, "__cpp_concepts=202002L");
--- gcc/cp/method.cc.jj	2026-05-06 10:38:27.603915832 +0200
+++ gcc/cp/method.cc	2026-05-06 15:52:25.085718750 +0200
@@ -2771,6 +2771,7 @@  walk_field_subobs (tree fields, special_
 
 	  bad = false;
 	  if (CP_TYPE_CONST_P (mem_type)
+	      && TREE_CODE (ctx) != UNION_TYPE
 	      && default_init_uninitialized_part (mem_type))
 	    {
 	      if (diag)
@@ -2847,6 +2848,15 @@  walk_field_subobs (tree fields, special_
       else
 	argtype = NULL_TREE;
 
+      if (cxx_dialect >= cxx26 && TREE_CODE (ctx) == UNION_TYPE)
+	{
+	  if (sfk == sfk_constructor || sfk == sfk_inheriting_constructor)
+	    continue;
+
+	  if (sfk == sfk_destructor && DECL_INITIAL (field) == NULL_TREE)
+	    continue;
+	}
+
       rval = locate_fn_flags (mem_type, fnname, argtype, flags, complain);
 
       process_subob_fn (rval, sfk, spec_p, trivial_p, deleted_p,
@@ -2985,6 +2995,38 @@  synthesized_method_walk (tree ctype, spe
       /* The synthesized method will call base dtors, but check complete
 	 here to avoid having to deal with VTT.  */
       fnname = complete_dtor_identifier;
+
+      if (TREE_CODE (ctype) == UNION_TYPE
+	  && cxx_dialect >= cxx26
+	  && deleted_p
+	  /* For still incomplete types defer this until ctor cloning.  */
+	  && COMPLETE_TYPE_P (ctype))
+	{
+	  /* [class.dtor]/(7.2.1):
+	     A defaulted destructor for a class X is defined as deleted if X
+	     is a union and
+	     -- overload resolution to select a constructor to
+		default-initialize an object of type X either fails or selects
+		a constructor that is either deleted or user-provided.  */
+	  tree ctor = locate_ctor (ctype);
+	  if (ctor == NULL_TREE)
+	    {
+	      *deleted_p = true;
+	      if (diag)
+		inform (DECL_SOURCE_LOCATION (TYPE_NAME (ctype)),
+		        "default constructor of %qT unusable", ctype);
+	      return;
+	    }
+	  else if (user_provided_p (ctor))
+	    {
+	      *deleted_p = true;
+	      if (diag)
+		inform (DECL_SOURCE_LOCATION (TYPE_NAME (ctype)),
+			"default constructor of %qT is user-provided",
+			ctype);
+	      return;
+	    }
+	}
     }
   else if (SFK_ASSIGN_P (sfk))
     fnname = assign_op_identifier;
--- gcc/cp/class.cc.jj	2026-05-06 10:38:27.597915931 +0200
+++ gcc/cp/class.cc	2026-05-06 16:03:31.068876166 +0200
@@ -3903,17 +3903,25 @@  check_field_decl (tree field,
       else
 	{
 	  TYPE_NEEDS_CONSTRUCTING (t) |= TYPE_NEEDS_CONSTRUCTING (type);
-	  TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t)
-	    |= TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type);
 	  TYPE_HAS_COMPLEX_COPY_ASSIGN (t)
 	    |= (TYPE_HAS_COMPLEX_COPY_ASSIGN (type)
 		|| !TYPE_HAS_COPY_ASSIGN (type));
 	  TYPE_HAS_COMPLEX_COPY_CTOR (t) |= (TYPE_HAS_COMPLEX_COPY_CTOR (type)
 					     || !TYPE_HAS_COPY_CTOR (type));
-	  TYPE_HAS_COMPLEX_MOVE_ASSIGN (t) |= TYPE_HAS_COMPLEX_MOVE_ASSIGN (type);
+	  TYPE_HAS_COMPLEX_MOVE_ASSIGN (t)
+	    |= TYPE_HAS_COMPLEX_MOVE_ASSIGN (type);
 	  TYPE_HAS_COMPLEX_MOVE_CTOR (t) |= TYPE_HAS_COMPLEX_MOVE_CTOR (type);
-	  TYPE_HAS_COMPLEX_DFLT (t) |= (!TYPE_HAS_DEFAULT_CONSTRUCTOR (type)
-					|| TYPE_HAS_COMPLEX_DFLT (type));
+	  /* In C++26, triviality of default ctor or dtor of a variant member
+	     doesn't matter for triviality of the t's default ctor or dtor.  */
+	  if (cxx_dialect < cxx26
+	      || TREE_CODE (DECL_CONTEXT (field)) != UNION_TYPE)
+	    {
+	      TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t)
+		|= TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type);
+	      TYPE_HAS_COMPLEX_DFLT (t)
+		|= (!TYPE_HAS_DEFAULT_CONSTRUCTOR (type)
+		    || TYPE_HAS_COMPLEX_DFLT (type));
+	    }
 	}
 
       if (TYPE_HAS_COPY_CTOR (type)
@@ -5528,7 +5536,26 @@  clone_constructors_and_destructors (tree
     clone_cdtor (fn, /*update_methods=*/true);
 
   if (tree dtor = CLASSTYPE_DESTRUCTOR (t))
-    clone_cdtor (dtor, /*update_methods=*/true);
+    {
+      if (cxx_dialect >= cxx26
+	  && TREE_CODE (t) == UNION_TYPE
+	  && DECL_DEFAULTED_IN_CLASS_P (dtor)
+	  && !DECL_DELETED_FN (dtor))
+	{
+	  /* [class.dtor]/(7.2.1):
+	     A defaulted destructor for a class X is defined as deleted if X
+	     is a union and
+	     -- overload resolution to select a constructor to
+		default-initialize an object of type X either fails or selects
+		a constructor that is either deleted or user-provided.
+	     This isn't done by synthesized_method_walk for incomplete types
+	     because the default ctor is not yet cloned.  */
+	  tree ctor = locate_ctor (t);
+	  if (ctor == NULL_TREE || user_provided_p (ctor))
+	    DECL_DELETED_FN (dtor) = true;
+	}
+      clone_cdtor (dtor, /*update_methods=*/true);
+    }
 }
 
 /* Deduce noexcept for a destructor DTOR.  */
--- gcc/testsuite/g++.dg/DRs/dr2581-1.C.jj	2026-05-06 10:38:27.614915651 +0200
+++ gcc/testsuite/g++.dg/DRs/dr2581-1.C	2026-05-06 12:27:41.930583042 +0200
@@ -94,7 +94,7 @@ 
 #undef __cpp_template_parameters
 #undef __cpp_template_template_args	// { dg-warning "undefining '__cpp_template_template_args'" "" { target c++20 } }
 #undef __cpp_threadsafe_static_init	// { dg-warning "undefining '__cpp_threadsafe_static_init'" "" { target c++20 } }
-#undef __cpp_trivial_union
+#undef __cpp_trivial_union		// { dg-warning "undefining '__cpp_trivial_union'" "" { target c++26 } }
 #undef __cpp_unicode_characters		// { dg-warning "undefining '__cpp_unicode_characters'" "" { target c++20 } }
 #undef __cpp_unicode_literals		// { dg-warning "undefining '__cpp_unicode_literals'" "" { target c++20 } }
 #undef __cpp_user_defined_literals	// { dg-warning "undefining '__cpp_user_defined_literals'" "" { target c++20 } }
--- gcc/testsuite/g++.dg/DRs/dr2581-2.C.jj	2026-05-06 10:38:27.615915634 +0200
+++ gcc/testsuite/g++.dg/DRs/dr2581-2.C	2026-05-06 12:27:41.932288299 +0200
@@ -95,7 +95,7 @@ 
 #define __cpp_template_parameters 202502L
 #define __cpp_template_template_args 201611L	// { dg-error "'__cpp_template_template_args' redefined" "" { target c++20 } }
 #define __cpp_threadsafe_static_init 200806L	// { dg-error "'__cpp_threadsafe_static_init' redefined" "" { target c++20 } }
-#define __cpp_trivial_union 202502L
+#define __cpp_trivial_union 202603L		// { dg-error "'__cpp_trivial_union' redefined" "" { target c++26 } }
 #define __cpp_unicode_characters 200704L	// { dg-error "'__cpp_unicode_characters' redefined" "" { target c++17 } }
 #define __cpp_unicode_literals 200710L		// { dg-error "'__cpp_unicode_literals' redefined" "" { target c++20 } }
 #define __cpp_user_defined_literals 200809L	// { dg-error "'__cpp_user_defined_literals' redefined" "" { target c++20 } }
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2026-05-06 10:38:27.621915535 +0200
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2026-05-06 12:27:41.932699175 +0200
@@ -652,3 +652,9 @@ 
 #elif __cpp_expansion_statements != 202506
 #  error "__cpp_expansion_statements != 202506"
 #endif
+
+#ifndef __cpp_trivial_union
+#  error "__cpp_trivial_union"
+#elif __cpp_trivial_union != 202603
+#  error "__cpp_trivial_union != 202603"
+#endif
--- gcc/testsuite/g++.dg/cpp26/trivial-union1.C.jj	2026-05-06 12:27:41.932834170 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-union1.C	2026-05-06 12:27:41.932834170 +0200
@@ -0,0 +1,73 @@ 
+// P3074R7 - trivial unions (was std::uninitialized<T>)
+// P3726R2 - Adjustments to Union Lifetime Rules
+// { dg-do compile { target c++11 } }
+
+#include <type_traits>
+
+// These two were incorrectly deleted.
+union A { int a; const int b; };
+static_assert (std::is_default_constructible <A>::value, "");
+static_assert (std::is_trivially_default_constructible <A>::value, "");
+static_assert (std::is_destructible <A>::value, "");
+static_assert (std::is_trivially_destructible <A>::value, "");
+struct B { int a; union { int b; const int c; }; };
+static_assert (std::is_default_constructible <B>::value, "");
+static_assert (std::is_trivially_default_constructible <B>::value, "");
+static_assert (std::is_destructible <B>::value, "");
+static_assert (std::is_trivially_destructible <B>::value, "");
+// C::C() is incorrectly not deleted in C++11 to 23, but in C++26 it should
+// not be deleted.
+union C { const int a = 42; const long b; ~C (); };
+#if __cpp_trivial_union >= 202502L
+static_assert (std::is_default_constructible <C>::value, "");
+static_assert (!std::is_trivially_default_constructible <C>::value, "");
+static_assert (std::is_destructible <C>::value, "");
+static_assert (!std::is_trivially_destructible <C>::value, "");
+#endif
+struct D { D () = delete; D (int); ~D () = default; };
+union E { D a = 42; D b; ~E (); };
+static_assert (std::is_default_constructible <E>::value, "");
+static_assert (std::is_destructible <E>::value, "");
+struct F { int a; union { D b = 42; D c; }; };
+static_assert (std::is_default_constructible <F>::value, "");
+static_assert (std::is_destructible <F>::value, "");
+struct G { G (); ~G (); };
+union I { int a; const int b; ~I (); };
+static_assert (std::is_default_constructible <I>::value, "");
+static_assert (!std::is_trivially_default_constructible <I>::value, "");
+static_assert (std::is_destructible <I>::value, "");
+static_assert (!std::is_trivially_destructible <I>::value, "");
+union J { D a; int b; };
+#if __cpp_trivial_union >= 202502L
+static_assert (std::is_default_constructible <J>::value, "");
+static_assert (std::is_trivially_default_constructible <J>::value, "");
+#else
+static_assert (!std::is_default_constructible <J>::value, "");
+static_assert (!std::is_trivially_default_constructible <J>::value, "");
+#endif
+static_assert (std::is_destructible <J>::value, "");
+static_assert (std::is_trivially_destructible <J>::value, "");
+union K { G a; int b; };
+#if __cpp_trivial_union >= 202502L
+static_assert (std::is_default_constructible <K>::value, "");
+static_assert (std::is_trivially_default_constructible <K>::value, "");
+static_assert (std::is_destructible <K>::value, "");
+static_assert (std::is_trivially_destructible <K>::value, "");
+#else
+static_assert (!std::is_default_constructible <K>::value, "");
+static_assert (!std::is_trivially_default_constructible <K>::value, "");
+static_assert (!std::is_destructible <K>::value, "");
+static_assert (!std::is_trivially_destructible <K>::value, "");
+#endif
+struct L { int a; union { G b; int c; }; };
+#if __cpp_trivial_union >= 202502L
+static_assert (std::is_default_constructible <L>::value, "");
+static_assert (std::is_trivially_default_constructible <L>::value, "");
+static_assert (std::is_destructible <L>::value, "");
+static_assert (std::is_trivially_destructible <L>::value, "");
+#else
+static_assert (!std::is_default_constructible <L>::value, "");
+static_assert (!std::is_trivially_default_constructible <L>::value, "");
+static_assert (!std::is_destructible <L>::value, "");
+static_assert (!std::is_trivially_destructible <L>::value, "");
+#endif
--- gcc/testsuite/g++.dg/reflect/trivial-union1.C.jj	2026-05-06 12:27:41.933015207 +0200
+++ gcc/testsuite/g++.dg/reflect/trivial-union1.C	2026-05-06 16:12:08.912460173 +0200
@@ -0,0 +1,108 @@ 
+// P3074R7 - trivial unions (was std::uninitialized<T>)
+// P3726R2 - Adjustments to Union Lifetime Rules
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+#include <ranges>
+
+using namespace std::meta;
+constexpr auto ctx = std::meta::access_context::unchecked ();
+union A { int a; const int b; };
+static_assert (is_default_constructible_type (^^A));
+static_assert (is_trivially_default_constructible_type (^^A));
+static_assert (is_destructible_type (^^A));
+static_assert (is_trivially_destructible_type (^^A));
+constexpr auto Actor = (members_of (^^A, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Adtor = (members_of (^^A, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Actor) && !is_deleted (Actor));
+static_assert (is_defaulted (Adtor) && !is_deleted (Adtor));
+struct B { int a; union { int b; const int c; }; };
+static_assert (is_default_constructible_type (^^B));
+static_assert (is_trivially_default_constructible_type (^^B));
+static_assert (is_destructible_type (^^B));
+static_assert (is_trivially_destructible_type (^^B));
+constexpr auto Bctor = (members_of (^^B, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Bdtor = (members_of (^^B, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Bctor) && !is_deleted (Bctor));
+static_assert (is_defaulted (Bdtor) && !is_deleted (Bdtor));
+union C { const int a = 42; const long b; ~C (); };
+static_assert (is_default_constructible_type (^^C));
+static_assert (!is_trivially_default_constructible_type (^^C));
+static_assert (is_destructible_type (^^C));
+static_assert (!is_trivially_destructible_type (^^C));
+constexpr auto Cctor = (members_of (^^C, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Cdtor = (members_of (^^C, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Cctor) && !is_deleted (Cctor));
+static_assert (!is_defaulted (Cdtor) && !is_deleted (Cdtor));
+struct D { D () = delete; D (int); ~D () = default; };
+union E { D a = 42; D b; ~E (); };
+static_assert (is_default_constructible_type (^^E));
+static_assert (is_destructible_type (^^E));
+constexpr auto Ector = (members_of (^^E, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Edtor = (members_of (^^E, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Ector) && !is_deleted (Ector));
+static_assert (!is_defaulted (Edtor) && !is_deleted (Edtor));
+struct F { int a; union { D b = 42; D c; }; };
+static_assert (is_default_constructible_type (^^F));
+static_assert (is_destructible_type (^^F));
+constexpr auto Fctor = (members_of (^^F, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Fdtor = (members_of (^^F, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Fctor) && !is_deleted (Fctor));
+static_assert (is_defaulted (Fdtor) && !is_deleted (Fdtor));
+struct G { G (); ~G (); };
+union I { int a; const int b; ~I (); };
+static_assert (is_default_constructible_type (^^I));
+static_assert (!is_trivially_default_constructible_type (^^I));
+static_assert (is_destructible_type (^^I));
+static_assert (!is_trivially_destructible_type (^^I));
+constexpr auto Ictor = (members_of (^^I, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Idtor = (members_of (^^I, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Ictor) && !is_deleted (Ictor));
+static_assert (!is_defaulted (Idtor) && !is_deleted (Idtor));
+union J { D a; int b; };
+static_assert (is_default_constructible_type (^^J));
+static_assert (is_trivially_default_constructible_type (^^J));
+static_assert (is_destructible_type (^^J));
+static_assert (is_trivially_destructible_type (^^J));
+constexpr auto Jctor = (members_of (^^J, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Jdtor = (members_of (^^J, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Jctor) && !is_deleted (Jctor));
+static_assert (is_defaulted (Jdtor) && !is_deleted (Jdtor));
+union K { G a; int b; };
+static_assert (is_default_constructible_type (^^K));
+static_assert (is_trivially_default_constructible_type (^^K));
+static_assert (is_destructible_type (^^K));
+static_assert (is_trivially_destructible_type (^^K));
+constexpr auto Kctor = (members_of (^^K, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Kdtor = (members_of (^^K, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Kctor) && !is_deleted (Kctor));
+static_assert (is_defaulted (Kdtor) && !is_deleted (Kdtor));
+struct L { int a; union { G b; int c; }; };
+static_assert (is_default_constructible_type (^^L));
+static_assert (is_trivially_default_constructible_type (^^L));
+static_assert (is_destructible_type (^^L));
+static_assert (is_trivially_destructible_type (^^L));
+constexpr auto Lctor = (members_of (^^L, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Ldtor = (members_of (^^L, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (is_defaulted (Lctor) && !is_deleted (Lctor));
+static_assert (is_defaulted (Ldtor) && !is_deleted (Ldtor));
+union M { M (); int a; long b; };
+static_assert (!is_default_constructible_type (^^M));
+static_assert (!is_trivially_default_constructible_type (^^M));
+static_assert (!is_destructible_type (^^M));
+static_assert (!is_trivially_destructible_type (^^M));
+constexpr auto Mctor = (members_of (^^M, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Mdtor = (members_of (^^M, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (!is_defaulted (Mctor) && !is_deleted (Mctor));
+static_assert (is_defaulted (Mdtor) && is_deleted (Mdtor));
+struct N { N () = default; N (const N &) = default; int a; ~N (); };
+union O { O (); int a; N b; };
+static_assert (!is_default_constructible_type (^^O));
+static_assert (!is_trivially_default_constructible_type (^^O));
+static_assert (!is_destructible_type (^^O));
+static_assert (!is_trivially_destructible_type (^^O));
+constexpr auto Octor = (members_of (^^O, ctx) | std::views::filter (is_default_constructor) | std::ranges::to <std::vector> ())[0];
+constexpr auto Odtor = (members_of (^^O, ctx) | std::views::filter (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (!is_defaulted (Octor) && !is_deleted (Octor));
+static_assert (is_defaulted (Odtor) && is_deleted (Odtor));
--- gcc/testsuite/g++.dg/reflect/type_trait6.C.jj	2026-03-27 10:17:16.145297923 +0100
+++ gcc/testsuite/g++.dg/reflect/type_trait6.C	2026-05-06 16:15:31.957157388 +0200
@@ -985,7 +985,7 @@  static_assert (!is_destructible_type (^^
 static_assert (!is_destructible_type (^^const N2::Del [1]));
 static_assert (!is_destructible_type (^^N2::Del []));
 static_assert (!is_destructible_type (^^const N2::Del []));
-static_assert (!is_destructible_type (^^N2::NontrivialUnion));
+static_assert (is_destructible_type (^^N2::NontrivialUnion));
 static_assert (is_destructible_type (^^N2::UnusualCopy));
 
 static_assert (is_trivially_default_constructible_type (^^int));
@@ -1367,8 +1367,8 @@  static_assert (!is_nothrow_destructible_
 static_assert (!is_nothrow_destructible_type (^^N2::Aggr2));
 static_assert (!is_nothrow_destructible_type (^^N2::Aggr2 [1]));
 static_assert (!is_nothrow_destructible_type (^^N2::TD1 [1][2]));
-static_assert (!is_nothrow_destructible_type (^^N2::Ut));
-static_assert (!is_nothrow_destructible_type (^^N2::Ut [3]));
+static_assert (is_nothrow_destructible_type (^^N2::Ut));
+static_assert (is_nothrow_destructible_type (^^N2::Ut [3]));
 static_assert (!is_nothrow_destructible_type (^^N2::AbstractDelDtor));
 static_assert (!is_nothrow_destructible_type (^^N2::Abstract2));
 static_assert (!is_nothrow_destructible_type (^^N2::Abstract3));
--- gcc/testsuite/g++.dg/reflect/is_constructible_type1.C.jj	2026-03-27 10:17:16.128298201 +0100
+++ gcc/testsuite/g++.dg/reflect/is_constructible_type1.C	2026-05-06 16:15:59.526708933 +0200
@@ -603,7 +603,7 @@  static_assert (!is_constructible_type (^
 static_assert (!is_constructible_type (^^const DelnAny, { ^^int, ^^void * }));
 static_assert (!is_constructible_type (^^DelnAny, { ^^Empty, ^^B, ^^D }));
 static_assert (!is_constructible_type (^^const DelnAny, { ^^Empty, ^^B, ^^D }));
-static_assert (!is_constructible_type (^^NontrivialUnion, {}));
+static_assert (is_constructible_type (^^NontrivialUnion, {}));
 static_assert (!is_constructible_type (^^NontrivialUnion, { ^^const NontrivialUnion & }));
 static_assert (!is_constructible_type (^^UnusualCopy, {}));
 static_assert (!is_constructible_type (^^UnusualCopy, { ^^UnusualCopy }));
--- libstdc++-v3/include/std/optional.jj	2026-05-06 10:38:27.623915503 +0200
+++ libstdc++-v3/include/std/optional	2026-05-06 12:27:41.933204607 +0200
@@ -221,7 +221,11 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Up, bool = is_trivially_destructible_v<_Up>>
 	union _Storage
 	{
+#if __cpp_trivial_union >= 202502L
+	  constexpr _Storage() noexcept = default;
+#else
 	  constexpr _Storage() noexcept : _M_empty() { }
+#endif
 
 	  template<typename... _Args>
 	    constexpr
@@ -258,7 +262,11 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  _Storage& operator=(_Storage&&) = default;
 #endif
 
+#if __cpp_trivial_union >= 202502L
+	  _Empty_byte _M_empty = {};
+#else
 	  _Empty_byte _M_empty;
+#endif
 	  _Up _M_value;
 	};