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 &&>);