c++: Implement __builtin_structured_binding_size trait

clang++ apparently added a SFINAE-friendly __builtin_structured_binding_size
trait to return the structured binding size (or error if not in SFINAE
contexts if a type doesn't have a structured binding size).

The expansion statement patch already anticipated this through adding
complain argument to cp_finish_decomp.

The following patch implements it.

2025-08-15  Jakub Jelinek  <jakub@redhat.com>

gcc/
	* doc/extend.texi (Type Traits): Document
	__builtin_structured_binding_size.
gcc/cp/
	* cp-trait.def (STRUCTURED_BINDING_SIZE): New unary trait.
	* cp-tree.h (finish_structured_binding_size): Declare.
	* semantics.cc (trait_expr_value): Handle
	CPTK_STRUCTURED_BINDING_SIZE.
	(finish_structured_binding_size): New function.
	(finish_trait_expr): Handle CPTK_RANK and CPTK_TYPE_ORDER
	in the switch instead of just doing break; for those and
	ifs at the end to handle them.  Handle CPTK_STRUCTURED_BINDING_SIZE.
	* pt.cc (tsubst_expr): Likewise.
	* constraint.cc (diagnose_trait_expr): Likewise.
	* decl.cc (get_tuple_size): Use mce_true for maybe_const_value.
	(cp_decomp_size): Diagnose incomplete types not just if
	processing_template_decl, and use error_at instead of pedwarn.
	If btype is NULL, just return 0 instead of diagnosing an error.
gcc/testsuite/
	* g++.dg/cpp26/expansion-stmt15.C: Expect different diagnostics
	for zero size destructuring expansion statement.
	* g++.dg/ext/builtin-structured-binding-size1.C: New test.
	* g++.dg/ext/builtin-structured-binding-size2.C: New test.
	* g++.dg/ext/builtin-structured-binding-size3.C: New test.
	* g++.dg/ext/builtin-structured-binding-size4.C: New test.
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index cbdfafc..4b20b79 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3304,6 +3304,9 @@
     case CPTK_TYPE_ORDER:
       inform (loc, "%qT and %qT cannot be ordered", t1, t2);
       break;
+    case CPTK_STRUCTURED_BINDING_SIZE:
+      inform (loc, "%qT is not destructurable", t1);
+      break;
     case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
       inform (loc, "%qT is not a reference that binds to a temporary "
 	      "object of type %qT (direct-initialization)", t1, t2);
diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
index 9fedfd7..5e4493a 100644
--- a/gcc/cp/cp-trait.def
+++ b/gcc/cp/cp-trait.def
@@ -117,6 +117,7 @@
 DEFTRAIT_TYPE (REMOVE_EXTENT, "__remove_extent", 1)
 DEFTRAIT_TYPE (REMOVE_POINTER, "__remove_pointer", 1)
 DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
+DEFTRAIT_EXPR (STRUCTURED_BINDING_SIZE, "__builtin_structured_binding_size", 1)
 DEFTRAIT_EXPR (TYPE_ORDER, "__builtin_type_order", 2)
 DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
 DEFTRAIT_TYPE (UNDERLYING_TYPE, "__underlying_type", 1)
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 126e123..e7749fd 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8292,6 +8292,7 @@
 extern tree finish_decltype_type                (tree, bool, tsubst_flags_t);
 extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
 extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *);
+extern tree finish_structured_binding_size	(location_t, tree, tsubst_flags_t);
 extern tree finish_trait_expr			(location_t, enum cp_trait_kind, tree, tree);
 extern tree finish_trait_type			(enum cp_trait_kind, tree, tree, tsubst_flags_t);
 extern tree build_lambda_expr                   (void);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 728b61c..48547fb 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -9762,7 +9762,7 @@
   if (val == error_mark_node)
     return NULL_TREE;
   if (VAR_P (val) || TREE_CODE (val) == CONST_DECL)
-    val = maybe_constant_value (val);
+    val = maybe_constant_value (val, NULL_TREE, mce_true);
   if (TREE_CODE (val) == INTEGER_CST)
     return val;
   else
