On 8/12/25 8:04 AM, Marek Polacek wrote:
On Sun, Aug 10, 2025 at 02:20:22PM -0700, Jason Merrill wrote:
On 8/8/25 11:37 AM, Marek Polacek wrote:
On Tue, Aug 05, 2025 at 02:54:01PM -0700, Jason Merrill wrote:
On 8/4/25 4:53 PM, Marek Polacek wrote:
Now that even dummy lambdas have an operator(), I had to tweak
resolvable_dummy_lambda to give the right answer.  This I did by
checking DECL_LAMBDA_FUNCTION_P which isn't set for the dummy op().

Hmm, that's a new one to me.  Perhaps we should remove LAMBDA_FUNCTION_P
now?

I would like to remove it but if I'm using it here then I guess I can't,
yet.

Are there uses of LAMBDA_FUNCTION_P that can't be replaced with
DECL_LAMBDA_FUNCTION_P?

Already the one in decl_dependent_p is problematic: LAMBDA_FUNCTION_P checks
DECL_DECLARES_FUNCTION_P and so takes both fn decls and template decls.
DECL_LAMBDA_FUNCTION_P just assumes a fn decl.  So the change would be more
involved.
In any case this new check needs a comment.

Added.

In tsubst_lambda_expr I have to add the dummy op() as well.  That meant
I had to play some push_nested_namespace/class games, kind of like in
instantiate_decl.

Hmm, when I comment out the push/pop, none of the tests break?

Ah, yes.  That's because I started only adding the dummy op() if there
are no captures.  I've added lambda-scope9.C (reduced from lambda-uneval17.C)
to show the error we'd get without the push/pop.  Unfortunately it
captures a non-automatic variable.  I don't know if there is a better
way to test it.

If there are no explicit captures, as it's often the case, we can skip
the dummy op() altogether.

@@ -12888,9 +12896,12 @@ finish_decltype_type (tree expr, bool 
id_expression_or_member_access_p,
          }
        else
          {
-      if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr))
-         && current_function_decl
-         && LAMBDA_FUNCTION_P (current_function_decl))
+      const bool cfun_is_lambda_p
+       = (current_function_decl && LAMBDA_FUNCTION_P (current_function_decl));
+      if ((outer_automatic_var_p (STRIP_REFERENCE_REF (expr))
+          && cfun_is_lambda_p)
+         || (automatic_var_p (STRIP_REFERENCE_REF (expr))
+             && current_lambda_expr ()))

Can we make outer_automatic_var_p give the right answer in this context
instead of adding a separate automatic_var_p, and then just change the
current_function_decl test to current_lambda_expr ()?

Sorry, I had no luck with this.  First I think process_outer_var_ref
can't handle these vars.  Also, like in lambda-generic-ice5.C, we can't
tell that in:

      int f = 3;
      auto l = [](auto of_type_X)->
                  Void<(decltype(of_type_X)::foo(f), 0)>
        {return;};

'f' is outer, but 'of_type_X' is not.

But we need to make that distinction.  Which shouldn't be so hard, since
they should have different DECL_CONTEXT and are in different binding levels.

Sorry, I don't understand.  Yes, we need to make that distinction.
But outer_automatic_var_p only gets one decl, and otherwise only looks
at current_function_decl.  And for both 'f' and 'of_type_X'
DECL_CONTEXT (decl) == current_function_decl.  And for both there is
current_lambda_expr ().

It should be possible to distinguish them by looking at the binding
levels/IDENTIFIER_BINDING if necessary.

I'm also suprised that of_type_X and f would have the same DECL_CONTEXT.
They clearly shouldn't, but perhaps they do at this point because we haven't
set the DECL_CONTEXT of of_type_X properly yet?

Sorry, I must have goofed this up before because that's not what I'm seeing
now.  Sorry.

