From: feedable <[email protected]>
This patch rewrites old unexpandable pack detection logic based on
current lambda to a one based on scopes.
The new logic tries to answer the following question: "Will the soonest
potential pack expansion have a pack that is declared within its own
pattern?". There aren't a lot of scopes over which we can directly
expand (only lambda and stmt-expr). We can take advantage of that and
assert that any expansion that hasn't occured yet must occur beyond that
scope, so any pack that's declared within that scope, cannot be expanded
over, as it would be within the pattern itself.
For example:
struct X { int x,y,z; };
template<class ...T>
void foo() {
([]{auto [...xs] = X{}; xs + T{};}, ...);
}
When `check_for_bare_parameter_packs` is called for the expr-stmt
`xs + T{};`, any possible expansion over that pack will contain the
lambda-scope, and as `xs` is declared within that scope, it will also be
contained within the pattern of that expansion, while `T` is declared
outside of that scope, so there are expansions which can expand it
without containing its declaration within the pattern.
Also, do actually check for bare parameter packs in the final statement
of a stmt-expr.
PR c++/126124
gcc/cp/ChangeLog:
* pt.cc (nearest_expandable_scope): New.
(filter_packs): New.
(check_for_bare_parameter_packs): Use scopes for bare pack
detection.
* semantics.cc (finish_stmt_expr_expr): Check for bare packs.
(finish_member_declaration): Skip for lambda fields.
gcc/testsuite/ChangeLog:
* g++.dg/cpp26/decomp33.C: New test.
* g++.dg/cpp2a/lambda-generic-variadic-22.C: New test.
* g++.dg/expr/stmt-expr-2.C: New test.
---
This patch rewrites old unexpandable pack detection logic based on
current lambda to a one based on scopes.
The new logic tries to answer the following question: "Will the soonest
potential pack expansion have a pack that is declared within its own
pattern?". There aren't a lot of scopes over which we can directly
expand (only lambda and stmt-expr). We can take advantage of that and
assert that any expansion that hasn't occured yet must occur beyond that
scope, so any pack that's declared within that scope, cannot be expanded
over, as it would be within the pattern itself.
For example:
struct X { int x,y,z; };
template<class ...T>
void foo() {
([]{auto [...xs] = X{}; xs + T{};}, ...);
}
When `check_for_bare_parameter_packs` is called for the expr-stmt
`xs + T{};`, any possible expansion over that pack will contain the
lambda-scope, and as `xs` is declared within that scope, it will also be
contained within the pattern of that expansion, while `T` is declared
outside of that scope, so there are expansions which can expand it
without containing its declaration within the pattern.
Also, do actually check for bare parameter packs in the final statement
of a stmt-expr.
PR c++/126124
gcc/cp/ChangeLog:
* pt.cc (nearest_expandable_scope): New.
(filter_packs): New.
(check_for_bare_parameter_packs): Use scopes for bare pack
detection.
* semantics.cc (finish_stmt_expr_expr): Check for bare packs.
(finish_member_declaration): Skip for lambda fields.
gcc/testsuite/ChangeLog:
* g++.dg/cpp26/decomp33.C: New test.
* g++.dg/cpp2a/lambda-generic-variadic-22.C: New test.
* g++.dg/expr/stmt-expr-2.C: New test.
Changes since v1:
- Removed `constexpr` from the variable for the lambda in
`filter_packs`.
Changes since v2:
- Rephrased the comment in `finish_stmt_expr_expr`.
- Skipped bare param pack check for lambda fields.
- Tweaked loop in `filter_packs`.
This is a forge pull request published on the gcc-patches mailing list mailing
list
as requested by feedable via Sourceware Forge
<[email protected]>.
Forge discussion: https://forge.sourceware.org/gcc/gcc/pulls/213
Get it locally using:
```
git fetch forge-upstream "+refs/versioned_pull/213/*:refs/versioned_pull/213/*"
git switch -c "pr-213-v3" "refs/versioned_pull/213/3/head"
```
Or, download the patch at: https://forge.sourceware.org/gcc/gcc/pulls/213.diff
Created on: 2026-08-08 16:43:31+00:00
Latest update: 2026-10-06 16:13:52+00:00
Changes: 5 changed files, 197 additions, 12 deletions
Head revision: feedable/gcc-TEST ref pr126124 commit
56c40064a048cf2a1eb379f9b0cabbd36241ceb8
Base revision: gcc/gcc ref trunk commit
8a312e7a448a81d5447729c2743ec7b69b581402 r17-4935-g8a312e7a448a81
Merge base: 8a312e7a448a81d5447729c2743ec7b69b581402
Requested Reviewers:
Changed files:
- A: gcc/testsuite/g++.dg/cpp26/decomp33.C
- A: gcc/testsuite/g++.dg/cpp2a/lambda-generic-variadic-22.C
- A: gcc/testsuite/g++.dg/expr/stmt-expr-2.C
- M: gcc/cp/pt.cc
- M: gcc/cp/semantics.cc
gcc/cp/pt.cc | 97 ++++++++++++++++---
gcc/cp/semantics.cc | 8 +-
gcc/testsuite/g++.dg/cpp26/decomp33.C | 18 ++++
.../g++.dg/cpp2a/lambda-generic-variadic-22.C | 26 +++++
gcc/testsuite/g++.dg/expr/stmt-expr-2.C | 60 ++++++++++++
5 files changed, 197 insertions(+), 12 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp26/decomp33.C
create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-generic-variadic-22.C
create mode 100644 gcc/testsuite/g++.dg/expr/stmt-expr-2.C
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 2b68ceb0cd23d..1e042ce8788eb 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -4498,6 +4498,89 @@ make_pack_index (tree pack, tree index)
return t;
}
+/* Return the nearest scope which could be part of an outer pack expansion,
+ or NULL if there is no such scope. Packs from outside this scope are
+ ignored by check_for_bare_parameter_packs.
+ For example:
+
+ template<class ...U>
+ void foo ()
+ {
+ ([]<class ...T>{T{} + U{};}, ...);
+ }
+
+ When check_for_bare_parameter_packs is called on the full-expr `T{} + U{}`,
+ the nearest expandable scope is the lambda scope, so the pack `T`, which
+ resides within that scope, must be a part of the pattern, and therefore
+ should be rejected, while the pack `U`, which resides outside the lambda
+ scope, can still be expanded by the fold-expr later on, so it should be
+ allowed. */
+static cp_binding_level *
+nearest_expandable_scope ()
+{
+ cp_binding_level *it = current_binding_level;
+
+ while (true)
+ {
+ /* These scopes are expandable, so packs beyond these scopes can still
+ be expanded later. */
+ if (it->kind == sk_lambda || it->kind == sk_stmt_expr)
+ break;
+ /* Nothing can possibly expand any pack from this point on. */
+ if (it->kind == sk_namespace)
+ return NULL;
+ /* This is a normal scope. It can be contained within an expandable one
+ to be expanded over, so we have to keep searching. */
+ it = it->level_chain;
+ }
+ /* We need to adjust the lambda scope to include its template params, since
+ these also can't be expanded at this point. */
+ if (it->kind == sk_lambda && it->level_chain->kind == sk_template_parms)
+ it = it->level_chain;
+ return it;
+}
+
+/* Scan the bindings for packs in PACKS that are declared within the pattern,
+ and remove the rest. DISREGARD_SINCE is the nearest expandable scope. */
+static tree
+filter_packs (cp_binding_level *disregard_since, tree packs)
+{
+ if (!disregard_since)
+ return packs;
+
+ auto to_decl = [](tree pack)
+ {
+ if (TREE_CODE (pack) == TEMPLATE_TYPE_PARM)
+ pack = TEMPLATE_TYPE_DECL (pack);
+ if (TREE_CODE (pack) == TEMPLATE_PARM_INDEX)
+ pack = TEMPLATE_PARM_DECL (pack);
+ if (is_capture_proxy (pack) && !DECL_PACK_P (pack))
+ pack = DECL_CAPTURED_VARIABLE (pack);
+ return pack;
+ };
+
+ hash_set<tree> expandable_packs;
+ for (tree it = packs; it; it = TREE_CHAIN (it))
+ expandable_packs.add (to_decl (TREE_VALUE (it)));
+
+ for (cp_binding_level *it = current_binding_level; ; it = it->level_chain)
+ {
+ for (tree n = it->names; n; n = TREE_CHAIN (n))
+ if (expandable_packs.contains (n))
+ expandable_packs.remove (n);
+ if (it == disregard_since)
+ break;
+ }
+
+ tree head = NULL_TREE, *tail = &head;
+ for (tree it = packs; it; it = TREE_CHAIN (it))
+ if (!expandable_packs.contains (to_decl (TREE_VALUE (it))))
+ *tail = it, tail = &TREE_CHAIN (*tail);
+ *tail = NULL_TREE;
+
+ return head;
+}
+
/* Checks T for any "bare" parameter packs, which have not yet been
expanded, and issues an error if any are found. This operation can
only be done on full expressions or types (e.g., an expression
@@ -4549,17 +4632,9 @@ check_for_bare_parameter_packs (tree t, location_t loc
/* = UNKNOWN_LOCATION */)
return true;
}
- if (lam && CLASSTYPE_TEMPLATE_INFO (lam))
- for (; parameter_packs;
- parameter_packs = TREE_CHAIN (parameter_packs))
- {
- tree pack = TREE_VALUE (parameter_packs);
- if (is_capture_proxy (pack)
- || (TREE_CODE (pack) == PARM_DECL
- && DECL_CONTEXT (pack)
- && DECL_CONTEXT (DECL_CONTEXT (pack)) == lam))
- break;
- }
+ cp_binding_level *scope = nearest_expandable_scope ();
+
+ parameter_packs = filter_packs (scope, parameter_packs);
if (parameter_packs)
{
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index f9f05eb092344..013ce496ced97 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -3146,6 +3146,12 @@ finish_stmt_expr_expr (tree expr, tree stmt_expr)
add_stmt (expr);
}
+ if (check_for_bare_parameter_packs (expr))
+ {
+ expr = error_mark_node;
+ type = error_mark_node;
+ }
+
/* The type of the statement-expression is the type of the last
expression. */
TREE_TYPE (stmt_expr) = type;
@@ -4415,7 +4421,7 @@ finish_member_declaration (tree decl)
/* Check for bare parameter packs in the non-static data member
declaration. */
- if (TREE_CODE (decl) == FIELD_DECL)
+ if (TREE_CODE (decl) == FIELD_DECL && !LAMBDA_TYPE_P (DECL_CONTEXT (decl)))
{
if (check_for_bare_parameter_packs (TREE_TYPE (decl)))
TREE_TYPE (decl) = error_mark_node;
diff --git a/gcc/testsuite/g++.dg/cpp26/decomp33.C
b/gcc/testsuite/g++.dg/cpp26/decomp33.C
new file mode 100644
index 0000000000000..938e39dd30bf7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/decomp33.C
@@ -0,0 +1,18 @@
+// PR c++/126124
+// { dg-do compile { target c++26 } }
+// { dg-options "" }
+
+struct x { int x, y, z; };
+template<int=0> void test1() {
+ (({auto [...xs] = x{}; xs;}), ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+}
+template<int=0> void test2() {
+ ([]{auto [...xs] = x{}; xs;}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+}
+
+auto p1 = test1;
+auto p2 = test2;
\ No newline at end of file
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-generic-variadic-22.C
b/gcc/testsuite/g++.dg/cpp2a/lambda-generic-variadic-22.C
new file mode 100644
index 0000000000000..886784586f9db
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/lambda-generic-variadic-22.C
@@ -0,0 +1,26 @@
+// PR c++/126124
+// { dg-do compile { target c++20 } }
+
+void f() {
+ ([]<int ...xs>{xs;}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+ ([]<class ...xs>{xs{};}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+ ([](auto ...xs){xs;}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+}
+template<class ...xs>
+void g() {
+ ([]{xs{};}, ...);
+}
+template<int ...xs>
+void g() {
+ ([]{xs;}, ...);
+}
+void g(auto ...xs) {
+ ([xs]{xs;}, ...);
+ ([ys=xs]{ys;}, ...);
+}
\ No newline at end of file
diff --git a/gcc/testsuite/g++.dg/expr/stmt-expr-2.C
b/gcc/testsuite/g++.dg/expr/stmt-expr-2.C
new file mode 100644
index 0000000000000..bfe2a9d117732
--- /dev/null
+++ b/gcc/testsuite/g++.dg/expr/stmt-expr-2.C
@@ -0,0 +1,60 @@
+// PR c++/126124
+// { dg-do compile { target c++17 } }
+// { dg-options "" }
+
+void f() {
+ [](auto ...xs) {(({ return xs; 0; }), ...);};
+ ([](auto ...xs) {({ return xs; 0; });}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+ [](auto ...xs) {(({ return xs; }), ...);};
+ ([](auto ...xs) {({ return xs; });}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+ []<int ...xs> {(({ return xs; 0; }), ...);};
+ ([]<int ...xs> {({ return xs; 0; });}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+ []<int ...xs> {(({ return xs; }), ...);};
+ ([]<int ...xs> {({ return xs; });}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+ []<class ...xs> {(({ return xs{}; 0; }), ...);};
+ ([]<class ...xs> {({ return xs{}; 0; });}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+ []<class ...xs> {(({ return xs{}; }), ...);};
+ ([]<class ...xs> {({ return xs{}; });}, ...);
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ // { dg-error " has no unexpanded parameter packs" "" { target *-*-* } .-2 }
+}
+
+template<class ...Ts>
+int g() {
+ (({ return Ts{}; 0; }), ...);
+ ({ return Ts{}; 0; });
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ (({ return Ts{}; }), ...);
+ ({ return Ts{}; });
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+}
+
+template<int ...xs>
+int g() {
+ (({ return xs; 0; }), ...);
+ ({ return xs; 0; });
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ (({ return xs; }), ...);
+ ({ return xs; });
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+}
+
+template<class ...Ts>
+int g(Ts ...xs) {
+ (({ return xs; 0; }), ...);
+ ({ return xs; 0; });
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+ (({ return xs; }), ...);
+ ({ return xs; });
+ // { dg-error "parameter packs not expanded with" "" { target *-*-* } .-1 }
+}
\ No newline at end of file
Range-diff against v2:
1: 66cadb90dd113 ! 1: 56c40064a048c c++: Detect unexpandable packs based on
scope
@@ Commit message
(check_for_bare_parameter_packs): Use scopes for bare pack
detection.
* semantics.cc (finish_stmt_expr_expr): Check for bare packs.
+ (finish_member_declaration): Skip for lambda fields.
gcc/testsuite/ChangeLog:
- * g++.dg/cpp26/decomp32.C: New test.
+ * g++.dg/cpp26/decomp33.C: New test.
* g++.dg/cpp2a/lambda-generic-variadic-22.C: New test.
* g++.dg/expr/stmt-expr-2.C: New test.
@@ gcc/cp/pt.cc: make_pack_index (tree pack, tree index)
return t;
}
-+/* Determine the nearest scope below which packs will be a part of the
pattern.
-+ Return NULL if no pack can be expanded without being part of the
pattern.
++/* Return the nearest scope which could be part of an outer pack
expansion,
++ or NULL if there is no such scope. Packs from outside this scope are
++ ignored by check_for_bare_parameter_packs.
+ For example:
+
+ template<class ...U>
@@ gcc/cp/pt.cc: make_pack_index (tree pack, tree index)
+{
+ cp_binding_level *it = current_binding_level;
+
-+ /* If we are inside a lambda-introducer, we may still be expanded over.
*/
-+ tree lambda = current_lambda_expr ();
-+ if (it->kind == sk_class && lambda && TREE_TYPE (lambda) ==
it->this_entity)
-+ return it;
-+
+ while (true)
+ {
+ /* These scopes are expandable, so packs beyond these scopes can
still
@@ gcc/cp/pt.cc: make_pack_index (tree pack, tree index)
+ break;
+ }
+
-+ for (; packs && expandable_packs.contains (to_decl (TREE_VALUE
(packs)));
-+ packs = TREE_CHAIN (packs))
-+ ;
-+ if (!packs) return packs;
-+ tree new_tail = packs;
-+ for (tree tail = TREE_CHAIN (packs); tail; tail = TREE_CHAIN (tail))
-+ if (!expandable_packs.contains (TREE_VALUE (tail)))
-+ {
-+ TREE_CHAIN (new_tail) = tail;
-+ new_tail = tail;
-+ }
-+ return packs;
++ tree head = NULL_TREE, *tail = &head;
++ for (tree it = packs; it; it = TREE_CHAIN (it))
++ if (!expandable_packs.contains (to_decl (TREE_VALUE (it))))
++ *tail = it, tail = &TREE_CHAIN (*tail);
++ *tail = NULL_TREE;
++
++ return head;
+}
+
/* Checks T for any "bare" parameter packs, which have not yet been
@@ gcc/cp/semantics.cc: finish_stmt_expr_expr (tree expr, tree stmt_expr)
/* The type of the statement-expression is the type of the last
expression. */
TREE_TYPE (stmt_expr) = type;
+@@ gcc/cp/semantics.cc: finish_member_declaration (tree decl)
+
+ /* Check for bare parameter packs in the non-static data member
+ declaration. */
+- if (TREE_CODE (decl) == FIELD_DECL)
++ if (TREE_CODE (decl) == FIELD_DECL && !LAMBDA_TYPE_P (DECL_CONTEXT
(decl)))
+ {
+ if (check_for_bare_parameter_packs (TREE_TYPE (decl)))
+ TREE_TYPE (decl) = error_mark_node;
- ## gcc/testsuite/g++.dg/cpp26/decomp32.C (new) ##
+ ## gcc/testsuite/g++.dg/cpp26/decomp33.C (new) ##
@@
+// PR c++/126124
+// { dg-do compile { target c++26 } }
--
2.54.0