@@ -9997,11 +9997,11 @@
     }
   else if (processing_template_decl && complete_type (type) == error_mark_node)
     return -1;
-  else if (processing_template_decl && !COMPLETE_TYPE_P (type))
+  else if (!COMPLETE_TYPE_P (type))
     {
       if (complain & tf_error)
-	pedwarn (loc, 0, "structured binding refers to incomplete class type "
-			 "%qT", type);
+	error_at (loc, "structured binding refers to incomplete class type "
+		  "%qT", type);
       return -1;
     }
   else
@@ -10010,12 +10010,7 @@
       if (btype == error_mark_node)
 	return -1;
       else if (btype == NULL_TREE)
-	{
-	  if (complain & tf_error)
-	    error_at (loc, "cannot decompose class type %qT without non-static "
-			   "data members", type);
-	  return -1;
-	}
+	return 0;
       for (tree field = TYPE_FIELDS (btype); field; field = TREE_CHAIN (field))
 	if (TREE_CODE (field) != FIELD_DECL
 	    || DECL_ARTIFICIAL (field)
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 116bdac..bb2d0b4 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -22655,6 +22655,13 @@
 	  type1 = tsubst_expr (type1, args, complain, in_decl);
 	tree type2 = tsubst (TRAIT_EXPR_TYPE2 (t), args,
 			     complain, in_decl);
+	if (TRAIT_EXPR_KIND (t) == CPTK_STRUCTURED_BINDING_SIZE
+	    && type1 != error_mark_node
+	    && !processing_template_decl)
+	  /* __builtin_structured_binding_size handled separately
+	     to make it SFINAE friendly.  */
+	  RETURN (finish_structured_binding_size (TRAIT_EXPR_LOCATION (t),
+						  type1, complain));
 	RETURN (finish_trait_expr (TRAIT_EXPR_LOCATION (t),
 				   TRAIT_EXPR_KIND (t), type1, type2));
       }
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 5e73eac..26709c7 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -13710,10 +13710,11 @@
     case CPTK_IS_DEDUCIBLE:
       return type_targs_deducible_from (type1, type2);
 
-    /* __array_rank and __builtin_type_order are handled in
-       finish_trait_expr.  */
+    /* __array_rank, __builtin_type_order and __builtin_structured_binding_size
+       are handled in finish_trait_expr.  */
     case CPTK_RANK:
     case CPTK_TYPE_ORDER:
+    case CPTK_STRUCTURED_BINDING_SIZE:
       gcc_unreachable ();
 
 #define DEFTRAIT_TYPE(CODE, NAME, ARITY) \
@@ -13818,6 +13819,27 @@
 	      (non_reference (to), non_reference (from))));
 }
 
+/* Helper for finish_trait_expr and tsubst_expr.  Handle
+   CPTK_STRUCTURED_BINDING_SIZE in possibly SFINAE-friendly
+   way.  */
+
+tree
+finish_structured_binding_size (location_t loc, tree type,
+				tsubst_flags_t complain)
+{
+  if (TYPE_REF_P (type))
+    {
+      if (complain & tf_error)
+	error_at (loc, "%qs argument %qT is a reference",
+		  "__builtin_structured_binding_size", type);
+      return error_mark_node;
+    }
+  HOST_WIDE_INT ret = cp_decomp_size (loc, type, complain);
+  if (ret == -1)
+    return error_mark_node;
+  return maybe_wrap_with_location (build_int_cst (size_type_node, ret), loc);
+}
+
 /* Process a trait expression.  */
 
 tree
