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.
gcc/testsuite/ChangeLog:
* g++.dg/cpp26/decomp32.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.
gcc/testsuite/ChangeLog:
* g++.dg/cpp26/decomp31.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`.
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-v2" "refs/versioned_pull/213/2/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-08-10 15:29:53+00:00
Changes: 5 changed files, 205 additions, 11 deletions
Head revision: feedable/gcc-TEST ref pr126124 commit
66cadb90dd11330a04d1801f1a3597eba719129f
Base revision: gcc/gcc ref trunk commit
2791799fe11f886736869e3aff093e0c14fb10b8 r17-3183-g2791799fe11f88
Merge base: 2791799fe11f886736869e3aff093e0c14fb10b8
Requested Reviewers:
Changed files:
- A: gcc/testsuite/g++.dg/cpp26/decomp32.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 | 106 ++++++++++++++++--
gcc/cp/semantics.cc | 6 +
gcc/testsuite/g++.dg/cpp26/decomp32.C | 18 +++
.../g++.dg/cpp2a/lambda-generic-variadic-22.C | 26 +++++
gcc/testsuite/g++.dg/expr/stmt-expr-2.C | 60 ++++++++++
5 files changed, 205 insertions(+), 11 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp26/decomp32.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 ab78bbb87c0ae..0f8b2fb43089b 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -4495,6 +4495,98 @@ 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.
+ 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;
+
+ /* 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;