When we get to outer_automatic_var_p while parsing, of_type_X doesn't have
a DECL_CONTEXT (so isn't outer).

Which seems fine, since it indeed isn't outer.

Yes.
And f isn't outer because its context is == c_f_d which is main.

But it effectively is outer, as we see when we try to instantiate the lambda
op().  But at parse time we wrongly consider it non-outer.

True.

This issue also affects non-lambdas, but fixing it in general is more
complicated due to vexing parse; a use that would be ill-formed in a
function declaration is fine in the initializer, so we would need to defer
the error.  Which should be doable by removing decl_constant_var_p calls in
process_outer_var_ref and mark_use, but isn't quite that simple.

I see what you mean.

But lambdas aren't subject to the most vexing parse, so if we're in
sk_lambda (and possibly sk_function_parms within that) we should be fine to
call it outer.

Yep.  I've added parsing_lambda_declarator to be used alongside parsing_nsdmi
in outer_var_p and process_outer_var_ref...and it works!

I've tried another approach -- checking cp_unevaluated_operand in outer_var_p
for the current_lambda_expr case.  Then finish_decltype_type can use
outer_automatic_var_p without any extra checks.

Hmm, the test looks like it would also be true for a local variable in a
member function of a local class in a lambda.

This should be false now with parsing_lambda_declarator, because of the
sk_block.
If I don't check cp_unevaluated_operand, lambda-generic-ice5.C crashes
because we get to process_outer_var_ref with 'f', and end up calling
add_default_capture with empty lambda_stack, and then process_outer_var_ref
returns NULL_TREE.  I've tried using current_lambda_expr but here we
shouldn't be creating a capture for 'f' in the first place.

Agreed, we should return 'var' for 'f'.  I think the problem is that
containing_function is set to the enclosing function; it should be fine to
set it to NULL_TREE like we do for parsing_nsdmi.

It seems to work!  Thanks a lot; I wish I'd noticed that before.

The only change in v6 is the addition of parsing_lambda_declarator.
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

OK, thanks.

-- >8 --
This patch is an attempt to implement P2036R3 along with P2579R0, fixing
build breakages caused by P2036R3.

The simplest example is:

   auto counter1 = [j=0]() mutable -> decltype(j) {
       return j++;
   };

which currently doesn't compile because the 'j' in the capture isn't
visible in the trailing return type.  With these proposals, the 'j'
will be in a lambda scope which spans the trailing return type, so
this test will compile.

This oughtn't be difficult but decltype and other issues made this patch
much more challenging.

We have to push the explicit captures before going into
_lambda_declarator_opt because that is what parses the trailing return
type.  Yet we can't build any captures until after _lambda_body ->
start_lambda_function which creates the lambda's operator(), without
which we can't build a proxy, but _lambda_body happens only after
parsing the declarator.  This patch works around it by creating a fake
operator() and adding it to the capture and then removing it when we
have the real operator().

Another thing is that in "-> decltype(j)" we don't have the right
current_function_decl yet.  If current_lambda_expr gives us a lambda,
we know this decltype appertains to a lambda.  But we have to know if we
are in a parameter-declaration-clause: as per [expr.prim.id.unqual]/4.4,
if we are, we shouldn't be adding "const".  The new LAMBDA_EXPR_CONST_QUAL_P
flag tracks this.  But it doesn't handle nested lambdas yet, specifically,
[expr.prim.id.unqual]/14.

I don't think this patch changes behavior for the tests in
"capture-default with [=]" as the paper promises; clang++ behaves the
same as gcc with this patch.

        PR c++/102610

gcc/cp/ChangeLog:

        * cp-tree.h (LAMBDA_EXPR_CONST_QUAL_P): Define.
        (maybe_add_dummy_lambda_op): Declare.
        (remove_dummy_lambda_op): Declare.
        (push_capture_proxies): Adjust.
        * lambda.cc (build_capture_proxy): No longer static.  New early_p
        parameter.  Use it.
        (add_capture): Adjust the call to build_capture_proxy.
        (resolvable_dummy_lambda): Check DECL_LAMBDA_FUNCTION_P.
        (push_capture_proxies): New.
        (start_lambda_function): Use it.
        * name-lookup.cc (check_local_shadow): Give an error for
        is_capture_proxy.
        (cp_binding_level_descriptor): Add lambda-scope.
        (begin_scope) <case sk_lambda>: New case.
        * name-lookup.h (enum scope_kind): Add sk_lambda.
        (struct cp_binding_level): Widen kind.
        * parser.cc (cp_parser_lambda_expression): Create a new (lambda) scope
        after the lambda-introducer.
        (cp_parser_lambda_declarator_opt): Set LAMBDA_EXPR_CONST_QUAL_P.
        Create a dummy operator() if needed.  Inject the captures into the
        lambda scope.  Remove the dummy operator().
        (make_dummy_lambda_op): New.
        (maybe_add_dummy_lambda_op): New.
        (remove_dummy_lambda_op): New.
        * pt.cc (tsubst_lambda_expr): Begin/end a lambda scope.  Push the
        capture proxies.  Build/remove a dummy operator() if needed.  Set
        LAMBDA_EXPR_CONST_QUAL_P.
        * semantics.cc (parsing_lambda_declarator): New.
        (outer_var_p): Also consider captures as outer variables if in a lambda
        declarator.
        (process_outer_var_ref): Reset containing_function when
        parsing_lambda_declarator.
        (finish_decltype_type): Process decls in the lambda-declarator as well.
        Look at LAMBDA_EXPR_CONST_QUAL_P unless we have an xobj function.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp0x/lambda/lambda-decltype3.C: Remove xfail.
        * g++.dg/warn/Wshadow-19.C: Add -Wpedantic.  Adjust a dg-warning.
        * g++.dg/warn/Wshadow-6.C: Adjust expected diagnostics.
        * g++.dg/cpp23/lambda-scope1.C: New test.
        * g++.dg/cpp23/lambda-scope2.C: New test.
        * g++.dg/cpp23/lambda-scope3.C: New test.
        * g++.dg/cpp23/lambda-scope4.C: New test.
        * g++.dg/cpp23/lambda-scope4b.C: New test.
        * g++.dg/cpp23/lambda-scope5.C: New test.
        * g++.dg/cpp23/lambda-scope6.C: New test.
        * g++.dg/cpp23/lambda-scope7.C: New test.
        * g++.dg/cpp23/lambda-scope8.C: New test.
        * g++.dg/cpp23/lambda-scope9.C: New test.
---
  gcc/cp/cp-tree.h                              |  11 +
  gcc/cp/lambda.cc                              |  40 +++-
  gcc/cp/name-lookup.cc                         |  11 +-
  gcc/cp/name-lookup.h                          |   5 +-
  gcc/cp/parser.cc                              |  94 ++++++++
  gcc/cp/pt.cc                                  |  21 ++
  gcc/cp/semantics.cc                           |  64 ++++--
  .../g++.dg/cpp0x/lambda/lambda-decltype3.C    |   2 +-
  gcc/testsuite/g++.dg/cpp23/lambda-scope1.C    |  85 +++++++
  gcc/testsuite/g++.dg/cpp23/lambda-scope2.C    | 217 ++++++++++++++++++
  gcc/testsuite/g++.dg/cpp23/lambda-scope3.C    |  44 ++++
  gcc/testsuite/g++.dg/cpp23/lambda-scope4.C    |  41 ++++
  gcc/testsuite/g++.dg/cpp23/lambda-scope4b.C   |  42 ++++
  gcc/testsuite/g++.dg/cpp23/lambda-scope5.C    |  22 ++
  gcc/testsuite/g++.dg/cpp23/lambda-scope6.C    |  20 ++
  gcc/testsuite/g++.dg/cpp23/lambda-scope7.C    |  20 ++
  gcc/testsuite/g++.dg/cpp23/lambda-scope8.C    |  25 ++
  gcc/testsuite/g++.dg/cpp23/lambda-scope9.C    |  15 ++
  gcc/testsuite/g++.dg/warn/Wshadow-19.C        |   4 +-
  gcc/testsuite/g++.dg/warn/Wshadow-6.C         |   8 +-
  20 files changed, 752 insertions(+), 39 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope1.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope2.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope3.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope4.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope4b.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope5.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope6.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope7.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope8.C
  create mode 100644 gcc/testsuite/g++.dg/cpp23/lambda-scope9.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index fb8e0d8d98e..caf8c0b9b9d 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -476,6 +476,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
        ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
        STATIC_INIT_DECOMP_BASE_P (in the TREE_LIST for {static,tls}_aggregates)
        MUST_NOT_THROW_THROW_P (in MUST_NOT_THROW_EXPR)
+      LAMBDA_EXPR_CONST_QUAL_P (in LAMBDA_EXPR)
     2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
        ICS_THIS_FLAG (in _CONV)
        DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
@@ -1557,6 +1558,13 @@ enum cp_lambda_default_capture_mode_type {
  #define LAMBDA_EXPR_CONSTEVAL_BLOCK_P(NODE) \
    TREE_LANG_FLAG_0 (LAMBDA_EXPR_CHECK (NODE))
+/* True if we should add "const" when figuring out the type of an entity
+   in a lambda.  This is false in the parameter-declaration-clause of
+   a lambda; after that, it will remain false if the mutable keyword is
+   present.  */
+#define LAMBDA_EXPR_CONST_QUAL_P(NODE) \
+  TREE_LANG_FLAG_1 (LAMBDA_EXPR_CHECK (NODE))
+
  /* True iff uses of a const variable capture were optimized away.  */
  #define LAMBDA_EXPR_CAPTURE_OPTIMIZED(NODE) \
    TREE_LANG_FLAG_2 (LAMBDA_EXPR_CHECK (NODE))
@@ -7774,6 +7782,8 @@ extern location_t defparse_location (tree);
  extern void maybe_show_extern_c_location (void);
  extern bool literal_integer_zerop (const_tree);
  extern tree attr_chainon (tree, tree);
+extern tree maybe_add_dummy_lambda_op (tree);
+extern void remove_dummy_lambda_op (tree, tree);
/* in pt.cc */
  extern tree canonical_type_parameter          (tree);
@@ -8301,6 +8311,7 @@ extern void record_lambda_scope                   (tree 
lambda);
  extern void record_lambda_scope_discriminator (tree lambda);
  extern void record_lambda_scope_sig_discriminator (tree lambda, tree fn);
  extern tree start_lambda_function             (tree fn, tree lambda_expr);
+extern void push_capture_proxies               (tree, bool = false);
  extern void finish_lambda_function            (tree body);
  extern bool regenerated_lambda_fn_p           (tree);
  extern tree lambda_regenerating_args          (tree);
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index c798967f8e1..711e3b7a18e 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -409,10 +409,11 @@ lambda_proxy_type (tree ref)
/* MEMBER is a capture field in a lambda closure class. Now that we're
     inside the operator(), build a placeholder var for future lookups and
-   debugging.  */
+   debugging.  But if EARLY_P is true, we do not have the real operator()
+   yet and we have to proceed differently.  */
-static tree
-build_capture_proxy (tree member, tree init)
+tree
+build_capture_proxy (tree member, tree init, bool early_p)
  {
    tree var, object, fn, closure, name, lam, type;
@@ -503,11 +504,19 @@ build_capture_proxy (tree member, tree init) if (name == this_identifier)
      {
+      if (early_p)
+       return var;
        gcc_assert (LAMBDA_EXPR_THIS_CAPTURE (lam) == member);
        LAMBDA_EXPR_THIS_CAPTURE (lam) = var;
      }
- if (fn == current_function_decl)
+  if (early_p)
+    {
+      gcc_checking_assert (current_binding_level->kind == sk_lambda);
+      /* insert_capture_proxy below wouldn't push into the lambda scope.  */
+      pushdecl (var);
+    }
+  else if (fn == current_function_decl)
      insert_capture_proxy (var);
    else
      vec_safe_push (LAMBDA_EXPR_PENDING_PROXIES (lam), var);
@@ -727,7 +736,7 @@ add_capture (tree lambda, tree id, tree orig_init, bool 
by_reference_p,
      = tree_cons (listmem, initializer, LAMBDA_EXPR_CAPTURE_LIST (lambda));
if (LAMBDA_EXPR_CLOSURE (lambda))
-    return build_capture_proxy (member, initializer);
+    return build_capture_proxy (member, initializer, /*early_p=*/false);
    /* For explicit captures we haven't started the function yet, so we wait
       and build the proxy from cp_parser_lambda_body.  */
    LAMBDA_CAPTURE_EXPLICIT_P (LAMBDA_EXPR_CAPTURE_LIST (lambda)) = true;
@@ -980,10 +989,14 @@ resolvable_dummy_lambda (tree object)
    tree type = TYPE_MAIN_VARIANT (TREE_TYPE (object));
    gcc_assert (!TYPE_PTR_P (type));
+ tree fn;
    if (type != current_class_type
        && current_class_type
        && LAMBDA_TYPE_P (current_class_type)
-      && lambda_function (current_class_type)
+      && (fn = lambda_function (current_class_type))
+      /* Even dummy lambdas have an operator() since P2036, but the
+        dummy operator() doesn't have this set.  */
+      && DECL_LAMBDA_FUNCTION_P (fn)
        && DERIVED_FROM_P (type, nonlambda_method_basetype()))
      return CLASSTYPE_LAMBDA_EXPR (current_class_type);
@@ -1784,6 +1797,17 @@ record_lambda_scope_sig_discriminator (tree lambda, tree fn)
    LAMBDA_EXPR_SCOPE_SIG_DISCRIMINATOR (lambda) = sig->count++;
  }
+/* Push the proxies for any explicit captures in LAMBDA_EXPR.
+   If EARLY_P, we do not have the real operator() yet.  */
+
+void
+push_capture_proxies (tree lambda_expr, bool early_p)
+{
+  for (tree cap = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); cap;
+       cap = TREE_CHAIN (cap))
+    build_capture_proxy (TREE_PURPOSE (cap), TREE_VALUE (cap), early_p);
+}
+
  tree
  start_lambda_function (tree fco, tree lambda_expr)
  {
@@ -1796,9 +1820,7 @@ start_lambda_function (tree fco, tree lambda_expr)
    tree body = begin_function_body ();
/* Push the proxies for any explicit captures. */
-  for (tree cap = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); cap;
-       cap = TREE_CHAIN (cap))
-    build_capture_proxy (TREE_PURPOSE (cap), TREE_VALUE (cap));
+  push_capture_proxies (lambda_expr);
return body;
  }
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index f5b36c954e1..647a6b97e33 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -3351,8 +3351,11 @@ check_local_shadow (tree decl)
        }
        /* Don't complain if it's from an enclosing function.  */
        else if (DECL_CONTEXT (old) == current_function_decl
-              && TREE_CODE (decl) != PARM_DECL
-              && TREE_CODE (old) == PARM_DECL)
+              && ((TREE_CODE (decl) != PARM_DECL
+                   && TREE_CODE (old) == PARM_DECL)
+                  /* We should also give an error for
+                      [x=1]{ int x; }  */
+                  || is_capture_proxy (old)))
        {
          /* Go to where the parms should be and see if we find
             them there.  */
@@ -4635,7 +4638,8 @@ cp_binding_level_descriptor (cp_binding_level *scope)
      "template-parameter-scope",
      "template-explicit-spec-scope",
      "transaction-scope",
-    "openmp-scope"
+    "openmp-scope",
+    "lambda-scope"
    };
    static_assert (ARRAY_SIZE (scope_kind_names) == sk_count,
                 "must keep names aligned with scope_kind enum");
