On 3/27/24 10:01, Patrick Palka wrote:
On Mon, 25 Mar 2024, Patrick Palka wrote:
On Mon, 25 Mar 2024, Patrick Palka wrote:

The below testcases use a lambda-expr as a template argument and they
all trip over the below added tsubst_lambda_expr sanity check ultimately
because current_template_parms is empty, which causes push_template_decl
to return error_mark_node from the call to begin_lambda_type.  Were it
not for the sanity check this silent error_mark_node result leads to
nonsensical errors down the line, or silent breakage.

In the first testcase, we hit this assert during instantiation of the
dependent alias template-id c1_t<_Data> from instantiate_template, which
clears current_template_parms via push_to_top_level.  Similar story for
the second testcase.  For the third testcase we hit the assert during
partial instantiation of the member template from instantiate_class_template
which similarly calls push_to_top_level.

These testcases illustrate that templated substitution into a lambda-expr
is not always possible, in particular when we lost the relevant template
context.  I experimented with recovering the template context by making
tsubst_lambda_expr fall back to using scope_chain->prev->template_parms if
current_template_parms is empty which worked but seemed like a hack.  I
also experimented with preserving the template context by keeping
current_template_parms set during instantiate_template for a dependent
specialization which also worked but it's at odds with the fact that we
cache dependent specializations (and so they should be independent of
the template context).

I suspect the problem comes from this bit in type_unification_real:

/* First instatiate in template context, in case we still depend on undeduced template parameters. */
                  ++processing_template_decl;
                  substed = tsubst_template_arg (arg, full_targs, complain,
                                                 NULL_TREE);
                  --processing_template_decl;
                  if (substed != error_mark_node
                      && !uses_template_parms (substed))
/* We replaced all the tparms, substitute again out of template context. */
                    substed = NULL_TREE;

and perhaps we should switch to searching the argument for undeduced template parameters rather than doing a trial substitution.

But the pattern of setting processing_template_decl, substituting, and clearing it again is very widespread, so we still want to handle lambdas in that context.

+  if (processing_template_decl && !in_template_context)
+    {
+      /* Defer templated substitution into a lambda-expr when arguments
+        are dependent or when we lost the necessary template context,
+        which may happen for a lambda-expr used as a template argument.  */

And this comment is stale (an earlier version of the patch also deferred
for dependent arguments even when current_template_parms is non-empty,
which I backed out to make the fix as narrow as possible).

FWIW I also experimented with unconditionally deferring templated
substitution into a lambda-expr (i.e. iff processing_template_decl)
which passed bootstrap+regtest, and turns out to also fix the
(non-regression) PR114167.  I didn't analyze the underlying issue
very closely though, there might very well be a better way to fix
that particular non-regression PR.

One downside of unconditionally deferring is that it'd mean less
ahead-of-time checking of uninvoked deeply-nested generic lambdas,
e.g.:

   int main() {
     [](auto x) {
       [](auto) {
         [](auto) { decltype(x)::fail; }; // not diagnosed anymore
       };
     }(0);
   }

Hmm, unconditionally deferring would probably also help to resolve issues with local classes in generic lambdas. It might be worth going that way rather than continue to grapple with partial substitution problems.

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 8cf0d5b7a8d..c25bdd283f1 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -19571,6 +19572,18 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
    tree oldfn = lambda_function (t);
    in_decl = oldfn;
+ args = add_extra_args (LAMBDA_EXPR_EXTRA_ARGS (t), args, complain, in_decl);
+  if (processing_template_decl && !in_template_context)
+    {
+      /* Defer templated substitution into a lambda-expr when we lost the
+        necessary template context, which may happen for a lambda-expr
+        used as a template argument.  */

as a *default* template argument.  OK with that tweak.

Jason

Reply via email to