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>(); +} -- 2.46.0.519.g2e7b89e038