@@ -4726,6 +4730,7 @@ begin_scope (scope_kind kind, tree entity)
      case sk_transaction:
      case sk_omp:
      case sk_stmt_expr:
+    case sk_lambda:
        scope->keep = keep_next_level_flag;
        break;
diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h
index 2fa736bd046..4acee3f86f4 100644
--- a/gcc/cp/name-lookup.h
+++ b/gcc/cp/name-lookup.h
@@ -214,6 +214,7 @@ enum scope_kind {
                        "template <>", this scope is always empty.  */
    sk_transaction,    /* A synchronized or atomic statement.  */
    sk_omp,          /* An OpenMP structured block.  */
+  sk_lambda,        /* A lambda scope.  */
    sk_count         /* Number of scope_kind enumerations.  */
  };
@@ -287,7 +288,7 @@ struct GTY(()) cp_binding_level {
    /* The kind of scope that this object represents.  However, a
        SK_TEMPLATE_SPEC scope is represented with KIND set to
        SK_TEMPLATE_PARMS and EXPLICIT_SPEC_P set to true.  */
-  ENUM_BITFIELD (scope_kind) kind : 4;
+  ENUM_BITFIELD (scope_kind) kind : 5;
/* True if this scope is an SK_TEMPLATE_SPEC scope. This field is
        only valid if KIND == SK_TEMPLATE_PARMS.  */
@@ -315,7 +316,7 @@ struct GTY(()) cp_binding_level {
       parent scope.  */
    unsigned artificial : 1;
- /* 21 bits left to fill a 32-bit word. */
+  /* 20 bits left to fill a 32-bit word.  */
  };
/* The binding level currently in effect. */
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 1dbe35b6c98..a0afbdb93c2 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -11881,6 +11881,10 @@ cp_parser_lambda_expression (cp_parser* parser,
      if (cp_parser_start_tentative_firewall (parser))
        start = token;
+ /* A lambda scope starts immediately after the lambda-introducer of E
+       and extends to the end of the compound-statement of E.  */
+    begin_scope (sk_lambda, NULL_TREE);
+
      ok &= cp_parser_lambda_declarator_opt (parser, lambda_expr,
                                           consteval_block_p);
@@ -11902,6 +11906,8 @@ cp_parser_lambda_expression (cp_parser* parser,
      if (ok)
        maybe_add_lambda_conv_op (type);
+ /* Leave the lambda scope. */
+    pop_bindings_and_leave_scope ();
      finish_struct (type, /*attributes=*/NULL_TREE);
in_consteval_if_p = save_in_consteval_if_p;
@@ -12292,6 +12298,13 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, 
tree lambda_expr,
    clear_decl_specs (&lambda_specs);
    /* A lambda op() is const unless explicitly 'mutable'.  */
    cp_cv_quals quals = TYPE_QUAL_CONST;
+  /* Don't add "const" to entities in the parameter-declaration-clause.  */
+  LAMBDA_EXPR_CONST_QUAL_P (lambda_expr) = false;
+
+  /* Inject the captures into the lambda scope as they may be used in the
+     declarator and we have to be able to look them up.  */
+  tree dummy_fco = maybe_add_dummy_lambda_op (lambda_expr);
+  push_capture_proxies (lambda_expr, /*early_p=*/true);
/* The template-parameter-list is optional, but must begin with
       an opening angle if present.  */
@@ -12482,6 +12495,10 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, 
tree lambda_expr,
        }
      }
+ /* Now we're done with the parameter-declaration-clause, and should
+     assume "const" unless "mutable" was present.  */
+  LAMBDA_EXPR_CONST_QUAL_P (lambda_expr) = quals == TYPE_QUAL_CONST;
+
    tx_qual = cp_parser_tx_qualifier_opt (parser);
    if (omitted_parms_loc && tx_qual)
      {
@@ -12539,6 +12556,10 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, 
tree lambda_expr,
        pop_bindings_and_leave_scope ();
      }
+ /* We are about to create the real operator(), so get rid of the old one. */
+  if (dummy_fco)
+    remove_dummy_lambda_op (dummy_fco, lambda_expr);
+
    /* Create the function call operator.
Messing with declarators like this is no uglier than building up the
@@ -12618,6 +12639,79 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, 
tree lambda_expr,
    }
  }
+/* Create a fake operator() for a lambda. We do this so that we can
+   build_capture_proxy even before start_lambda_function.  */
+
+static tree
+make_dummy_lambda_op ()
+{
+  cp_decl_specifier_seq return_type_specs;
+  cp_cv_quals quals = TYPE_UNQUALIFIED;
+
+  clear_decl_specs (&return_type_specs);
+  return_type_specs.type = make_auto ();
+
+  void *p = obstack_alloc (&declarator_obstack, 0);
+
+  cp_declarator *declarator = make_id_declarator (NULL_TREE,
+                                                 call_op_identifier,
+                                                 sfk_none,
+                                                 input_location);
+
+  declarator = make_call_declarator (declarator, void_list_node, quals,
+                                    VIRT_SPEC_UNSPECIFIED,
+                                    REF_QUAL_NONE, NULL_TREE,
+                                    NULL_TREE, NULL_TREE, NULL_TREE,
+                                    NULL_TREE, UNKNOWN_LOCATION);
+
+  tree fco = grokmethod (&return_type_specs, declarator, NULL_TREE);
+  obstack_free (&declarator_obstack, p);
+
+  return fco;
+}
+
+/* We need to push early capture proxies (for parsing the lambda-declarator),
+   and we may need a dummy operator() to be able to build the proxies.
+   LAMBDA_EXPR is the lambda we are building the captures for.  */
+
+tree
+maybe_add_dummy_lambda_op (tree lambda_expr)
+{
+  /* If there are no captures, we don't need this.  */
+  if (!LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))
+    return NULL_TREE;
+
+  tree fco = make_dummy_lambda_op ();
+  if (fco != error_mark_node)
+    finish_member_declaration (fco);
+
+  return fco;
+}
+
+/* Remove the dummy operator() DUMMY_FCO we built for parsing the
+   lambda-declarator of LAMBDA_EXPR.  */
+
+void
+remove_dummy_lambda_op (tree dummy_fco, tree lambda_expr)
+{
+  tree type = TREE_TYPE (lambda_expr);
+  if (TYPE_FIELDS (type) == dummy_fco)
+    {
+      /* Stitch out the dummy operator().  */
+      TYPE_FIELDS (type) = DECL_CHAIN (TYPE_FIELDS (type));
+      /* And clear the member vector as well.  */
+      auto *member_vec = CLASSTYPE_MEMBER_VEC (type);
+      gcc_assert (member_vec->length () == 1);
+      member_vec->truncate (0);
+    }
+  /* Class templates will have the dummy operator() stashed here too.  */
+  tree &list = CLASSTYPE_DECL_LIST (type);
+  if (list && TREE_VALUE (list) == dummy_fco)
+    list = TREE_CHAIN (list);
+  /* ??? We can't ggc_free dummy_fco yet.  There's still a binding in the
+     closure to it, and the captures have it as their DECL_CONTEXT.  */
+}
+
  /* Parse the body of a lambda expression, which is simply
compound-statement
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index b6b13edd03f..30b35193f0c 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -20529,6 +20529,18 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
tree fntype = static_fn_type (oldfn); + begin_scope (sk_lambda, NULL_TREE);
+
+  /* Like in cp_parser_lambda_expression, we need to bring the captures
+     into the lambda scope.  */
+  tree ns = decl_namespace_context (type);
+  push_nested_namespace (ns);
+  push_nested_class (type);
+  tree dummy_fco = maybe_add_dummy_lambda_op (r);
+  pop_nested_class ();
+  pop_nested_namespace (ns);
+  push_capture_proxies (r, /*early_p=*/true);
+
    tree saved_ctp = current_template_parms;
    if (oldtmpl)
      {
@@ -20542,6 +20554,10 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
        --processing_template_decl;
      }
