diff mbox

c++/pr77585 bogus this error with generic lambda

Message ID d5fbf52d-d8e8-1552-44f9-4ced725aa0ab@acm.org
State New
Headers show

Commit Message

Nathan Sidwell Dec. 15, 2016, 12:38 p.m. UTC
77585 concerns the instantiation of a generic lambda that contains a 
call to a non-dependent non-static member function.

   auto lam = [&](auto) { return Share (); };
   r += Eat (lam);  // instantation of lambda::operator() here

During instantiation of the call to Share, maybe_resolve_dummy gets 
called and uses current_nonlambda_class_type, which peeks up the 
current_current_class stack.

That peeking presupposes we're actually pushing and popping class scopes 
as we enter them all the way from the global scope.  But that doesn't 
always happen in instantiation.  push_nested_class pushes the 
immediately enclosing scopes, but stops at function scope.  So we don't 
get the class scope of that function pushed.  Thus stack peeking fails.

This hasn't previously been an instantiation problem, because templates 
couldn't be defined at local scope.  But generic lambdas now have that 
property (wrt this capture at least).

This patch amends instantiate_decl to first push the containing 
non-lambda class scope before start_preparsed_function does its stack 
pushing.

ok?

nathan
-- 
Nathan Sidwell

Comments

Jason Merrill Dec. 15, 2016, 7:41 p.m. UTC | #1
OK.

On Thu, Dec 15, 2016 at 7:38 AM, Nathan Sidwell <nathan@acm.org> wrote:
> 77585 concerns the instantiation of a generic lambda that contains a call to

> a non-dependent non-static member function.

>

>   auto lam = [&](auto) { return Share (); };

>   r += Eat (lam);  // instantation of lambda::operator() here

>

> During instantiation of the call to Share, maybe_resolve_dummy gets called

> and uses current_nonlambda_class_type, which peeks up the

> current_current_class stack.

>

> That peeking presupposes we're actually pushing and popping class scopes as

> we enter them all the way from the global scope.  But that doesn't always

> happen in instantiation.  push_nested_class pushes the immediately enclosing

> scopes, but stops at function scope.  So we don't get the class scope of

> that function pushed.  Thus stack peeking fails.

>

> This hasn't previously been an instantiation problem, because templates

> couldn't be defined at local scope.  But generic lambdas now have that

> property (wrt this capture at least).

>

> This patch amends instantiate_decl to first push the containing non-lambda

> class scope before start_preparsed_function does its stack pushing.

>

> ok?

>

> nathan

> --

> Nathan Sidwell
diff mbox

Patch

2016-12-14  Nathan Sidwell  <nathan@acm.org>

	PR c++/77585
	* pt.c (instantiate_decl): Push to class scope lambda resides
	within when instantiating a generic lambda function.

	PR c++/77585
	* g++.dg/cpp1y/pr77585.C: New.

Index: cp/pt.c
===================================================================
--- cp/pt.c	(revision 243661)
+++ cp/pt.c	(working copy)
@@ -22483,6 +22483,7 @@  instantiate_decl (tree d, int defer_ok,
       tree tmpl_parm;
       tree spec_parm;
       tree block = NULL_TREE;
+      tree lambda_ctx = NULL_TREE;
 
       /* Save away the current list, in case we are instantiating one
 	 template from within the body of another.  */
@@ -22496,7 +22497,23 @@  instantiate_decl (tree d, int defer_ok,
 	  && TREE_CODE (DECL_CONTEXT (code_pattern)) == FUNCTION_DECL)
 	block = push_stmt_list ();
       else
-	start_preparsed_function (d, NULL_TREE, SF_PRE_PARSED);
+	{
+	  if (LAMBDA_FUNCTION_P (d))
+	    {
+	      /* When instantiating a lambda's templated function
+		 operator, we need to push the non-lambda class scope
+		 of the lambda itself so that the nested function
+		 stack is sufficiently correct to deal with this
+		 capture.  */
+	      lambda_ctx = DECL_CONTEXT (d);
+	      do 
+		lambda_ctx = decl_type_context (TYPE_NAME (lambda_ctx));
+	      while (lambda_ctx && LAMBDA_TYPE_P (lambda_ctx));
+	      if (lambda_ctx)
+		push_nested_class (lambda_ctx);
+	    }
+	  start_preparsed_function (d, NULL_TREE, SF_PRE_PARSED);
+	}
 
       /* Some typedefs referenced from within the template code need to be
 	 access checked at template instantiation time, i.e now. These
@@ -22564,6 +22581,8 @@  instantiate_decl (tree d, int defer_ok,
 	  d = finish_function (0);
 	  expand_or_defer_fn (d);
 	}
+      if (lambda_ctx)
+	pop_nested_class ();
 
       if (DECL_OMP_DECLARE_REDUCTION_P (code_pattern))
 	cp_check_omp_declare_reduction (d);
Index: testsuite/g++.dg/cpp1y/pr77585.C
===================================================================
--- testsuite/g++.dg/cpp1y/pr77585.C	(revision 0)
+++ testsuite/g++.dg/cpp1y/pr77585.C	(working copy)
@@ -0,0 +1,41 @@ 
+// PR c++/77585
+// { dg-do run { target c++14 } }
+
+// Confusion about this capture when instantiating generic lambda's
+// function operator
+
+template <typename F> int Eat (F &&f) { return f (1); }
+
+struct Foo {
+  int x = 1;
+  int Share () { return x++; }
+  int Frob (int);
+};
+
+int Foo::Frob (int r)
+{
+  auto lam = [&](auto) { return Share (); };
+  r += Eat (lam);
+
+  auto lam0 = [&](auto) {
+    auto lam1 = [&](auto) { return Share (); };
+    return Eat (lam1); };
+  r += Eat (lam0);
+
+  return r;
+}
+
+int Frob (int r) 
+{
+  auto lam = [&](auto) { return 1; };
+  r += Eat (lam);
+  return r;
+}
+
+
+int main ()
+{
+  Foo f;
+  
+  return Frob (f.Frob (0)) == 4 ? 0 : 1;
+}