c++: template depth of lambda in default targ [PR116567]
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_gcc_check--master-arm |
success
|
Test passed
|
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 |
success
|
Test passed
|
Commit Message
Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
for trunk/14?
-- >8 --
A lambda within a default template argument used in some template-id
may have a smaller template depth than the context of the template-id.
For example, the lambda in v1's default template argument has template
depth 1, and in v2's has template depth 2, but the template-ids v1<0>
and v2<0> which uses these default arguments appear in a depth 3 template
context. So add_extra_args will ultimately return args with depth 3 --
too many args for the lambda, leading to a bogus substitution.
This patch fixes this by trimming the result of add_extra_args to match
the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH field
is added that tracks the template-ness of a lambda;
PR c++/116567
gcc/cp/ChangeLog:
* pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
trim the augmented template arguments to match the template depth
of the lambda.
gcc/testsuite/ChangeLog:
* g++.dg/cpp2a/lambda-targ7.C: New test.
---
gcc/cp/pt.cc | 11 +++++++++
gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30 +++++++++++++++++++++++
2 files changed, 41 insertions(+)
create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
Comments
On Thu, 5 Sep 2024, Patrick Palka wrote:
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> for trunk/14?
>
> -- >8 --
>
> A lambda within a default template argument used in some template-id
> may have a smaller template depth than the context of the template-id.
> For example, the lambda in v1's default template argument has template
> depth 1, and in v2's has template depth 2, but the template-ids v1<0>
> and v2<0> which uses these default arguments appear in a depth 3 template
> context. So add_extra_args will ultimately return args with depth 3 --
> too many args for the lambda, leading to a bogus substitution.
>
> This patch fixes this by trimming the result of add_extra_args to match
> the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH field
> is added that tracks the template-ness of a lambda;
Oops, disregard this last sentence, it's a vestige from an earlier
version of this patch before it occurred to me we can freely obtain the
template depth of a lambda through its operator()'s TEMPLATE_DECL.
>
> PR c++/116567
>
> gcc/cp/ChangeLog:
>
> * pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
> trim the augmented template arguments to match the template depth
> of the lambda.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/cpp2a/lambda-targ7.C: New test.
> ---
> gcc/cp/pt.cc | 11 +++++++++
> gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30 +++++++++++++++++++++++
> 2 files changed, 41 insertions(+)
> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
>
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 747e627f547..c49a26b4f5e 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
> LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
> return t;
> }
> + if (LAMBDA_EXPR_EXTRA_ARGS (t))
> + {
> + /* If we deferred substitution into this lambda, then it's probably from
> + a context (e.g. default template argument context) which may have fewer
> + levels than the current context it's embedded in. Adjust the result of
> + add_extra_args accordingly. */
> + tree ctx_parms = DECL_TEMPLATE_PARMS (DECL_TI_TEMPLATE (oldfn));
> + if (generic_lambda_fn_p (oldfn))
> + ctx_parms = TREE_CHAIN (ctx_parms);
> + args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (ctx_parms));
> + }
>
> tree r = build_lambda_expr ();
>
> diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> new file mode 100644
> index 00000000000..c5c0525908e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> @@ -0,0 +1,30 @@
> +// PR c++/116567
> +// { dg-do compile { target c++20 } }
> +
> +template<auto N, auto F = []{}>
> +bool v1 = true;
> +
> +template<auto N, auto F = [](auto){}>
> +bool v1g = true;
> +
> +template<class T>
> +struct A {
> + template<auto N, auto F = []{}>
> + static inline bool v2 = true;
> +
> + template<auto N, auto F = [](auto){}>
> + static inline bool v2g = true;
> +
> + template<class U>
> + struct B {
> + template<class V>
> + static void f() {
> + v1<0> && v1g<0>;
> + v2<0> && v2g<0>;
> + }
> + };
> +};
> +
> +auto main() -> int {
> + A<int>::B<int>::f<int>();
> +}
> --
> 2.46.0.519.g2e7b89e038
>
>
On 9/5/24 10:54 AM, Patrick Palka wrote:
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> for trunk/14?
>
> -- >8 --
>
> A lambda within a default template argument used in some template-id
> may have a smaller template depth than the context of the template-id.
> For example, the lambda in v1's default template argument has template
> depth 1, and in v2's has template depth 2, but the template-ids v1<0>
> and v2<0> which uses these default arguments appear in a depth 3 template
> context. So add_extra_args will ultimately return args with depth 3 --
> too many args for the lambda, leading to a bogus substitution.
>
> This patch fixes this by trimming the result of add_extra_args to match
> the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH field
> is added that tracks the template-ness of a lambda;
>
> PR c++/116567
>
> gcc/cp/ChangeLog:
>
> * pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
> trim the augmented template arguments to match the template depth
> of the lambda.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/cpp2a/lambda-targ7.C: New test.
> ---
> gcc/cp/pt.cc | 11 +++++++++
> gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30 +++++++++++++++++++++++
> 2 files changed, 41 insertions(+)
> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
>
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 747e627f547..c49a26b4f5e 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
> LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
> return t;
> }
> + if (LAMBDA_EXPR_EXTRA_ARGS (t))
> + {
> + /* If we deferred substitution into this lambda, then it's probably from
"probably" seems wrong, given that it wasn't implemented for this case.
> + a context (e.g. default template argument context) which may have fewer
> + levels than the current context it's embedded in. Adjust the result of
> + add_extra_args accordingly. */
Hmm, this looks like a situation of not just fewer levels, but
potentially unrelated levels. "args" here is for f, which shares no
template context with v1. What happens if your templates have non-type
template parameters?
> + tree ctx_parms = DECL_TEMPLATE_PARMS (DECL_TI_TEMPLATE (oldfn));
> + if (generic_lambda_fn_p (oldfn))
> + ctx_parms = TREE_CHAIN (ctx_parms);
> + args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (ctx_parms));
> + }
>
> tree r = build_lambda_expr ();
>
> diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> new file mode 100644
> index 00000000000..c5c0525908e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> @@ -0,0 +1,30 @@
> +// PR c++/116567
> +// { dg-do compile { target c++20 } }
> +
> +template<auto N, auto F = []{}>
> +bool v1 = true;
> +
> +template<auto N, auto F = [](auto){}>
> +bool v1g = true;
> +
> +template<class T>
> +struct A {
> + template<auto N, auto F = []{}>
> + static inline bool v2 = true;
> +
> + template<auto N, auto F = [](auto){}>
> + static inline bool v2g = true;
> +
> + template<class U>
> + struct B {
> + template<class V>
> + static void f() {
> + v1<0> && v1g<0>;
> + v2<0> && v2g<0>;
> + }
> + };
> +};
> +
> +auto main() -> int {
> + A<int>::B<int>::f<int>();
> +}
On Thu, 5 Sep 2024, Jason Merrill wrote:
> On 9/5/24 10:54 AM, Patrick Palka wrote:
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> > for trunk/14?
> >
> > -- >8 --
> >
> > A lambda within a default template argument used in some template-id
> > may have a smaller template depth than the context of the template-id.
> > For example, the lambda in v1's default template argument has template
> > depth 1, and in v2's has template depth 2, but the template-ids v1<0>
> > and v2<0> which uses these default arguments appear in a depth 3 template
> > context. So add_extra_args will ultimately return args with depth 3 --
> > too many args for the lambda, leading to a bogus substitution.
> >
> > This patch fixes this by trimming the result of add_extra_args to match
> > the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH field
> > is added that tracks the template-ness of a lambda;
> >
> > PR c++/116567
> >
> > gcc/cp/ChangeLog:
> >
> > * pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
> > trim the augmented template arguments to match the template depth
> > of the lambda.
> >
> > gcc/testsuite/ChangeLog:
> >
> > * g++.dg/cpp2a/lambda-targ7.C: New test.
> > ---
> > gcc/cp/pt.cc | 11 +++++++++
> > gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30 +++++++++++++++++++++++
> > 2 files changed, 41 insertions(+)
> > create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> >
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 747e627f547..c49a26b4f5e 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args,
> > tsubst_flags_t complain, tree in_decl)
> > LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
> > return t;
> > }
> > + if (LAMBDA_EXPR_EXTRA_ARGS (t))
> > + {
> > + /* If we deferred substitution into this lambda, then it's probably
> > from
>
> "probably" seems wrong, given that it wasn't implemented for this case.
I said "probably" because in e.g.
template<int N, auto F = []{}>
bool b = true;
template<class T>
void f() {
b<0>;
}
the lambda context has the same depth as the template-id context. But
as you point out, the issue is ultimately related vs unrelated
parameters rather than depth.
>
> > + a context (e.g. default template argument context) which may have
> > fewer
> > + levels than the current context it's embedded in. Adjust the result
> > of
> > + add_extra_args accordingly. */
>
> Hmm, this looks like a situation of not just fewer levels, but potentially
> unrelated levels. "args" here is for f, which shares no template context with
> v1. What happens if your templates have non-type template parameters?
Indeed before add_extra_args 'args' will be unrelated, but after doing
add_extra_args the innermost levels of 'args' will correspond to the
lambda's template context, and so using get_innermost_template_args
ought to get rid of the unrelated arguments, keeping only the ones
relevant to the original lambda context.
Here's v2 which clarifies the comment to talk about related parameters
rather than differing depth, and extends the testcase to invoke
the lambda etc.
-- >8 --
Subject: [PATCH] c++: template depth of lambda in default targ [PR116567]
PR c++/116567
gcc/cp/ChangeLog:
* pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
trim the augmented template arguments to match the template depth
of the lambda.
gcc/testsuite/ChangeLog:
* g++.dg/cpp2a/lambda-targ7.C: New test.
---
gcc/cp/pt.cc | 12 +++++++
gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 42 +++++++++++++++++++++++
2 files changed, 54 insertions(+)
create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 747e627f547..e6c10d5bd20 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -19699,6 +19699,18 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
return t;
}
+ if (LAMBDA_EXPR_EXTRA_ARGS (t))
+ {
+ /* If we deferred substitution into this lambda, then its original
+ context (e.g. default template argument context) might be unrelated
+ to the current context it's embedded in. After add_extra_args though,
+ the innermost levels of 'args' will correspond to the lambda context,
+ so get rid of all unrelated levels. */
+ tree ctx_parms = DECL_TEMPLATE_PARMS (DECL_TI_TEMPLATE (oldfn));
+ if (generic_lambda_fn_p (oldfn))
+ ctx_parms = TREE_CHAIN (ctx_parms);
+ args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (ctx_parms));
+ }
tree r = build_lambda_expr ();
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
new file mode 100644
index 00000000000..97b42d6ed0c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
@@ -0,0 +1,42 @@
+// PR c++/116567
+// { dg-do compile { target c++20 } }
+
+struct X { int n; };
+
+template<auto N, auto F = []{ return N; }>
+auto v1 = F;
+
+template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+auto v1g = F;
+
+template<class T>
+struct A {
+ template<auto N, auto F = []{ return N; }>
+ static inline auto v2 = F;
+
+ template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+ static inline auto v2g = F;
+
+ template<class U>
+ struct B {
+ template<auto N, auto F = []{ return N; }>
+ static inline auto v3 = F;
+
+ template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+ static inline auto v3g = F;
+
+ template<class V>
+ static void f() {
+ static_assert(v1<X{1}>().n == 1);
+ static_assert(v1g<X{1}>(42).n == 1 + 42);
+ static_assert(v2<X{2}>().n == 2);
+ static_assert(v2g<X{2}>(42).n == 2 + 42);
+ static_assert(v3<X{3}>().n == 3);
+ static_assert(v3g<X{3}>(42).n == 3 + 42);
+ }
+ };
+};
+
+int main() {
+ A<int>::B<int>::f<int>();
+}
On 9/5/24 1:26 PM, Patrick Palka wrote:
> On Thu, 5 Sep 2024, Jason Merrill wrote:
>
>> On 9/5/24 10:54 AM, Patrick Palka wrote:
>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
>>> for trunk/14?
>>>
>>> -- >8 --
>>>
>>> A lambda within a default template argument used in some template-id
>>> may have a smaller template depth than the context of the template-id.
>>> For example, the lambda in v1's default template argument has template
>>> depth 1, and in v2's has template depth 2, but the template-ids v1<0>
>>> and v2<0> which uses these default arguments appear in a depth 3 template
>>> context. So add_extra_args will ultimately return args with depth 3 --
>>> too many args for the lambda, leading to a bogus substitution.
>>>
>>> This patch fixes this by trimming the result of add_extra_args to match
>>> the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH field
>>> is added that tracks the template-ness of a lambda;
>>>
>>> PR c++/116567
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> * pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
>>> trim the augmented template arguments to match the template depth
>>> of the lambda.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> * g++.dg/cpp2a/lambda-targ7.C: New test.
>>> ---
>>> gcc/cp/pt.cc | 11 +++++++++
>>> gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30 +++++++++++++++++++++++
>>> 2 files changed, 41 insertions(+)
>>> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
>>>
>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>> index 747e627f547..c49a26b4f5e 100644
>>> --- a/gcc/cp/pt.cc
>>> +++ b/gcc/cp/pt.cc
>>> @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args,
>>> tsubst_flags_t complain, tree in_decl)
>>> LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
>>> return t;
>>> }
>>> + if (LAMBDA_EXPR_EXTRA_ARGS (t))
>>> + {
>>> + /* If we deferred substitution into this lambda, then it's probably
>>> from
>>
>> "probably" seems wrong, given that it wasn't implemented for this case.
>
> I said "probably" because in e.g.
>
> template<int N, auto F = []{}>
> bool b = true;
>
> template<class T>
> void f() {
> b<0>;
> }
>
> the lambda context has the same depth as the template-id context. But
> as you point out, the issue is ultimately related vs unrelated
> parameters rather than depth.
>
>>
>>> + a context (e.g. default template argument context) which may have
>>> fewer
>>> + levels than the current context it's embedded in. Adjust the result
>>> of
>>> + add_extra_args accordingly. */
>>
>> Hmm, this looks like a situation of not just fewer levels, but potentially
>> unrelated levels. "args" here is for f, which shares no template context with
>> v1. What happens if your templates have non-type template parameters?
>
> Indeed before add_extra_args 'args' will be unrelated, but after doing
> add_extra_args the innermost levels of 'args' will correspond to the
> lambda's template context, and so using get_innermost_template_args
> ought to get rid of the unrelated arguments, keeping only the ones
> relevant to the original lambda context.
Will they? The original function of add_extra_args was to reintroduce
outer args that we weren't able to substitute the last time through
tsubst_lambda_expr. I expect the innermost levels of 'args' to be the
same before and after.
Hmm, looking at add_extra_args again, I see that whether the EXTRA_ARGS
go on the outside or the inside depends on whether they're dependent.
How does this work other than by accident? >.>
Jason
On Thu, 5 Sep 2024, Jason Merrill wrote:
> On 9/5/24 1:26 PM, Patrick Palka wrote:
> > On Thu, 5 Sep 2024, Jason Merrill wrote:
> >
> > > On 9/5/24 10:54 AM, Patrick Palka wrote:
> > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> > > > for trunk/14?
> > > >
> > > > -- >8 --
> > > >
> > > > A lambda within a default template argument used in some template-id
> > > > may have a smaller template depth than the context of the template-id.
> > > > For example, the lambda in v1's default template argument has template
> > > > depth 1, and in v2's has template depth 2, but the template-ids v1<0>
> > > > and v2<0> which uses these default arguments appear in a depth 3
> > > > template
> > > > context. So add_extra_args will ultimately return args with depth 3 --
> > > > too many args for the lambda, leading to a bogus substitution.
> > > >
> > > > This patch fixes this by trimming the result of add_extra_args to match
> > > > the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH
> > > > field
> > > > is added that tracks the template-ness of a lambda;
> > > >
> > > > PR c++/116567
> > > >
> > > > gcc/cp/ChangeLog:
> > > >
> > > > * pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
> > > > trim the augmented template arguments to match the template depth
> > > > of the lambda.
> > > >
> > > > gcc/testsuite/ChangeLog:
> > > >
> > > > * g++.dg/cpp2a/lambda-targ7.C: New test.
> > > > ---
> > > > gcc/cp/pt.cc | 11 +++++++++
> > > > gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30
> > > > +++++++++++++++++++++++
> > > > 2 files changed, 41 insertions(+)
> > > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> > > >
> > > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > > > index 747e627f547..c49a26b4f5e 100644
> > > > --- a/gcc/cp/pt.cc
> > > > +++ b/gcc/cp/pt.cc
> > > > @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args,
> > > > tsubst_flags_t complain, tree in_decl)
> > > > LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
> > > > complain);
> > > > return t;
> > > > }
> > > > + if (LAMBDA_EXPR_EXTRA_ARGS (t))
> > > > + {
> > > > + /* If we deferred substitution into this lambda, then it's
> > > > probably
> > > > from
> > >
> > > "probably" seems wrong, given that it wasn't implemented for this case.
> >
> > I said "probably" because in e.g.
> >
> > template<int N, auto F = []{}>
> > bool b = true;
> >
> > template<class T>
> > void f() {
> > b<0>;
> > }
> >
> > the lambda context has the same depth as the template-id context. But
> > as you point out, the issue is ultimately related vs unrelated
> > parameters rather than depth.
> >
> > >
> > > > + a context (e.g. default template argument context) which may have
> > > > fewer
> > > > + levels than the current context it's embedded in. Adjust the result
> > > > of
> > > > + add_extra_args accordingly. */
> > >
> > > Hmm, this looks like a situation of not just fewer levels, but potentially
> > > unrelated levels. "args" here is for f, which shares no template context
> > > with
> > > v1. What happens if your templates have non-type template parameters?
> >
> > Indeed before add_extra_args 'args' will be unrelated, but after doing
> > add_extra_args the innermost levels of 'args' will correspond to the
> > lambda's template context, and so using get_innermost_template_args
> > ought to get rid of the unrelated arguments, keeping only the ones
> > relevant to the original lambda context.
>
> Will they? The original function of add_extra_args was to reintroduce outer
> args that we weren't able to substitute the last time through
> tsubst_lambda_expr. I expect the innermost levels of 'args' to be the same
> before and after.
>
> Hmm, looking at add_extra_args again, I see that whether the EXTRA_ARGS go on
> the outside or the inside depends on whether they're dependent. How does this
> work other than by accident? >.>
It's kind of a happy accident indeed :P In the cases this patch is
concerned with, i.e. a template-id using a default argument containing
a lambda, the extra args will always be considered dependent because
during default template argument coercion we substitute an incomplete
set of targs into the default targ (namely the element corresponding to
the default targ is NULL_TREE), which any_dependent_template_arguments_p
considers dependent. So add_extra_args will reliably put these captured
extra args as the innermost!
>
> Jason
>
>
On 9/5/24 2:28 PM, Patrick Palka wrote:
> On Thu, 5 Sep 2024, Jason Merrill wrote:
>
>> On 9/5/24 1:26 PM, Patrick Palka wrote:
>>> On Thu, 5 Sep 2024, Jason Merrill wrote:
>>>
>>>> On 9/5/24 10:54 AM, Patrick Palka wrote:
>>>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
>>>>> for trunk/14?
>>>>>
>>>>> -- >8 --
>>>>>
>>>>> A lambda within a default template argument used in some template-id
>>>>> may have a smaller template depth than the context of the template-id.
>>>>> For example, the lambda in v1's default template argument has template
>>>>> depth 1, and in v2's has template depth 2, but the template-ids v1<0>
>>>>> and v2<0> which uses these default arguments appear in a depth 3
>>>>> template
>>>>> context. So add_extra_args will ultimately return args with depth 3 --
>>>>> too many args for the lambda, leading to a bogus substitution.
>>>>>
>>>>> This patch fixes this by trimming the result of add_extra_args to match
>>>>> the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH
>>>>> field
>>>>> is added that tracks the template-ness of a lambda;
>>>>>
>>>>> PR c++/116567
>>>>>
>>>>> gcc/cp/ChangeLog:
>>>>>
>>>>> * pt.cc (tsubst_lambda_expr): For a deferred-substitution lambda,
>>>>> trim the augmented template arguments to match the template depth
>>>>> of the lambda.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>> * g++.dg/cpp2a/lambda-targ7.C: New test.
>>>>> ---
>>>>> gcc/cp/pt.cc | 11 +++++++++
>>>>> gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30
>>>>> +++++++++++++++++++++++
>>>>> 2 files changed, 41 insertions(+)
>>>>> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
>>>>>
>>>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>>>> index 747e627f547..c49a26b4f5e 100644
>>>>> --- a/gcc/cp/pt.cc
>>>>> +++ b/gcc/cp/pt.cc
>>>>> @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args,
>>>>> tsubst_flags_t complain, tree in_decl)
>>>>> LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
>>>>> complain);
>>>>> return t;
>>>>> }
>>>>> + if (LAMBDA_EXPR_EXTRA_ARGS (t))
>>>>> + {
>>>>> + /* If we deferred substitution into this lambda, then it's
>>>>> probably
>>>>> from
>>>>
>>>> "probably" seems wrong, given that it wasn't implemented for this case.
>>>
>>> I said "probably" because in e.g.
>>>
>>> template<int N, auto F = []{}>
>>> bool b = true;
>>>
>>> template<class T>
>>> void f() {
>>> b<0>;
>>> }
>>>
>>> the lambda context has the same depth as the template-id context. But
>>> as you point out, the issue is ultimately related vs unrelated
>>> parameters rather than depth.
>>>
>>>>
>>>>> + a context (e.g. default template argument context) which may have
>>>>> fewer
>>>>> + levels than the current context it's embedded in. Adjust the result
>>>>> of
>>>>> + add_extra_args accordingly. */
>>>>
>>>> Hmm, this looks like a situation of not just fewer levels, but potentially
>>>> unrelated levels. "args" here is for f, which shares no template context
>>>> with
>>>> v1. What happens if your templates have non-type template parameters?
>>>
>>> Indeed before add_extra_args 'args' will be unrelated, but after doing
>>> add_extra_args the innermost levels of 'args' will correspond to the
>>> lambda's template context, and so using get_innermost_template_args
>>> ought to get rid of the unrelated arguments, keeping only the ones
>>> relevant to the original lambda context.
>>
>> Will they? The original function of add_extra_args was to reintroduce outer
>> args that we weren't able to substitute the last time through
>> tsubst_lambda_expr. I expect the innermost levels of 'args' to be the same
>> before and after.
>>
>> Hmm, looking at add_extra_args again, I see that whether the EXTRA_ARGS go on
>> the outside or the inside depends on whether they're dependent. How does this
>> work other than by accident? >.>
>
> It's kind of a happy accident indeed :P In the cases this patch is
> concerned with, i.e. a template-id using a default argument containing
> a lambda, the extra args will always be considered dependent because
> during default template argument coercion we substitute an incomplete
> set of targs into the default targ (namely the element corresponding to
> the default targ is NULL_TREE), which any_dependent_template_arguments_p
> considers dependent. So add_extra_args will reliably put these captured
> extra args as the innermost!
I see that the testcases you enabled this code to handle in
r12-2222-g2c699fd29829cd were also about lambda/requires in default
template arguments. Can we detect this case some other way than
uses_template_parms?
Can we do the pruning added by this patch in add_extra_args instead of
its caller?
Incidentally, why is having extra outer levels causing trouble? I
thought we were generally able to safely ignore them.
Jason
On Thu, 5 Sep 2024, Jason Merrill wrote:
> On 9/5/24 2:28 PM, Patrick Palka wrote:
> > On Thu, 5 Sep 2024, Jason Merrill wrote:
> >
> > > On 9/5/24 1:26 PM, Patrick Palka wrote:
> > > > On Thu, 5 Sep 2024, Jason Merrill wrote:
> > > >
> > > > > On 9/5/24 10:54 AM, Patrick Palka wrote:
> > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> > > > > > for trunk/14?
> > > > > >
> > > > > > -- >8 --
> > > > > >
> > > > > > A lambda within a default template argument used in some template-id
> > > > > > may have a smaller template depth than the context of the
> > > > > > template-id.
> > > > > > For example, the lambda in v1's default template argument has
> > > > > > template
> > > > > > depth 1, and in v2's has template depth 2, but the template-ids
> > > > > > v1<0>
> > > > > > and v2<0> which uses these default arguments appear in a depth 3
> > > > > > template
> > > > > > context. So add_extra_args will ultimately return args with depth 3
> > > > > > --
> > > > > > too many args for the lambda, leading to a bogus substitution.
> > > > > >
> > > > > > This patch fixes this by trimming the result of add_extra_args to
> > > > > > match
> > > > > > the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH
> > > > > > field
> > > > > > is added that tracks the template-ness of a lambda;
> > > > > >
> > > > > > PR c++/116567
> > > > > >
> > > > > > gcc/cp/ChangeLog:
> > > > > >
> > > > > > * pt.cc (tsubst_lambda_expr): For a deferred-substitution
> > > > > > lambda,
> > > > > > trim the augmented template arguments to match the template
> > > > > > depth
> > > > > > of the lambda.
> > > > > >
> > > > > > gcc/testsuite/ChangeLog:
> > > > > >
> > > > > > * g++.dg/cpp2a/lambda-targ7.C: New test.
> > > > > > ---
> > > > > > gcc/cp/pt.cc | 11 +++++++++
> > > > > > gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30
> > > > > > +++++++++++++++++++++++
> > > > > > 2 files changed, 41 insertions(+)
> > > > > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
> > > > > >
> > > > > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > > > > > index 747e627f547..c49a26b4f5e 100644
> > > > > > --- a/gcc/cp/pt.cc
> > > > > > +++ b/gcc/cp/pt.cc
> > > > > > @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args,
> > > > > > tsubst_flags_t complain, tree in_decl)
> > > > > > LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
> > > > > > complain);
> > > > > > return t;
> > > > > > }
> > > > > > + if (LAMBDA_EXPR_EXTRA_ARGS (t))
> > > > > > + {
> > > > > > + /* If we deferred substitution into this lambda, then it's
> > > > > > probably
> > > > > > from
> > > > >
> > > > > "probably" seems wrong, given that it wasn't implemented for this
> > > > > case.
> > > >
> > > > I said "probably" because in e.g.
> > > >
> > > > template<int N, auto F = []{}>
> > > > bool b = true;
> > > >
> > > > template<class T>
> > > > void f() {
> > > > b<0>;
> > > > }
> > > >
> > > > the lambda context has the same depth as the template-id context. But
> > > > as you point out, the issue is ultimately related vs unrelated
> > > > parameters rather than depth.
> > > >
> > > > >
> > > > > > + a context (e.g. default template argument context) which may
> > > > > > have
> > > > > > fewer
> > > > > > + levels than the current context it's embedded in. Adjust the
> > > > > > result
> > > > > > of
> > > > > > + add_extra_args accordingly. */
> > > > >
> > > > > Hmm, this looks like a situation of not just fewer levels, but
> > > > > potentially
> > > > > unrelated levels. "args" here is for f, which shares no template
> > > > > context
> > > > > with
> > > > > v1. What happens if your templates have non-type template parameters?
> > > >
> > > > Indeed before add_extra_args 'args' will be unrelated, but after doing
> > > > add_extra_args the innermost levels of 'args' will correspond to the
> > > > lambda's template context, and so using get_innermost_template_args
> > > > ought to get rid of the unrelated arguments, keeping only the ones
> > > > relevant to the original lambda context.
> > >
> > > Will they? The original function of add_extra_args was to reintroduce
> > > outer
> > > args that we weren't able to substitute the last time through
> > > tsubst_lambda_expr. I expect the innermost levels of 'args' to be the
> > > same
> > > before and after.
> > >
> > > Hmm, looking at add_extra_args again, I see that whether the EXTRA_ARGS go
> > > on
> > > the outside or the inside depends on whether they're dependent. How does
> > > this
> > > work other than by accident? >.>
> >
> > It's kind of a happy accident indeed :P In the cases this patch is
> > concerned with, i.e. a template-id using a default argument containing
> > a lambda, the extra args will always be considered dependent because
> > during default template argument coercion we substitute an incomplete
> > set of targs into the default targ (namely the element corresponding to
> > the default targ is NULL_TREE), which any_dependent_template_arguments_p
> > considers dependent. So add_extra_args will reliably put these captured
> > extra args as the innermost!
>
> I see that the testcases you enabled this code to handle in
> r12-2222-g2c699fd29829cd were also about lambda/requires in default template
> arguments. Can we detect this case some other way than uses_template_parms?
I think during default template argument coercion we need to set
tf_partial when in a template context. Then build_extra_args should
remember if this flag was set. Then add_extra_args should merge the
extra arguments according to whether the flag was set during deferring.
>
> Can we do the pruning added by this patch in add_extra_args instead of its
> caller?
If we're only concerned with the default template argument situation, then
it seems 'extra' will always be a full set of template arguments (even when
the template-id names a template from the current instantiation) so I think
add_extra_args just needs to substitute into 'extra' and not do any additional
pruning.
>
> Incidentally, why is having extra outer levels causing trouble? I thought we
> were generally able to safely ignore them.
I thought extra _inner_ levels are safe to ignore generally? If we have
extra outer levels then it causes the inner args to no longer be at the
right level.
For
template<auto N, auto = [] {}>
bool flag = true;
template<typename>
struct core {
template<typename>
auto func() -> bool {
return flag<0>;
}
};
auto main() -> int {
core<int> _;
[[maybe_unused]] bool flag = _.func<int>();
}
we crash during substitution into lambda's 'auto' return type which has
level 2, and 'args' after add_extra_args is {{int},{0, <empty>}}, so
we see non-type argument at level 2 index 0 where a type is expected.
(And if the lambda used N, that substitution would be similarly wrong
as well.)
How does the following look? Bootstrapped and regtested on
x86_64-pc-linux-gnu. This would be too risky to backport, so for 14 I
think we should go with the original patch.
-- >8 --
Subject: [PATCH] c++: deferring partial substitution into lambda [PR116567]
PR c++/116567
gcc/cp/ChangeLog:
* pt.cc (coerce_template_parms): Pass tf_partial when substituting
into a default template argument in a template context.
(build_extra_args): Set TREE_STATIC on the extra args if this is
a partial substitution.
(add_extra_args): Handle deferred partial substitution.
(tsubst_lammda_expr): Check for tf_partial instead of dependence
of args when determining whether to defer substitution.
(tsubst_expr) <case LAMBDA_EXPR>: Remove tf_partial early exit.
gcc/testsuite/ChangeLog:
* g++.dg/cpp2a/lambda-targ7.C: New test.
---
gcc/cp/pt.cc | 33 +++++++++---------
gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 42 +++++++++++++++++++++++
2 files changed, 58 insertions(+), 17 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 9195a5274e1..9a9c16a47c2 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -9326,7 +9326,9 @@ coerce_template_parms (tree parms,
{
/* There must be a default arg in this case. */
arg = tsubst_template_arg (TREE_PURPOSE (parm), new_args,
- complain, in_decl);
+ complain | (processing_template_decl
+ ? tf_partial : tf_none),
+ in_decl);
/* The position of the first default template argument,
is also the number of non-defaulted arguments in NEW_INNER_ARGS.
Record that. */
@@ -13572,6 +13574,9 @@ build_extra_args (tree pattern, tree args, tsubst_flags_t complain)
if (local_specializations)
if (tree locals = extract_local_specs (pattern, complain))
extra = tree_cons (NULL_TREE, extra, locals);
+ if (complain & tf_partial)
+ /* Remember whether this is a partial substitution. */
+ TREE_STATIC (extra) = true;
return extra;
}
@@ -13581,7 +13586,10 @@ build_extra_args (tree pattern, tree args, tsubst_flags_t complain)
tree
add_extra_args (tree extra, tree args, tsubst_flags_t complain, tree in_decl)
{
- if (extra && TREE_CODE (extra) == TREE_LIST)
+ if (!extra)
+ return args;
+
+ if (TREE_CODE (extra) == TREE_LIST)
{
for (tree elt = TREE_CHAIN (extra); elt; elt = TREE_CHAIN (elt))
{
@@ -13599,13 +13607,11 @@ add_extra_args (tree extra, tree args, tsubst_flags_t complain, tree in_decl)
gcc_assert (!TREE_PURPOSE (extra));
extra = TREE_VALUE (extra);
}
- if (uses_template_parms (extra))
- {
- /* This can happen after dependent substitution into a
- requires-expr or a lambda that uses constexpr if. */
- extra = tsubst_template_args (extra, args, complain, in_decl);
- args = add_outermost_template_args (args, extra);
- }
+ if (TREE_STATIC (extra))
+ /* This is a partial substitution into e.g. a requires-expr or lambda
+ inside a default template argument; we expect 'extra' to be a full
+ set of template arguments for the template context. */
+ args = tsubst_template_args (extra, args, complain, in_decl);
else
args = add_to_template_args (extra, args);
return args;
@@ -19706,7 +19712,7 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
args = add_extra_args (LAMBDA_EXPR_EXTRA_ARGS (t), args, complain, in_decl);
if (processing_template_decl
- && (!in_template_context || any_dependent_template_arguments_p (args)))
+ && (!in_template_context || (complain & tf_partial)))
{
/* Defer templated substitution into a lambda-expr if we lost the
necessary template context. This may happen for a lambda-expr
@@ -21827,13 +21833,6 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
case LAMBDA_EXPR:
{
- if (complain & tf_partial)
- {
- /* We don't have a full set of template arguments yet; don't touch
- the lambda at all. */
- gcc_assert (processing_template_decl);
- return t;
- }
tree r = tsubst_lambda_expr (t, args, complain, in_decl);
RETURN (build_lambda_object (r));
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
new file mode 100644
index 00000000000..97b42d6ed0c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
@@ -0,0 +1,42 @@
+// PR c++/116567
+// { dg-do compile { target c++20 } }
+
+struct X { int n; };
+
+template<auto N, auto F = []{ return N; }>
+auto v1 = F;
+
+template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+auto v1g = F;
+
+template<class T>
+struct A {
+ template<auto N, auto F = []{ return N; }>
+ static inline auto v2 = F;
+
+ template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+ static inline auto v2g = F;
+
+ template<class U>
+ struct B {
+ template<auto N, auto F = []{ return N; }>
+ static inline auto v3 = F;
+
+ template<auto N, auto F = [](auto y){ return decltype(N){N.n + y}; }>
+ static inline auto v3g = F;
+
+ template<class V>
+ static void f() {
+ static_assert(v1<X{1}>().n == 1);
+ static_assert(v1g<X{1}>(42).n == 1 + 42);
+ static_assert(v2<X{2}>().n == 2);
+ static_assert(v2g<X{2}>(42).n == 2 + 42);
+ static_assert(v3<X{3}>().n == 3);
+ static_assert(v3g<X{3}>(42).n == 3 + 42);
+ }
+ };
+};
+
+int main() {
+ A<int>::B<int>::f<int>();
+}
On 9/6/24 11:19 AM, Patrick Palka wrote:
> On Thu, 5 Sep 2024, Jason Merrill wrote:
>
>> On 9/5/24 2:28 PM, Patrick Palka wrote:
>>> On Thu, 5 Sep 2024, Jason Merrill wrote:
>>>
>>>> On 9/5/24 1:26 PM, Patrick Palka wrote:
>>>>> On Thu, 5 Sep 2024, Jason Merrill wrote:
>>>>>
>>>>>> On 9/5/24 10:54 AM, Patrick Palka wrote:
>>>>>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
>>>>>>> for trunk/14?
>>>>>>>
>>>>>>> -- >8 --
>>>>>>>
>>>>>>> A lambda within a default template argument used in some template-id
>>>>>>> may have a smaller template depth than the context of the
>>>>>>> template-id.
>>>>>>> For example, the lambda in v1's default template argument has
>>>>>>> template
>>>>>>> depth 1, and in v2's has template depth 2, but the template-ids
>>>>>>> v1<0>
>>>>>>> and v2<0> which uses these default arguments appear in a depth 3
>>>>>>> template
>>>>>>> context. So add_extra_args will ultimately return args with depth 3
>>>>>>> --
>>>>>>> too many args for the lambda, leading to a bogus substitution.
>>>>>>>
>>>>>>> This patch fixes this by trimming the result of add_extra_args to
>>>>>>> match
>>>>>>> the template depth of the lambda. A new LAMBDA_EXPR_TEMPLATE_DEPTH
>>>>>>> field
>>>>>>> is added that tracks the template-ness of a lambda;
>>>>>>>
>>>>>>> PR c++/116567
>>>>>>>
>>>>>>> gcc/cp/ChangeLog:
>>>>>>>
>>>>>>> * pt.cc (tsubst_lambda_expr): For a deferred-substitution
>>>>>>> lambda,
>>>>>>> trim the augmented template arguments to match the template
>>>>>>> depth
>>>>>>> of the lambda.
>>>>>>>
>>>>>>> gcc/testsuite/ChangeLog:
>>>>>>>
>>>>>>> * g++.dg/cpp2a/lambda-targ7.C: New test.
>>>>>>> ---
>>>>>>> gcc/cp/pt.cc | 11 +++++++++
>>>>>>> gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 30
>>>>>>> +++++++++++++++++++++++
>>>>>>> 2 files changed, 41 insertions(+)
>>>>>>> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
>>>>>>>
>>>>>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>>>>>> index 747e627f547..c49a26b4f5e 100644
>>>>>>> --- a/gcc/cp/pt.cc
>>>>>>> +++ b/gcc/cp/pt.cc
>>>>>>> @@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args,
>>>>>>> tsubst_flags_t complain, tree in_decl)
>>>>>>> LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
>>>>>>> complain);
>>>>>>> return t;
>>>>>>> }
>>>>>>> + if (LAMBDA_EXPR_EXTRA_ARGS (t))
>>>>>>> + {
>>>>>>> + /* If we deferred substitution into this lambda, then it's
>>>>>>> probably
>>>>>>> from
>>>>>>
>>>>>> "probably" seems wrong, given that it wasn't implemented for this
>>>>>> case.
>>>>>
>>>>> I said "probably" because in e.g.
>>>>>
>>>>> template<int N, auto F = []{}>
>>>>> bool b = true;
>>>>>
>>>>> template<class T>
>>>>> void f() {
>>>>> b<0>;
>>>>> }
>>>>>
>>>>> the lambda context has the same depth as the template-id context. But
>>>>> as you point out, the issue is ultimately related vs unrelated
>>>>> parameters rather than depth.
>>>>>
>>>>>>
>>>>>>> + a context (e.g. default template argument context) which may
>>>>>>> have
>>>>>>> fewer
>>>>>>> + levels than the current context it's embedded in. Adjust the
>>>>>>> result
>>>>>>> of
>>>>>>> + add_extra_args accordingly. */
>>>>>>
>>>>>> Hmm, this looks like a situation of not just fewer levels, but
>>>>>> potentially
>>>>>> unrelated levels. "args" here is for f, which shares no template
>>>>>> context
>>>>>> with
>>>>>> v1. What happens if your templates have non-type template parameters?
>>>>>
>>>>> Indeed before add_extra_args 'args' will be unrelated, but after doing
>>>>> add_extra_args the innermost levels of 'args' will correspond to the
>>>>> lambda's template context, and so using get_innermost_template_args
>>>>> ought to get rid of the unrelated arguments, keeping only the ones
>>>>> relevant to the original lambda context.
>>>>
>>>> Will they? The original function of add_extra_args was to reintroduce
>>>> outer
>>>> args that we weren't able to substitute the last time through
>>>> tsubst_lambda_expr. I expect the innermost levels of 'args' to be the
>>>> same
>>>> before and after.
>>>>
>>>> Hmm, looking at add_extra_args again, I see that whether the EXTRA_ARGS go
>>>> on
>>>> the outside or the inside depends on whether they're dependent. How does
>>>> this
>>>> work other than by accident? >.>
>>>
>>> It's kind of a happy accident indeed :P In the cases this patch is
>>> concerned with, i.e. a template-id using a default argument containing
>>> a lambda, the extra args will always be considered dependent because
>>> during default template argument coercion we substitute an incomplete
>>> set of targs into the default targ (namely the element corresponding to
>>> the default targ is NULL_TREE), which any_dependent_template_arguments_p
>>> considers dependent. So add_extra_args will reliably put these captured
>>> extra args as the innermost!
>>
>> I see that the testcases you enabled this code to handle in
>> r12-2222-g2c699fd29829cd were also about lambda/requires in default template
>> arguments. Can we detect this case some other way than uses_template_parms?
>
> I think during default template argument coercion we need to set
> tf_partial when in a template context. Then build_extra_args should
> remember if this flag was set. Then add_extra_args should merge the
> extra arguments according to whether the flag was set during deferring.
>
>> Can we do the pruning added by this patch in add_extra_args instead of its
>> caller?
>
> If we're only concerned with the default template argument situation, then
> it seems 'extra' will always be a full set of template arguments (even when
> the template-id names a template from the current instantiation) so I think
> add_extra_args just needs to substitute into 'extra' and not do any additional
> pruning.
>
>> Incidentally, why is having extra outer levels causing trouble? I thought we
>> were generally able to safely ignore them.
>
> I thought extra _inner_ levels are safe to ignore generally? If we have
> extra outer levels then it causes the inner args to no longer be at the
> right level.
>
> For
>
> template<auto N, auto = [] {}>
> bool flag = true;
>
> template<typename>
> struct core {
> template<typename>
> auto func() -> bool {
> return flag<0>;
> }
> };
>
> auto main() -> int {
> core<int> _;
> [[maybe_unused]] bool flag = _.func<int>();
> }
>
> we crash during substitution into lambda's 'auto' return type which has
> level 2, and 'args' after add_extra_args is {{int},{0, <empty>}}, so
> we see non-type argument at level 2 index 0 where a type is expected.
> (And if the lambda used N, that substitution would be similarly wrong
> as well.)
>
> How does the following look? Bootstrapped and regtested on
> x86_64-pc-linux-gnu. This would be too risky to backport, so for 14 I
> think we should go with the original patch.
>
> -- >8 --
>
> Subject: [PATCH] c++: deferring partial substitution into lambda [PR116567]
>
> PR c++/116567
>
> gcc/cp/ChangeLog:
>
> * pt.cc (coerce_template_parms): Pass tf_partial when substituting
> into a default template argument in a template context.
> (build_extra_args): Set TREE_STATIC on the extra args if this is
> a partial substitution.
> (add_extra_args): Handle deferred partial substitution.
> (tsubst_lammda_expr): Check for tf_partial instead of dependence
> of args when determining whether to defer substitution.
> (tsubst_expr) <case LAMBDA_EXPR>: Remove tf_partial early exit.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/cpp2a/lambda-targ7.C: New test.
> ---
> gcc/cp/pt.cc | 33 +++++++++---------
> gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C | 42 +++++++++++++++++++++++
> 2 files changed, 58 insertions(+), 17 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-targ7.C
>
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 9195a5274e1..9a9c16a47c2 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -9326,7 +9326,9 @@ coerce_template_parms (tree parms,
> {
> /* There must be a default arg in this case. */
> arg = tsubst_template_arg (TREE_PURPOSE (parm), new_args,
> - complain, in_decl);
> + complain | (processing_template_decl
> + ? tf_partial : tf_none),
> + in_decl);
> /* The position of the first default template argument,
> is also the number of non-defaulted arguments in NEW_INNER_ARGS.
> Record that. */
> @@ -13572,6 +13574,9 @@ build_extra_args (tree pattern, tree args, tsubst_flags_t complain)
> if (local_specializations)
> if (tree locals = extract_local_specs (pattern, complain))
> extra = tree_cons (NULL_TREE, extra, locals);
> + if (complain & tf_partial)
> + /* Remember whether this is a partial substitution. */
> + TREE_STATIC (extra) = true;
> return extra;
> }
>
> @@ -13581,7 +13586,10 @@ build_extra_args (tree pattern, tree args, tsubst_flags_t complain)
> tree
> add_extra_args (tree extra, tree args, tsubst_flags_t complain, tree in_decl)
> {
> - if (extra && TREE_CODE (extra) == TREE_LIST)
> + if (!extra)
> + return args;
> +
> + if (TREE_CODE (extra) == TREE_LIST)
> {
> for (tree elt = TREE_CHAIN (extra); elt; elt = TREE_CHAIN (elt))
> {
> @@ -13599,13 +13607,11 @@ add_extra_args (tree extra, tree args, tsubst_flags_t complain, tree in_decl)
> gcc_assert (!TREE_PURPOSE (extra));
> extra = TREE_VALUE (extra);
> }
> - if (uses_template_parms (extra))
> - {
> - /* This can happen after dependent substitution into a
> - requires-expr or a lambda that uses constexpr if. */
> - extra = tsubst_template_args (extra, args, complain, in_decl);
> - args = add_outermost_template_args (args, extra);
> - }
> + if (TREE_STATIC (extra))
> + /* This is a partial substitution into e.g. a requires-expr or lambda
> + inside a default template argument; we expect 'extra' to be a full
> + set of template arguments for the template context. */
> + args = tsubst_template_args (extra, args, complain, in_decl);
> else
> args = add_to_template_args (extra, args);
Let's re-add a checking_assert as well, I guess now asserting that
(TREE_STATIC (extra) == uses_template_parms (extra))
OK with that change. And the original patch is OK for 14.
Jason
@@ -19699,6 +19699,17 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
LAMBDA_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
return t;
}
+ if (LAMBDA_EXPR_EXTRA_ARGS (t))
+ {
+ /* If we deferred substitution into this lambda, then it's probably from
+ a context (e.g. default template argument context) which may have fewer
+ levels than the current context it's embedded in. Adjust the result of
+ add_extra_args accordingly. */
+ tree ctx_parms = DECL_TEMPLATE_PARMS (DECL_TI_TEMPLATE (oldfn));
+ if (generic_lambda_fn_p (oldfn))
+ ctx_parms = TREE_CHAIN (ctx_parms);
+ args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (ctx_parms));
+ }
tree r = build_lambda_expr ();
new file mode 100644
@@ -0,0 +1,30 @@
+// PR c++/116567
+// { dg-do compile { target c++20 } }
+
+template<auto N, auto F = []{}>
+bool v1 = true;
+
+template<auto N, auto F = [](auto){}>
+bool v1g = true;
+
+template<class T>
+struct A {
+ template<auto N, auto F = []{}>
+ static inline bool v2 = true;
+
+ template<auto N, auto F = [](auto){}>
+ static inline bool v2g = true;
+
+ template<class U>
+ struct B {
+ template<class V>
+ static void f() {
+ v1<0> && v1g<0>;
+ v2<0> && v2g<0>;
+ }
+ };
+};
+
+auto main() -> int {
+ A<int>::B<int>::f<int>();
+}