+ /* We are about to create the real operator(), so get rid of the old one. */
+  if (dummy_fco)
+    remove_dummy_lambda_op (dummy_fco, r);
+
    if (fntype == error_mark_node)
      r = error_mark_node;
    else
@@ -20575,6 +20591,10 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
         enclosing expression.  */
        cp_evaluated ev;
+ /* Now we're done with the parameter-declaration-clause, and should
+        assume "const" unless "mutable" was present.  */
+      LAMBDA_EXPR_CONST_QUAL_P (r) = LAMBDA_EXPR_CONST_QUAL_P (t);
+
        bool nested = cfun;
        if (nested)
        push_function_context ();
@@ -20643,6 +20663,7 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
      }
out:
+  pop_bindings_and_leave_scope ();
    finish_struct (type, /*attr*/NULL_TREE);
insert_pending_capture_proxies ();
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 0c7788d9cbf..797b5295d05 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4496,6 +4496,17 @@ baselink_for_fns (tree fns)
    return build_baselink (conv_path, access_path, fns, /*optype=*/NULL_TREE);
  }
+/* Returns true iff we are currently parsing a lambda-declarator. */
+
+static bool
+parsing_lambda_declarator ()
+{
+  cp_binding_level *b = current_binding_level;
+  while (b->kind == sk_template_parms || b->kind == sk_function_parms)
+    b = b->level_chain;
+  return b->kind == sk_lambda;
+}
+
  /* Returns true iff DECL is a variable from a function outside
     the current one.  */