@@ -13830,7 +13852,7 @@
   if (processing_template_decl)
     {
       tree trait_expr = make_node (TRAIT_EXPR);
-      if (kind == CPTK_RANK)
+      if (kind == CPTK_RANK || kind == CPTK_STRUCTURED_BINDING_SIZE)
 	TREE_TYPE (trait_expr) = size_type_node;
       else if (kind == CPTK_TYPE_ORDER)
 	{
@@ -13947,10 +13969,23 @@
     case CPTK_IS_UNBOUNDED_ARRAY:
     case CPTK_IS_UNION:
     case CPTK_IS_VOLATILE:
-    case CPTK_RANK:
-    case CPTK_TYPE_ORDER:
       break;
 
+    case CPTK_RANK:
+      {
+	size_t rank = 0;
+	for (; TREE_CODE (type1) == ARRAY_TYPE; type1 = TREE_TYPE (type1))
+	  ++rank;
+	return maybe_wrap_with_location (build_int_cst (size_type_node, rank),
+					 loc);
+      }
+
+    case CPTK_TYPE_ORDER:
+      return maybe_wrap_with_location (type_order_value (type1, type2), loc);
+
+    case CPTK_STRUCTURED_BINDING_SIZE:
+      return finish_structured_binding_size (loc, type1, tf_warning_or_error);
+
     case CPTK_IS_LAYOUT_COMPATIBLE:
       if (!array_of_unknown_bound_p (type1)
 	  && TREE_CODE (type1) != VOID_TYPE
@@ -13980,20 +14015,8 @@
       gcc_unreachable ();
     }
 
-  tree val;
-  if (kind == CPTK_RANK)
-    {
-      size_t rank = 0;
-      for (; TREE_CODE (type1) == ARRAY_TYPE; type1 = TREE_TYPE (type1))
-	++rank;
-      val = build_int_cst (size_type_node, rank);
-    }
-  else if (kind == CPTK_TYPE_ORDER)
-    val = type_order_value (type1, type2);
-  else
-    val = (trait_expr_value (kind, type1, type2)
-	   ? boolean_true_node : boolean_false_node);
-
+  tree val = (trait_expr_value (kind, type1, type2)
+	      ? boolean_true_node : boolean_false_node);
   return maybe_wrap_with_location (val, loc);
 }
 
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index f644184..3b9b428 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -30846,6 +30846,12 @@
 @var{type2} refer to the same type.
 @enddefbuiltin
 
+@defbuiltin{size_t __builtin_structured_binding_size (@var{type})}
+This trait returns the structured binding size ([dcl.struct.bind])
+of @var{type}.  If a type does not have a structured binding size,
+an error is diagnosed unless it is used in SFINAE contexts.
+@enddefbuiltin
+
 
 @node Deprecated Features
 @section Deprecated Features
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt15.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt15.C
index 8f8ed75..0c69afa 100644
--- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt15.C
+++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt15.C
@@ -27,7 +27,7 @@
   int e = 42;
   d[0] = 42;
   template for (auto a : A {})		// { dg-warning "'template for' only available with" "" { target c++23_down } }
-    ;					// { dg-error "cannot decompose class type 'A' without non-static data members" "" { target *-*-* } .-1 }
+    ;					// { dg-error "empty structured binding" "" { target *-*-* } .-1 }
   template for (int b : B {})		// { dg-warning "'template for' only available with" "" { target c++23_down } }
     ;
   template for (int i : c)		// { dg-warning "'template for' only available with" "" { target c++23_down } }
diff --git a/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size1.C b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size1.C
new file mode 100644
index 0000000..736911b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size1.C
@@ -0,0 +1,56 @@
+// { dg-do compile { target c++11 } }
+
+namespace std {
+  template <typename T> struct tuple_size;
+  template <int, typename> struct tuple_element;
+}
+
+struct A { int a, b, c, d, e; };
+struct B {};
+struct C { int a, b; };
+struct D { int a, b, c; static int d; };
+struct E { int a : 1; int : 0; int : 2; int b : 1; int c : 3; int d : 4; };
+typedef float V [[gnu::vector_size (16 * sizeof (float))]];
+template <>
+struct std::tuple_size <C> { static constexpr int value = 42; };
+
+static_assert (__builtin_structured_binding_size (const A) == 5, "");
+static_assert (__is_same_as (decltype (__builtin_structured_binding_size (A)), decltype (sizeof (int))), "");
+static_assert (__builtin_structured_binding_size (B) == 0, "");
+static_assert (__builtin_structured_binding_size (C) == 42, "");
+static_assert (__builtin_structured_binding_size (A[17]) == 17, "");
+static_assert (__builtin_structured_binding_size (C[6]) == 6, "");
+static_assert (__builtin_structured_binding_size (volatile _Complex double) == 2, "");
+static_assert (__builtin_structured_binding_size (V) == 16, "");
+static_assert (__builtin_structured_binding_size (float [[gnu::vector_size (8 * sizeof (float))]]) == 8, "");
+static_assert (__builtin_structured_binding_size (D) == 3, "");
+static_assert (__builtin_structured_binding_size (E) == 4, "");
+
+struct F {
+  static short f[42];
+  static_assert (__builtin_structured_binding_size (decltype (f)) == 42, "");
+};
+
+template <typename A, typename B, typename C, typename D,
+	  typename E, typename F, typename G, typename H,
+	  typename I, typename J>
+void
+foo ()
+{
+  static_assert (__builtin_structured_binding_size (const A) == 5, "");
+  static_assert (__builtin_structured_binding_size (B) == 0, "");
+  static_assert (__builtin_structured_binding_size (C) == 42, "");
+  static_assert (__builtin_structured_binding_size (D) == 17, "");
+  static_assert (__builtin_structured_binding_size (E) == 6, "");
+  static_assert (__builtin_structured_binding_size (F) == 2, "");
+  static_assert (__builtin_structured_binding_size (volatile G) == 16, "");
+  static_assert (__builtin_structured_binding_size (H) == 8, "");
+  static_assert (__builtin_structured_binding_size (I) == 3, "");
+  static_assert (__builtin_structured_binding_size (J) == 4, "");
+}
+
+void
+bar ()
+{
+  foo <A, B, C, A[17], C[6], _Complex double, V, float [[gnu::vector_size (8 * sizeof (float))]], D, E> ();
+}
diff --git a/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size2.C b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size2.C
new file mode 100644
index 0000000..9abd80f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size2.C
@@ -0,0 +1,51 @@
+// { dg-do compile { target c++11 } }
+// { dg-options "" }
+
+namespace std {
+  template <typename T> struct tuple_size;
+  template <int, typename> struct tuple_element;
+}
+
+struct A;
+struct B { int a; };
+struct C { int a; };
+struct D { int a; };
+union E { int a; long b; };
+struct F : public B { int c; };
+struct G { struct { int d; }; int e; };
+struct H { union { int f; long g; }; int h; };
+struct I { private: int i; };
+struct J { int b; };
+struct K : public B, J {};
+template <>
+struct std::tuple_size <C> { static constexpr double value = 42; };
+template <>
+struct std::tuple_size <D> { static constexpr int value = -1; };
+int a = __builtin_structured_binding_size (A);			// { dg-error "structured binding refers to incomplete class type 'A'" }
+int b = __builtin_structured_binding_size (A &);		// { dg-error "'__builtin_structured_binding_size' argument 'A\\\&' is a reference" }
+int c = __builtin_structured_binding_size (B[]);		// { dg-error "cannot decompose array of unknown bound 'B \\\[\\\]'" }
+int d = __builtin_structured_binding_size (C);			// { dg-error "'std::tuple_size<C>::value' is not an integral constant expression" }
+int e = __builtin_structured_binding_size (D);			// { dg-error "'std::tuple_size<D>::value' is not an integral constant expression" }
+int f = __builtin_structured_binding_size (E);			// { dg-error "cannot decompose union type 'E'" }
+int g = __builtin_structured_binding_size (float);		// { dg-error "cannot decompose non-array non-class type 'float'" }
+int h = __builtin_structured_binding_size (void);		// { dg-error "cannot decompose non-array non-class type 'void'" }
+int i = __builtin_structured_binding_size (long &);		// { dg-error "'__builtin_structured_binding_size' argument 'long int\\\&' is a reference" }
+int j = __builtin_structured_binding_size (long *);		// { dg-error "cannot decompose non-array non-class type 'long int\\\*'" }
+auto k = []() {};
+int l = __builtin_structured_binding_size (decltype (k));	// { dg-error "cannot decompose lambda closure type '<lambda\\\(\\\)>'" }
+int m = __builtin_structured_binding_size (F);			// { dg-error "cannot decompose class type 'F': both it and its base class 'B' have non-static data members" }
+int n = __builtin_structured_binding_size (G);			// { dg-error "cannot decompose class type 'G' because it has an anonymous struct member" }
+int o = __builtin_structured_binding_size (H);			// { dg-error "cannot decompose class type 'H' because it has an anonymous union member" }
+int p = __builtin_structured_binding_size (I);			// { dg-error "cannot decompose inaccessible member 'I::i' of 'I'" }
+int q = __builtin_structured_binding_size (K);			// { dg-error "cannot decompose class type 'K': its base classes 'B' and 'J' have non-static data members" }
+static_assert (__builtin_structured_binding_size (int[0]) == 0);
+void foo (int r[10], int s = __builtin_structured_binding_size (decltype (r))); // { dg-error "cannot decompose non-array non-class type 'int\\\*'" }
+
+template <typename T, int N = __builtin_structured_binding_size (T)> // { dg-error "cannot decompose non-array non-class type 'int'" }
+// { dg-error "'std::tuple_size<C>::value' is not an integral constant expression" "" { target *-*-* } .-1 }
+struct L {
+  static constexpr int value = N;
+};
+L<int> l1;							// { dg-error "template argument 2 is invalid" }
+static_assert (L<B>::value == 1, "");
+L<C> l2;							// { dg-error "template argument 2 is invalid" }
diff --git a/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size3.C b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size3.C
new file mode 100644
index 0000000..19b66b4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size3.C
@@ -0,0 +1,51 @@
+// { dg-do compile { target c++11 } }
+
+namespace std {
+  template <typename T> struct tuple_size;
+  template <int, typename> struct tuple_element;
+}
+
+struct A { int a, b, c, d, e; };
+struct B {};
+struct C { int a, b; };
+typedef float V [[gnu::vector_size (16 * sizeof (float))]];
+template <>
+struct std::tuple_size <C> { static constexpr int value = 42; };
+
+int a = __builtin_structured_binding_size (const A &);		// { dg-error "'__builtin_structured_binding_size' argument 'const A\\\&' is a reference" }
+int b = __builtin_structured_binding_size (B &);		// { dg-error "'__builtin_structured_binding_size' argument 'B\\\&' is a reference" }
+int c = __builtin_structured_binding_size (C &);		// { dg-error "'__builtin_structured_binding_size' argument 'C\\\&' is a reference" }
+int d = __builtin_structured_binding_size (const A (&)[17]);	// { dg-error "'__builtin_structured_binding_size' argument 'const A \\\(\\\&\\\)\\\[17\\\]' is a reference" }
+int e = __builtin_structured_binding_size (C (&)[6]);		// { dg-error "'__builtin_structured_binding_size' argument 'C \\\(\\\&\\\)\\\[6\\\]' is a reference" }
+int f = __builtin_structured_binding_size (_Complex double &);	// { dg-error "'__builtin_structured_binding_size' argument '__complex__ double\\\&' is a reference" }
+int g = __builtin_structured_binding_size (const V &);		// { dg-error "'__builtin_structured_binding_size' argument 'const V\\\&'\[^\n\r]* is a reference" }
+int h = __builtin_structured_binding_size (float [[gnu::vector_size (8 * sizeof (float))]] &);	// { dg-error "'__builtin_structured_binding_size' argument '__vector\\\(8\\\) float\\\&' is a reference" }
+int i = __builtin_structured_binding_size (A &&);		// { dg-error "'__builtin_structured_binding_size' argument 'A\\\&\\\&' is a reference" }
+int j = __builtin_structured_binding_size (B &&);		// { dg-error "'__builtin_structured_binding_size' argument 'B\\\&\\\&' is a reference" }
+int k = __builtin_structured_binding_size (C &&);		// { dg-error "'__builtin_structured_binding_size' argument 'C\\\&\\\&' is a reference" }
+int l = __builtin_structured_binding_size (A (&&)[17]);		// { dg-error "'__builtin_structured_binding_size' argument 'A \\\(\\\&\\\&\\\)\\\[17\\\]' is a reference" }
+int m = __builtin_structured_binding_size (C (&&)[6]);		// { dg-error "'__builtin_structured_binding_size' argument 'C \\\(\\\&\\\&\\\)\\\[6\\\]' is a reference" }
+int n = __builtin_structured_binding_size (_Complex double &&);	// { dg-error "'__builtin_structured_binding_size' argument '__complex__ double\\\&\\\&' is a reference" }
+int o = __builtin_structured_binding_size (V &&);		// { dg-error "'__builtin_structured_binding_size' argument 'V\\\&\\\&'\[^\n\r]* is a reference" }
+
+template <typename A, typename B, typename C, typename D,
+	  typename E, typename F, typename G, typename H>
+void
+foo ()
+{
+  int a = __builtin_structured_binding_size (A);		// { dg-error "'__builtin_structured_binding_size' argument 'A\\\&\\\&?' is a reference" }
+  int b = __builtin_structured_binding_size (B);		// { dg-error "'__builtin_structured_binding_size' argument '(const )?B\\\&\\\&?' is a reference" }
+  int c = __builtin_structured_binding_size (C);		// { dg-error "'__builtin_structured_binding_size' argument 'C\\\&\\\&?' is a reference" }
+  int d = __builtin_structured_binding_size (D);		// { dg-error "'__builtin_structured_binding_size' argument 'A \\\(\\\&\\\&?\\\)\\\[17\\\]' is a reference" }
+  int e = __builtin_structured_binding_size (E);		// { dg-error "'__builtin_structured_binding_size' argument 'C \\\(\\\&\\\&?\\\)\\\[6\\\]' is a reference" }
+  int f = __builtin_structured_binding_size (F);		// { dg-error "'__builtin_structured_binding_size' argument '(const )?__complex__ float\\\&\\\&?' is a reference" }
+  int g = __builtin_structured_binding_size (G);		// { dg-error "'__builtin_structured_binding_size' argument '__vector\\\(16\\\) float\\\&\\\&?' is a reference" }
+  int h = __builtin_structured_binding_size (H);		// { dg-error "'__builtin_structured_binding_size' argument '__vector\\\(8\\\) float\\\&\\\&?' is a reference" }
+}
+
+void
+bar ()
+{
+  foo <A &, const B &, C &, A (&)[17], C (&)[6], const _Complex float &, V &, float [[gnu::vector_size (8 * sizeof (float))]] &> ();
+  foo <A &&, B &&, C &&, A (&&)[17], C (&&)[6], _Complex float &&, V &&, float [[gnu::vector_size (8 * sizeof (float))]] &> ();
+}
diff --git a/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size4.C b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size4.C
new file mode 100644
index 0000000..18a87e9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/builtin-structured-binding-size4.C
@@ -0,0 +1,32 @@
+// { dg-do compile { target c++20 } }
+
+namespace std {
+  template <typename T> struct tuple_size;
+  template <int, typename> struct tuple_element;
+}
+
+struct A { int a, b, c, d, e; };
+struct B {};
+struct C { int a, b; };
+struct D { int a; };
+typedef float V [[gnu::vector_size (16 * sizeof (float))]];
+template <>
+struct std::tuple_size <C> { static constexpr int value = 42; };
+template <>
+struct std::tuple_size <D> { static constexpr int value = 0; };
+
+template <typename T>
+concept is_destructurable = requires { { __builtin_structured_binding_size (T) }; };
+
+static_assert (is_destructurable <A>);
+static_assert (is_destructurable <const B>);
+static_assert (is_destructurable <C>);
+static_assert (!is_destructurable <A &>);
+static_assert (!is_destructurable <int[]>);
+static_assert (is_destructurable <int[1]>);
+static_assert (is_destructurable <A[42]>);
+static_assert (is_destructurable <float[10]>);
+static_assert (!is_destructurable <int *>);
+static_assert (is_destructurable <D volatile>);
+static_assert (is_destructurable <const D>);
+static_assert (!is_destructurable <C &&>);