Hi! 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. Lightly tested so far, ok for trunk if it passes full bootstrap/regtest? 2025-08-15 Jakub Jelinek <ja...@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. --- gcc/doc/extend.texi.jj 2025-07-27 23:31:09.956003968 +0200 +++ gcc/doc/extend.texi 2025-08-15 17:30:10.251098390 +0200 @@ -30815,6 +30815,12 @@ A binary type trait: @code{true} wheneve @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 --- gcc/cp/cp-trait.def.jj 2025-07-11 19:01:25.844532378 +0200 +++ gcc/cp/cp-trait.def 2025-08-15 13:47:22.189927367 +0200 @@ -117,6 +117,7 @@ DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_c 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) --- gcc/cp/cp-tree.h.jj 2025-08-14 22:30:03.003336583 +0200 +++ gcc/cp/cp-tree.h 2025-08-15 15:29:03.477054136 +0200 @@ -8292,6 +8292,7 @@ extern void finish_static_assert 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); --- gcc/cp/semantics.cc.jj 2025-08-14 22:30:03.046336062 +0200 +++ gcc/cp/semantics.cc 2025-08-15 15:26:53.151660976 +0200 @@ -13710,10 +13710,11 @@ trait_expr_value (cp_trait_kind kind, tr 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 @@ same_type_ref_bind_p (cp_trait_kind kind (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 @@ finish_trait_expr (location_t loc, cp_tr 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,9 +13969,22 @@ finish_trait_expr (location_t loc, cp_tr case CPTK_IS_UNBOUNDED_ARRAY: case CPTK_IS_UNION: case CPTK_IS_VOLATILE: + 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: - break; + 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) @@ -13980,20 +14015,8 @@ finish_trait_expr (location_t loc, cp_tr 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); } --- gcc/cp/pt.cc.jj 2025-08-14 22:30:03.042336110 +0200 +++ gcc/cp/pt.cc 2025-08-15 15:28:24.412535782 +0200 @@ -22655,6 +22655,13 @@ tsubst_expr (tree t, tree args, tsubst_f 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)); } --- gcc/cp/constraint.cc.jj 2025-07-31 10:40:49.079391393 +0200 +++ gcc/cp/constraint.cc 2025-08-15 13:34:29.335630713 +0200 @@ -3304,6 +3304,9 @@ diagnose_trait_expr (location_t loc, tre 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); --- gcc/cp/decl.cc.jj 2025-08-14 22:28:44.046293342 +0200 +++ gcc/cp/decl.cc 2025-08-15 17:17:27.027452280 +0200 @@ -9762,7 +9762,7 @@ get_tuple_size (tree type) 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 @@ cp_decomp_size (location_t loc, tree typ } 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 @@ cp_decomp_size (location_t loc, tree typ 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) --- gcc/testsuite/g++.dg/cpp26/expansion-stmt15.C.jj 2025-08-13 22:10:18.897791624 +0200 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt15.C 2025-08-15 14:12:40.587939623 +0200 @@ -27,7 +27,7 @@ foo (int n) 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 } } --- gcc/testsuite/g++.dg/ext/builtin-structured-binding-size1.C.jj 2025-08-15 14:03:08.729069313 +0200 +++ gcc/testsuite/g++.dg/ext/builtin-structured-binding-size1.C 2025-08-15 16:39:48.351301795 +0200 @@ -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> (); +} --- gcc/testsuite/g++.dg/ext/builtin-structured-binding-size2.C.jj 2025-08-15 14:24:00.209693544 +0200 +++ gcc/testsuite/g++.dg/ext/builtin-structured-binding-size2.C 2025-08-15 16:45:43.861958765 +0200 @@ -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" } --- gcc/testsuite/g++.dg/ext/builtin-structured-binding-size3.C.jj 2025-08-15 15:18:15.694030796 +0200 +++ gcc/testsuite/g++.dg/ext/builtin-structured-binding-size3.C 2025-08-15 16:02:03.618725510 +0200 @@ -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))]] &> (); +} --- gcc/testsuite/g++.dg/ext/builtin-structured-binding-size4.C.jj 2025-08-15 16:46:08.080662895 +0200 +++ gcc/testsuite/g++.dg/ext/builtin-structured-binding-size4.C 2025-08-15 17:25:13.535736940 +0200 @@ -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 &&>); Jakub