@@ -4510,7 +4521,15 @@ outer_var_p (tree decl)
          /* Don't get confused by temporaries.  */
          && DECL_NAME (decl)
          && (DECL_CONTEXT (decl) != current_function_decl
-             || parsing_nsdmi ()));
+             || parsing_nsdmi ()
+             /* Also consider captures as outer vars if we are in
+                decltype in a lambda declarator as in:
+                  auto l = [j=0]() -> decltype((j)) { ... }
+                for the sake of finish_decltype_type.
+
+                (Similar issue also affects non-lambdas, but vexing parse
+                makes it more difficult to handle than lambdas.)  */
+             || parsing_lambda_declarator ()));
  }
/* As above, but also checks that DECL is automatic. */
@@ -4556,7 +4575,7 @@ process_outer_var_ref (tree decl, tsubst_flags_t 
complain, bool odr_use)
    if (!mark_used (decl, complain))
      return error_mark_node;
- if (parsing_nsdmi ())
+  if (parsing_nsdmi () || parsing_lambda_declarator ())
      containing_function = NULL_TREE;
if (containing_function && LAMBDA_FUNCTION_P (containing_function))
@@ -12926,9 +12945,9 @@ finish_decltype_type (tree expr, bool 
id_expression_or_member_access_p,
      }
    else
      {
-      if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr))
-         && current_function_decl
-         && LAMBDA_FUNCTION_P (current_function_decl))
+      tree decl = STRIP_REFERENCE_REF (expr);
+      tree lam = current_lambda_expr ();
+      if (lam && outer_automatic_var_p (decl))
        {
          /* [expr.prim.id.unqual]/3: If naming the entity from outside of an
             unevaluated operand within S would refer to an entity captured by
@@ -12945,8 +12964,6 @@ finish_decltype_type (tree expr, bool 
id_expression_or_member_access_p,
             local variable inside decltype, not just decltype((x)) (PR83167).
             And we don't handle nested lambdas properly, where we need to
             consider the outer lambdas as well (PR112926). */
-         tree decl = STRIP_REFERENCE_REF (expr);
-         tree lam = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT 
(current_function_decl));
          tree cap = lookup_name (DECL_NAME (decl), LOOK_where::BLOCK,
                                  LOOK_want::HIDDEN_LAMBDA);
@@ -12962,17 +12979,28 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, if (type && !TYPE_REF_P (type))
            {
-             tree obtype = TREE_TYPE (DECL_ARGUMENTS (current_function_decl));
-             if (WILDCARD_TYPE_P (non_reference (obtype)))
-               /* We don't know what the eventual obtype quals will be.  */
-               goto dependent;
-             auto direct_type = [](tree t){
-                 if (INDIRECT_TYPE_P (t))
-                   return TREE_TYPE (t);
-                 return t;
-              };
-             int const quals = cp_type_quals (type)
-                             | cp_type_quals (direct_type (obtype));
+             int quals;
+             if (current_function_decl
+                 && LAMBDA_FUNCTION_P (current_function_decl)
+                 && DECL_XOBJ_MEMBER_FUNCTION_P (current_function_decl))
+               {
+                 tree obtype = TREE_TYPE (DECL_ARGUMENTS 
(current_function_decl));
+                 if (WILDCARD_TYPE_P (non_reference (obtype)))
+                   /* We don't know what the eventual obtype quals will be.  */
+                   goto dependent;
+                 auto direct_type = [](tree t){
+                     if (INDIRECT_TYPE_P (t))
+                       return TREE_TYPE (t);
+                     return t;
+                  };
+                 quals = (cp_type_quals (type)
+                          | cp_type_quals (direct_type (obtype)));
+               }
+             else
+               /* We are in the parameter clause, trailing return type, or
+                  the requires clause and have no relevant c_f_decl yet.  */
+               quals = (LAMBDA_EXPR_CONST_QUAL_P (lam)
+                        ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED);
              type = cp_build_qualified_type (type, quals);
              type = build_reference_type (type);
            }
diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype3.C 
b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype3.C
index 2e06e496140..6ba0dabc37d 100644
--- a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype3.C
+++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype3.C
@@ -11,7 +11,7 @@ void f() {
      decltype((x)) y2 = y1;      // y2 has type float const&
      decltype(r) r1 = y1;        // r1 has type float&
      decltype((r)) r2 = y2;      // r2 has type float const&
-    return y2;                  // { dg-bogus "'float&' to 'const float'" "" { 
xfail *-*-* } }
+    return y2;                  // { dg-bogus "'float&' to 'const float'" }
    };
[=](decltype((x)) y) {
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope1.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope1.C
new file mode 100644
index 00000000000..000979e3077
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope1.C
@@ -0,0 +1,85 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++23 } }
+
+template <typename T, typename U>
+constexpr bool is_same = false;
+
+template <typename T>
+constexpr bool is_same<T, T> = true;
+
+struct S {
+  void foo () {
+    auto counter1 = [j=0]() mutable -> decltype(j) {
+      return j++;
+    };
+    auto counter2 = [j=0, o=0, k=0, e=0]() mutable -> decltype(j) {
+      return j + o + k + e;
+    };
+  }
+};
+
+// [expr.prim.id.unqual]/3.2
+void
+f ()
+{
+  float x, &r = x;
+
+  [=]() -> decltype((x)) {      // lambda returns float const& because this 
lambda is not mutable and
+                                // x is an lvalue
+    decltype(x) y1;             // y1 has type float
+    decltype((x)) y2 = y1;      // y2 has type float const&
+    decltype(r) r1 = y1;        // r1 has type float&
+    decltype((r)) r2 = y2;      // r2 has type float const&
+    return y2;
+  };
+
+  [=](decltype((x)) y) {
+    decltype((x)) z = x;        // OK, y has type float&, z has type float 
const&
+    static_assert(is_same<float&, decltype((y))>);
+    static_assert(is_same<const float&, decltype((z))>);
+  };
+
+  [=] {
+    [](decltype((x)) y) {     // OK, lambda takes a parameter of type float 
const&
+    };
+
+    [x=1](decltype((x)) y) {
+      decltype((x)) z = x;      // OK, y has type int&, z has type int const&
+      // FIXME We don't handle nested lambdas yet?
+      //static_assert(is_same<int&, decltype((y))>);
+      static_assert(is_same<const int&, decltype((z))>);
+    };
+  };
+
+  [x=1](decltype((x)) y) {
+    decltype((x)) z = x;
+    static_assert(is_same<int&, decltype((y))>);
+    static_assert(is_same<const int&, decltype((z))>);
+  };
+}
+
+void
+ok ()
+{
+  auto counter1 = [j=0]() mutable -> decltype(j) {
+    static_assert(is_same<int&, decltype((j))>);
+    return j++;
+  };
+
+  auto l = [j=0]() -> decltype(j) {
+    static_assert(is_same<const int&, decltype((j))>);
+    return j;
+  };
+
+  int y;
+  [=] -> decltype((y)) {
+    return y;
+  };
+}
+
+void
+foo ()
+{
+  int x = [x](int y[sizeof x]){return sizeof x;}(0);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope2.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope2.C
new file mode 100644
index 00000000000..6b55e5fe513
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope2.C
@@ -0,0 +1,217 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++23 } }
+// From LLVM's test/SemaCXX/lambda-capture-type-deduction.cpp
+
+template <typename T, typename U>
+constexpr bool is_same = false;
+
+template <typename T>
+constexpr bool is_same<T, T> = true;
+
+void
+f ()
+{
+  int y;
+
+  static_assert(is_same<const int &,
+                       decltype([x = 1] -> decltype((x)) { return x; }())>);
+
+  static_assert(is_same<int &,
+                        decltype([x = 1] mutable -> decltype((x)) { return x; 
}())>);
+
+  static_assert(is_same<const int &,
+                        decltype([=] -> decltype((y)) { return y; }())>);
+
+  static_assert(is_same<int &,
+                        decltype([=] mutable -> decltype((y)) { return y; 
}())>);
+
+  // Clang++ rejects this one, though the only difference is the extra (),
+  // and without the () the result is correct, as demonstrated above.
+  static_assert(is_same<const int &,
+                        decltype([=]() -> decltype((y)) { return y; }())>);
+
+  static_assert(is_same<int &,
+                        decltype([=]() mutable -> decltype((y)) { return y; 
}())>);
+
+  static_assert(is_same<const int &,
+                       decltype([y] -> decltype((y)) { return y; }())>);
+
+  static_assert(is_same<int &,
+                        decltype([y] mutable -> decltype((y)) { return y; 
}())>);
+
+
+  auto ref = [&x = y](
+                 decltype([&](decltype(x)) { return 0; }) y) {
+    return x;
+  };
+}
+
+void
+nested ()
+{
+  int x, y, z;
+  (void)[&](
+      decltype([&](
+                   decltype([=](
+                                decltype([&](
+                                             decltype([&](decltype(x)) {})) 
{})) {})) {})){};
+
+  (void)[&](
+      decltype([&](
+                   decltype([&](
+                                decltype([&](
+                                             decltype([&](decltype(y)) {})) 
{})) {})) {})){};
+
+  (void)[=](
+      decltype([=](
+                   decltype([=](
+                                decltype([=](
+                                             decltype([&]<decltype(z)> {})) 
{})) {})) {})){};
+}
+
+void
+test_noexcept ()
+{
+  int y;
+
+  static_assert(noexcept([x = 1] noexcept(is_same<const int &, decltype((x))>) 
{}()));
+  static_assert(noexcept([x = 1] mutable noexcept(is_same<int &, 
decltype((x))>) {}()));
+  static_assert(noexcept([y] noexcept(is_same<const int &, decltype((y))>) 
{}()));
+  static_assert(noexcept([y] mutable noexcept(is_same<int &, decltype((y))>) 
{}()));
+  static_assert(noexcept([=] noexcept(is_same<const int &, decltype((y))>) 
{}()));
+  static_assert(noexcept([=] mutable noexcept(is_same<int &, decltype((y))>) 
{}()));
+  static_assert(noexcept([&] noexcept(is_same<int &, decltype((y))>) {}()));
+  static_assert(noexcept([&] mutable noexcept(is_same<int &, decltype((y))>) 
{}()));
+}
+
+void
+check_params ()
+{
+  int i = 0;
+  int &j = i;
+
+  [=](decltype((j)) jp, decltype((i)) ip) {
+    static_assert(is_same<const int&, decltype((j))>);
+    static_assert(is_same<const int &, decltype((i))>);
+    static_assert(is_same<int &, decltype((jp))>);
+    static_assert(is_same<int &, decltype((ip))>);
+  };
+
+  [=](decltype((j)) jp, decltype((i)) ip) mutable {
+    static_assert(is_same<int &, decltype((j))>);
+    static_assert(is_same<int &, decltype((i))>);
+    static_assert(is_same<int &, decltype((jp))>);
+    static_assert(is_same<int &, decltype((ip))>);
+    static_assert(is_same<int &, decltype(jp)>);
+    static_assert(is_same<int &, decltype(ip)>);
+  };
+
+  [a = 0](decltype((a)) ap) mutable {
+    static_assert(is_same<int &, decltype((a))>);
+    static_assert(is_same<int, decltype(a)>);
+    static_assert(is_same<int &, decltype(ap)>);
+    decltype(a) x;
+    decltype((a)) y = x;
+    static_assert(is_same<int &, decltype(y)>);
+  };
+
+  [a = 0](decltype((a)) ap) {
+    static_assert(is_same<const int &, decltype((a))>);
+    static_assert(is_same<int, decltype(a)>);
+    static_assert(is_same<int&, decltype((ap))>);
+    decltype(a) x;
+    decltype((a)) y = x;
+    static_assert(is_same<const int &, decltype(y)>);
+  };
+
+  int a;
+  [a](decltype((a)) ap) mutable {
+    static_assert(is_same<int &, decltype((a))>);
+    static_assert(is_same<int, decltype(a)>);
+    static_assert(is_same<int &, decltype(ap)>);
+    decltype(a) x;
+    decltype((a)) y = x;
+    static_assert(is_same<int &, decltype(y)>);
+  };
+
+  [a](decltype((a)) ap) {
+    static_assert(is_same<const int &, decltype((a))>);
+    static_assert(is_same<int, decltype(a)>);
+    static_assert(is_same<int&, decltype((ap))>);
+    decltype(a) x;
+    decltype((a)) y = x;
+    static_assert(is_same<const int &, decltype(y)>);
+  };
+}
+
+template <typename T>
+void
+check_params_tpl ()
+{
+  T i = 0;
+  T &j = i;
+  (void)[=](decltype((j)) jp, decltype((i)) ip) {
+    static_assert(is_same<const int&, decltype((j))>);
+    static_assert(is_same<const int &, decltype((i))>);
+    // In these two, clang++ produces 'const int&'.  Why, when it's
+    // the same as in check_params, just not a template?
+    static_assert(is_same<int &, decltype((jp))>);
+    static_assert(is_same<int &, decltype((ip))>);
+  };
+
+  (void)[=](decltype((j)) jp, decltype((i)) ip) mutable {
+    static_assert(is_same<int &, decltype((j))>);
+    static_assert(is_same<int &, decltype((i))>);
+    static_assert(is_same<int &, decltype((jp))>);
+    static_assert(is_same<int &, decltype((ip))>);
+    static_assert(is_same<int &, decltype(jp)>);
+    static_assert(is_same<int &, decltype(ip)>);
+  };
+
+  (void)[a = 0](decltype((a)) ap) mutable {
+    static_assert(is_same<int &, decltype((a))>);
+    static_assert(is_same<int, decltype(a)>);
+    static_assert(is_same<int &, decltype(ap)>);
+  };
+  (void)[a = 0](decltype((a)) ap) {
+    static_assert(is_same<const int &, decltype((a))>);
+    static_assert(is_same<int, decltype(a)>);
+    static_assert(is_same<int&, decltype((ap))>);
+  };
+}
+
+template<typename T>
+void
+test_requires ()
+{
+  int x;
+
+  [x = 1]() requires is_same<const int &, decltype((x))> {} ();
+  [x = 1]() mutable requires is_same<int &, decltype((x))> {}
+  ();
+  [x]() requires is_same<const int &, decltype((x))> {} ();
+  [x]() mutable requires is_same<int &, decltype((x))> {}
+  ();
+  [=]() requires is_same<const int &, decltype((x))> {} ();
+  [=]() mutable requires is_same<int &, decltype((x))> {}
+  ();
+  [&]() requires is_same<int &, decltype((x))> {}
+  ();
+  [&]() mutable requires is_same<int &, decltype((x))> {}
+  ();
+  [&x]() requires is_same<int &, decltype((x))> {}
+  ();
+  [&x]() mutable requires is_same<int &, decltype((x))> {}
+  ();
+
+  [x = 1]() requires is_same<const int &, decltype((x))> {} ();
+  [x = 1]() mutable requires is_same<int &, decltype((x))> {} ();
+}
+
+void
+use ()
+{
+  test_requires<int>();
+  check_params_tpl<int>();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope3.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope3.C
new file mode 100644
index 00000000000..697fdaae095
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope3.C
@@ -0,0 +1,44 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++17 } }
+
+template <typename T>
+inline constexpr auto
+equal1 (T &&t)
+{
+  return [t = 3](const auto& obj) -> decltype(obj == t)
+    {
+      return obj == t;
+    };
+}
+
+template <typename T>
+inline constexpr auto
+equal2 (T &&t)
+{
+  return [t = t](const auto& obj) -> decltype(obj == t)
+    {
+      return obj == t;
+    };
+}
+
+template <typename T>
+inline constexpr auto
+equal3 (T &&t)
+{
+  return [t = 4](const auto& obj) -> decltype(obj == t)
+    {
+      return obj == t;
+    };
+}
+
+void
+g ()
+{
+  constexpr auto l1 = equal1 (5);
+  static_assert (l1 (3));
+  constexpr auto l2 = equal2 (3);
+  static_assert (l2 (3));
+  constexpr auto l3 = equal3 (2);
+  static_assert (l3 (4));
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope4.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope4.C
new file mode 100644
index 00000000000..9442db3f956
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope4.C
@@ -0,0 +1,41 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++17 } }
+
+struct integral_constant {
+  using type = integral_constant;
+};
+template <bool> using __bool_constant = integral_constant;
+template <typename _Fn, typename>
+struct is_invocable : __bool_constant<true> {};
+int forward() { return 42; }
+template <typename...> class tuple;
+struct plus {
+  template <typename _Tp, typename _Up>
+  constexpr auto operator()(_Tp __t, _Up __u) {
+    return __t > __u;
+  }
+};
+constexpr auto equal() {
+  int t = 0;
+  return [t = 3](auto obj) -> decltype(obj == t) { return t; };
+}
+template <typename> struct is_tuple_invocable;
+template <typename... Ts> struct is_tuple_invocable<tuple<Ts...>> {
+  using type = typename is_invocable<Ts...>::type;
+};
+namespace detail {
+template <typename F, typename Tail, typename... T>
+constexpr auto compose(__bool_constant<true>, F f, Tail tail, T... objs) {
+  return f(tail(objs...));
+}
+} // namespace detail
+template <typename F, typename... Fs> constexpr auto compose(F f, Fs... fs) {
+  return [f, tail(fs...)](auto... objs) {
+    auto unitail =
+        typename is_tuple_invocable<tuple<decltype(objs)...>>::type{};
+    return detail::compose(unitail, f, tail, objs...);
+  };
+}
+template <auto> constexpr auto eq = equal();
+static_assert(compose(eq<3>, plus{})(1, 2));
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope4b.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope4b.C
new file mode 100644
index 00000000000..6a9e6ec5e9d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope4b.C
@@ -0,0 +1,42 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++17 } }
+
+struct integral_constant {
+  using type = integral_constant;
+};
+template <bool> using __bool_constant = integral_constant;
+template <typename _Fn, typename>
+struct is_invocable : __bool_constant<true> {};
+int forward() { return 42; }
+template <typename...> class tuple;
+struct plus {
+  template <typename _Tp, typename _Up>
+  constexpr auto operator()(_Tp __t, _Up __u) {
+    return __t > __u;
+  }
+};
+constexpr auto equal() {
+  int t = 0;
+  return [t = 3](auto obj) -> decltype(obj == t) { return t; };
+}
+template <typename> struct is_tuple_invocable;
+template <typename... Ts> struct is_tuple_invocable<tuple<Ts...>> {
+  using type = typename is_invocable<Ts...>::type;
+};
+namespace detail {
+template <typename F, typename Tail, typename... T>
+constexpr auto compose(__bool_constant<true>, F f, Tail tail, T... objs) {
+  return f(tail(objs...));
+}
+} // namespace detail
+template <typename F, typename... Fs>
+constexpr auto compose(F f, Fs... fs) {
+  return [f, tail(fs...)](auto... objs) -> decltype (detail::compose(typename 
is_tuple_invocable<tuple<decltype(objs)...>>::type{}, f, tail, objs...)) {
+    auto unitail =
+        typename is_tuple_invocable<tuple<decltype(objs)...>>::type{};
+    return detail::compose(unitail, f, tail, objs...);
+  };
+}
+template <auto> constexpr auto eq = equal();
+static_assert(compose(eq<3>, plus{})(1, 2));
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope5.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope5.C
new file mode 100644
index 00000000000..48b5ef34c9e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope5.C
@@ -0,0 +1,22 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++17 } }
+
+constexpr auto equal() {
+  int t = 0;
+  return [t](auto obj) { return obj; };
+}
+template <typename F>
+constexpr int bar (F) {
+  return 42;
+}
+
+template <typename F>
+constexpr auto compose(F f)
+{
+  return [f=f](int i) -> decltype(bar (f)) {
+      return 42;
+  };
+}
+template <auto> constexpr auto eq = equal();
+static_assert(compose(eq<3>)(1));
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope6.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope6.C
new file mode 100644
index 00000000000..720c1ec6cba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope6.C
@@ -0,0 +1,20 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++23 } }
+
+void
+g ()
+{
+  /* It looks like this shouldn't work but [expr.prim.lambda.closure]/6
+     says "Otherwise, it is a non-static member function or member function
+     template that is declared const if and only if the lambda-expression's
+     parameter-declaration-clause is not followed by mutable and the
+     lambda-declarator does not contain an explicit object parameter."  */
+  auto counter = [j=0](this auto const& self) -> decltype(j) {
+      return j++;
+  };
+
+  auto counter2 = [j=0](this auto& self) -> decltype(j) {
+      return j++;
+  };
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope7.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope7.C
new file mode 100644
index 00000000000..6db39bd8692
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope7.C
@@ -0,0 +1,20 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++14_only } }
+
+template <typename T>
+inline constexpr auto
+equal1 (T &&t)
+{
+  return [t = 3](const auto& obj) -> decltype(obj == t)
+    {
+      return obj == t;
+    };
+}
+
+void
+g ()
+{
+  constexpr auto l1 = equal1 (5); // { dg-error "not literal" }
+  static_assert (l1 (3), "");          // { dg-error 
"non-constant|non-.constexpr." }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope8.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope8.C
new file mode 100644
index 00000000000..87ad4732380
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope8.C
@@ -0,0 +1,25 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++23 } }
+// { dg-options "-Wshadow" }
+
+void
+bad ()
+{
+  [x=1](int x){};  // { dg-error "declared as a capture" }
+  // { dg-warning "shadows a lambda capture" "" { target *-*-* } .-1 }
+  [x=1]{ int x; };  // { dg-error "shadows a parameter" }
+
+  auto f = [i = 5] () { int i; return 0; }; // { dg-error "shadows a 
parameter" }
+  auto f2 = [i = 5] <int N> () { int i; return 0; };  // { dg-error "shadows a 
parameter" }
+
+  // [expr.prim.lambda.capture]/5
+  int x = 0;
+  auto g = [x](int x) { return 0; };  // { dg-error "declared as a capture" }
+  // { dg-warning "shadows a lambda capture" "" { target *-*-* } .-1 }
+  auto h = [y = 0]<typename y>(y) { return 0; };  // { dg-error "shadows template 
parameter" }
+
+  auto l2 = [i = 0, j = i]() -> decltype(i) { // { dg-error "not declared in this 
scope" }
+    return i;
+  };
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/lambda-scope9.C 
b/gcc/testsuite/g++.dg/cpp23/lambda-scope9.C
new file mode 100644
index 00000000000..b75e97cb89d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/lambda-scope9.C
@@ -0,0 +1,15 @@
+// P2036R3 - Change scope of lambda trailing-return-type
+// PR c++/102610
+// { dg-do compile { target c++23 } }
+// { dg-options "" }
+
+constexpr char f(auto a) { return 'a'; }
+
+namespace A {
+  int i = 42;
+  template<char X = f([i]{})> void g() { } // { dg-warning "capture of variable 
.A::i. with non-automatic storage duration" }
+}
+
+namespace B {
+  void call() { A::g(); }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wshadow-19.C 
b/gcc/testsuite/g++.dg/warn/Wshadow-19.C
index 030aefd153d..d0b8dba8549 100644
--- a/gcc/testsuite/g++.dg/warn/Wshadow-19.C
+++ b/gcc/testsuite/g++.dg/warn/Wshadow-19.C
@@ -1,5 +1,5 @@
  // { dg-do compile }
-// { dg-options "-Wshadow" }
+// { dg-options "-Wshadow -Wpedantic" }
void
  foo (int x)
@@ -10,7 +10,7 @@ foo (int x)
      extern int y;                             // { dg-warning "declaration of 'y' 
shadows a previous local" }
    }
  #if __cplusplus >= 201102L
-  auto fn = [x] () { extern int x; return 0; };        // { dg-warning "declaration of 'x' 
shadows a lambda capture" "" { target c++11 } }
+  auto fn = [x] () { extern int x; return 0; };    // { dg-warning "declaration of 'int x' 
shadows a parameter" "" { target c++11 } }
  #endif
  }
diff --git a/gcc/testsuite/g++.dg/warn/Wshadow-6.C b/gcc/testsuite/g++.dg/warn/Wshadow-6.C
index 1d8d21b9b6f..7b44d52781a 100644
--- a/gcc/testsuite/g++.dg/warn/Wshadow-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wshadow-6.C
@@ -33,8 +33,8 @@ void f2(struct S i, int j) {
void f3(int i) {
   [=]{
-   int j = i;                  // { dg-message "shadowed declaration" }
-   int i;                      // { dg-warning "shadows a lambda capture" }
+   int j = i;                  // { dg-message "previously declared here" }
+   int i;                      // { dg-error "shadows a parameter" }
     i = 1;
   };
  }
@@ -42,8 +42,8 @@ void f3(int i) {
  template <class T>
  void f4(int i) {
   [=]{
-   int j = i;                  // { dg-message "shadowed declaration" }
-   int i;                      // { dg-warning "shadows a " }
+   int j = i;                  // { dg-message "previously declared here" }
+   int i;                      // { dg-error "shadows a parameter" }
     i = 1;
   };
  }

base-commit: 9467435253948b83fcb5f7430f6cd571236960d8

Reply via email to