> I generally filter these out by comparing to an earlier baseline testrun
> of my own, but you can also check gcc-testresults.

Ok, thanks, took in account.

> Please name testcases by what they test, not only their PR number, to
> enable testing like that you mention doing.  The PR number can come at
> the end (our usual convention of a sequence number often causes
> rebase issues).

Renamed pr127132.C to typeid-lambda-capture-pr127132.C.

> Instead of a new class, how about making this case a defaulted parameter
> to the cp_unevaluated constructor?

I considered a class derived from cp_unevaluated, but couldn't
find precedent anywhere in the compiler for inheriting from a
small RAII state-guard like this, and didn't want to be the first to
extend cp_unevaluated's contract that way - it's been essentially
unchanged since Andrew Sutton introduced it for Concepts TS
support in 2015 (r971e17ff873), formalized from struct to class by
Martin Sebor in 2019 (r6c1dae73cd2c).  A parallel class seemed
the safer choice at the time.  Your defaulted-parameter suggestion
is simpler than either; done in v4.

> Are there any tests that depend on this?  In general mark_used doesn't
> do much in a template.

No, dropped it.  Reverted to processing_template_decl == 0, the
original PR125886 condition - mark_used and build_typeid both already
no-op unconditionally in any template context regardless of which pass
reaches them, so the extra gate had nothing to add.

> These || are suprising.  They seem to be needed in this patch because
> the in_typeid_probe check is limited to the lambda_expr case.  If we
> instead check it immediately after the capture case, i.e. before

Done, exactly that - moved the in_typeid_probe check right after the
capture attempt and dropped the ||.

> This test isn't reliable; the compiler is allowed to optimize away the
> capture if n it isn't actually odr-used, as in
> https://eel.is/c++draft/expr.prim#lambda.capture-example-4

> I'd actually consider this comparison succeeding to be a (minor)
> missed-optimization bug.  But let's not mess with that in this patch,
> since the assert passed in GCC 16 so fixing the optimization would be a
> lambda ABI change that we'd need to control with -fabi-version.

> So I guess let's keep this test as it is for now, but add a comment

> // If this starts to fail that's OK, the compiler is allowed to optimize
> // away the capture, but we need to control it with -fabi-version.

Added, verbatim.

> > +// [expr.prim.lambda.capture]/7: capture must not depend on whether the
> > +// typeid operand is evaluated.  F is final, so [expr.typeid]/4 would
> > +// leave typeid (f) unevaluated, but f is still captured.
Generic-lambda
> > +// variant of typeid14.C's final_glvalue.
> > +
> > +struct F final { virtual ~F (); };
> > +
> > +void
> > +final_glvalue_generic (F &f)
> > +{
> > +  [=](auto a) { (void) typeid (f); }(1);
> > +}

> This could also call foo(), and/or check the size?

Done. Now calls foo() and checks sizeof (lam) == sizeof (F).

> > +// D::i names no local entity, so [expr.prim.lambda.capture]/7 does not
> > +// apply and a DR613 non-static member id-expression stays unevaluated
> > +// (no capture).  Generic-lambda variant of typeid14.C's nsm.
> > +
> > +struct D { int i; };
> > +
> > +void
> > +nsm_generic ()
> > +{
> > +  [=](auto a) { (void) typeid (D::i); }(1);
> > +}

> This could also check the size.

The same, done. Checks sizeof (lam) == 1 (as expected nothing captured).

> It would also be good to also have tests of 'this' capture, such as if
typeid(D::i) were in a member function of D.

Added this_capture_regardless_of_evaluated and a generic-lambda
variant, using [&] to sidestep the unrelated C++20 warning about
implicit this-capture via [=].

> Also the typeid14.C references seem stale, the current patch no longer
> modifies that file.

Fixed. Those comments actually meant pr125886.C's final_glavlue/nsm
(typeid14.C never had those), corrected.

---

gcc-verify and gcc-style - are ok.
Tested sweep for [
  cpp0x/pr125886.C
  coroutines/unevaluated.C
  rtti/*.C
  cpp0x/constexpr-typeid*.C
  cpp2a/constexpr-typeid*.C
  template/typeid*.C
  cpp0x/lambda/*.C
  cpp1y/*lambda*.C
  cpp2a/*lambda*.C
], all 3553 green.
From 922478b73fbb14cbbc70a994392839413d783212 Mon Sep 17 00:00:00 2001
From: Vladislav Semykin <[email protected]>
Date: Sat, 5 Sep 2026 00:19:35 +0300
Subject: [PATCH v4] c++: fix typeid capture in generic lambdas [PR c++/127132]

[expr.prim.lambda.capture]/7 (P0588R1 / CWG1468) requires a lambda to
capture a local entity if the expression would be potentially evaluated
were the effect of any enclosing typeid expressions ignored.  Capture
must not depend on whether the typeid operand is itself evaluated.

The PR125886 two-pass parse gated the capture-triggering evaluated
re-parse on processing_template_decl == 0.  A generic lambda's own auto
parameter makes that nonzero regardless of the operand, so capture of
an outer polymorphic glvalue was skipped and the resulting uncaptured
decl ICEd at RTL expand (PR127132).

Do not teach typeid_evaluated_p to treat a template context as
evaluated: mark_used must still suppress instantiation while the
operand is genuinely unevaluated (declval<T>()), and
resolves_to_fixed_type_p already returns "fixed" whenever
in_template_context.

Instead, add cp_unevaluated_typeid_cutoff: process_outer_var_ref and
lambda_expr_this_capture suppress capture only when
cp_unevaluated_operand exceeds that cutoff.  A typeid probe passes
typeid_operand=true to cp_unevaluated, which raises the cutoff only
when operand == cutoff at probe start, so sizeof (typeid (b)) stays
suppressed while typeid (typeid (x)) looks through every enclosing
typeid.  Inside the cutoff, capture is attempted as usual, but a
missing capture-default is not diagnosed there - the real
evaluated-ness isn't known yet, and a later, non-probe pass will
diagnose it if the operand turns out evaluated.  Outside a lambda (a
local class, a default argument) the capture attempt has nothing to
attach to and falls through unchanged, so params() in pr125886.C still
sees an unevaluated operand with no extra special-casing.  Every
force-reset site that clears cp_unevaluated_operand also
save/reset/restores the cutoff, including cp_evaluated.

processing_template_decl == 0 turned out to still be the right gate for
the evaluated re-parse itself: mark_used and build_typeid both already
no-op unconditionally in any template context, independent of which
pass hits them, so the two-pass parse needs no extra gate there either.

Changes since v3:
- Jason: fold cp_unevaluated_typeid_operand into cp_unevaluated as a
  defaulted TYPEID_OPERAND parameter.
- Jason: lambda_expr_this_capture was missing the in_typeid_probe
  deferral that process_outer_var_ref has for 'this'.
- Jason: drop the process_outer_var_ref || conditions by checking
  in_typeid_probe right after the capture attempt instead of only
  inside the lambda_expr error case.
- Jason: the instantiation_dependent_expression_p gate on the evaluated
  re-parse isn't needed; reverted to processing_template_decl == 0.
- Tests: renamed pr127132.C to typeid-lambda-capture-pr127132.C
  (named for what it tests, PR at the end); moved the /7 cases back
  out of pr125886.C into it; added foo()/sizeof checks to
  final_glvalue_generic and nsm_generic; added this-capture tests;
  fixed stale typeid14.C references; noted the -fabi-version caveat on
  capture_regardless_of_evaluated.

Changes since v2:
- Jason: capture must ignore enclosing typeid entirely
  ([expr.prim.lambda.capture]/7).  Do not change typeid_evaluated_p or
  mark_used.
- New cutoff + typeid-operand RAII on the probe (parser and
  tsubst_expr).  process_outer_var_ref looks through typeid only for
  lambdas; local classes keep the unevaluated early-return
  (pr125886.C params).
- this-capture and every force-reset of cp_unevaluated_operand follow
  the cutoff, including cp_evaluated.
- Tests: pr127132.C plus /7 cases on pr125886.C, including 10-deep
  nested typeid and the same nest inside sizeof.

Changes since v1:
- Jason: typeid_evaluated_p should err on the side of "evaluated" when
  it can't tell, not try to compute a precise answer.  That v2 approach
  is superseded: capture no longer depends on typeid_evaluated_p.

Bootstrapped on x86_64-pc-linux-gnu.  Targeted g++.dg sweep (rtti,
constexpr-typeid, template/typeid, lambda dirs, pr125886.C,
coroutines/unevaluated.C): clean, 3553 expected passes.  Full CI (make
-k check-c++-all) in progress; will follow up with results.

gcc/cp/ChangeLog:

	PR c++/127132
	* cp-tree.h (saved_scope): Save unevaluated_typeid_cutoff.
	(cp_unevaluated_typeid_cutoff): Declare.
	(cp_unevaluated): Add a defaulted TYPEID_OPERAND parameter; save
	and restore the cutoff.
	(cp_evaluated): Save, reset, and restore the cutoff.
	* parser.cc (cp_unevaluated_typeid_cutoff): Define.
	(cp_unevaluated::cp_unevaluated, cp_unevaluated::~cp_unevaluated):
	Implement TYPEID_OPERAND cutoff handling.
	(cp_parser_postfix_expression): Pass typeid_operand=true for the
	typeid probe.
	* pt.cc (instantiate_class_template): Save/reset/restore the
	cutoff around NSDMI / lambda capture substitution.
	(tsubst_expr): Pass typeid_operand=true for TYPEID_EXPR's probe.
	* semantics.cc (process_outer_var_ref): Allow lambda capture
	through a typeid probe per [expr.prim.lambda.capture]/7; defer
	the "not captured" diagnostic to a later, non-probe pass.
	* lambda.cc (lambda_expr_this_capture): Same cutoff and deferral
	for 'this'.
	* constraint.cc (tsubst_constraint_variables): Save/reset/restore
	the cutoff with cp_unevaluated_operand.
	* name-lookup.cc (push_to_top_level, pop_from_top_level): Same.
	(local_state_t): Same.

gcc/testsuite/ChangeLog:

	PR c++/127132
	* g++.dg/rtti/typeid-lambda-capture-pr127132.C: New test,
	consolidating the original PR127132 reproducer with the /7 cases
	previously added to pr125886.C, plus this-capture coverage and
	closure-size checks.

Signed-off-by: Vladislav Semykin <[email protected]>
---
 gcc/cp/constraint.cc                          |   3 +
 gcc/cp/cp-tree.h                              |  29 ++-
 gcc/cp/lambda.cc                              |  13 +-
 gcc/cp/name-lookup.cc                         |   7 +
 gcc/cp/parser.cc                              |  15 +-
 gcc/cp/pt.cc                                  |   5 +-
 gcc/cp/semantics.cc                           |  14 +-
 .../rtti/typeid-lambda-capture-pr127132.C     | 169 ++++++++++++++++++
 8 files changed, 242 insertions(+), 13 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/rtti/typeid-lambda-capture-pr127132.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index fa271a96282..d0d39da7979 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -1703,9 +1703,12 @@ tsubst_constraint_variables (tree t, tree args, subst_info info)
   /* Clear cp_unevaluated_operand across tsubst so that we get a proper chain
      of PARM_DECLs.  */
   int saved_unevaluated_operand = cp_unevaluated_operand;
+  int saved_unevaluated_typeid_cutoff = cp_unevaluated_typeid_cutoff;
   cp_unevaluated_operand = 0;
+  cp_unevaluated_typeid_cutoff = 0;
   tree vars = tsubst (t, args, info.complain, info.in_decl);
   cp_unevaluated_operand = saved_unevaluated_operand;
+  cp_unevaluated_typeid_cutoff = saved_unevaluated_typeid_cutoff;
   if (vars == error_mark_node)
     return error_mark_node;
   return declare_constraint_vars (t, vars);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 369d2a9119d..05b0d2ac19f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2133,6 +2133,7 @@ struct GTY(()) saved_scope {
   bool expansion_stmt : 1;
 
   int unevaluated_operand;
+  int unevaluated_typeid_cutoff;
   int inhibit_evaluation_warnings;
   int noexcept_operand;
   int ref_temp_count;
@@ -6303,13 +6304,25 @@ extern bool cp_preserve_using_decl;
 
 extern int cp_unevaluated_operand;
 
+/* cp_unevaluated_operand depth up to which lambda capture is still
+   allowed.  A typeid operand's own unevaluated probe raises this so
+   that probe does not by itself suppress capture
+   ([expr.prim.lambda.capture]/7).  */
+
+extern int cp_unevaluated_typeid_cutoff;
+
 /* RAII class used to inhibit the evaluation of operands during parsing
-   and template instantiation.  Evaluation warnings are also inhibited.  */
+   and template instantiation.  Evaluation warnings are also inhibited.
+   TYPEID_OPERAND: this is a typeid operand's own probe, so it should
+   not by itself suppress lambda capture
+   ([expr.prim.lambda.capture]/7) - see cp_unevaluated_typeid_cutoff.  */
 
 class cp_unevaluated
 {
 public:
-  cp_unevaluated ();
+  int saved_cutoff;
+
+  cp_unevaluated (bool typeid_operand = false);
   ~cp_unevaluated ();
 };
 
@@ -6321,13 +6334,19 @@ class cp_evaluated
 public:
   int uneval;
   int inhibit;
+  int typeid_cutoff;
   cp_evaluated (bool reset = true)
-    : uneval(cp_unevaluated_operand), inhibit(c_inhibit_evaluation_warnings)
+    : uneval (cp_unevaluated_operand), inhibit (c_inhibit_evaluation_warnings),
+      typeid_cutoff (cp_unevaluated_typeid_cutoff)
   { if (reset)
-      cp_unevaluated_operand = c_inhibit_evaluation_warnings = 0; }
+      {
+	cp_unevaluated_operand = c_inhibit_evaluation_warnings = 0;
+	cp_unevaluated_typeid_cutoff = 0;
+      } }
   ~cp_evaluated ()
   { cp_unevaluated_operand = uneval;
-    c_inhibit_evaluation_warnings = inhibit; }
+    c_inhibit_evaluation_warnings = inhibit;
+    cp_unevaluated_typeid_cutoff = typeid_cutoff; }
 };
 
 /* in pt.cc  */
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index e0d676cae81..dfb0e472fb5 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -847,8 +847,10 @@ lambda_expr_this_capture (tree lambda, int add_capture_p)
 	this_capture = spec;
       }
 
-  /* In unevaluated context this isn't an odr-use, so don't capture.  */
-  if (cp_unevaluated_operand)
+  /* In unevaluated context this isn't an odr-use, so don't capture.
+     A typeid operand's own probe is capture-transparent
+     ([expr.prim.lambda.capture]/7).  */
+  if (cp_unevaluated_operand > cp_unevaluated_typeid_cutoff)
     add_capture_p = false;
 
   /* If we captured 'this' but don't have a capture proxy yet, look up the
@@ -948,16 +950,19 @@ lambda_expr_this_capture (tree lambda, int add_capture_p)
         }
     }
 
-  if (cp_unevaluated_operand)
+  if (cp_unevaluated_operand > cp_unevaluated_typeid_cutoff)
     result = this_capture;
   else if (!this_capture)
     {
-      if (add_capture_p == 1)
+      if (add_capture_p == 1 && !cp_unevaluated_operand)
 	{
 	  error ("%<this%> was not captured for this lambda function");
 	  result = error_mark_node;
 	}
       else
+	/* Either add_capture_p != 1, or we're inside a typeid operand's
+	   own probe and don't know yet whether it's really evaluated;
+	   defer instead of diagnosing.  */
 	result = NULL_TREE;
     }
   else
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 60a6e6f4430..13cee628faa 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -9069,6 +9069,7 @@ push_to_top_level (void)
   s->need_pop_function_context = need_pop;
   s->function_decl = current_function_decl;
   s->unevaluated_operand = cp_unevaluated_operand;
+  s->unevaluated_typeid_cutoff = cp_unevaluated_typeid_cutoff;
   s->inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
   s->suppress_location_wrappers = suppress_location_wrappers;
   s->x_stmt_tree.stmts_are_full_exprs_p = true;
@@ -9080,6 +9081,7 @@ push_to_top_level (void)
   current_namespace = global_namespace;
   push_class_stack ();
   cp_unevaluated_operand = 0;
+  cp_unevaluated_typeid_cutoff = 0;
   c_inhibit_evaluation_warnings = 0;
   suppress_location_wrappers = 0;
 }
@@ -9112,6 +9114,7 @@ pop_from_top_level (void)
     pop_function_context ();
   current_function_decl = s->function_decl;
   cp_unevaluated_operand = s->unevaluated_operand;
+  cp_unevaluated_typeid_cutoff = s->unevaluated_typeid_cutoff;
   c_inhibit_evaluation_warnings = s->inhibit_evaluation_warnings;
   suppress_location_wrappers = s->suppress_location_wrappers;
 
@@ -9129,6 +9132,7 @@ namespace {
 struct local_state_t
 {
   int cp_unevaluated_operand;
+  int cp_unevaluated_typeid_cutoff;
   int c_inhibit_evaluation_warnings;
   int cp_noexcept_operand_;
   bool has_cfun;
@@ -9139,6 +9143,8 @@ struct local_state_t
     local_state_t s;
     s.cp_unevaluated_operand = ::cp_unevaluated_operand;
     ::cp_unevaluated_operand = 0;
+    s.cp_unevaluated_typeid_cutoff = ::cp_unevaluated_typeid_cutoff;
+    ::cp_unevaluated_typeid_cutoff = 0;
     s.c_inhibit_evaluation_warnings = ::c_inhibit_evaluation_warnings;
     ::c_inhibit_evaluation_warnings = 0;
     s.cp_noexcept_operand_ = ::cp_noexcept_operand;
@@ -9153,6 +9159,7 @@ struct local_state_t
   restore () const
   {
     ::cp_unevaluated_operand = this->cp_unevaluated_operand;
+    ::cp_unevaluated_typeid_cutoff = this->cp_unevaluated_typeid_cutoff;
     ::c_inhibit_evaluation_warnings = this->c_inhibit_evaluation_warnings;
     ::cp_noexcept_operand = this->cp_noexcept_operand_;
     if (this->has_cfun)
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 967ba3fd542..634dd410ff7 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -296,6 +296,12 @@ static FILE *cp_lexer_debug_stream;
    sizeof can be nested.  */
 int cp_unevaluated_operand;
 
+/* cp_unevaluated_operand depth up to which lambda capture is still
+   allowed; raised by a typeid operand's own unevaluated probe so that
+   probe does not by itself suppress capture
+   ([expr.prim.lambda.capture]/7).  */
+int cp_unevaluated_typeid_cutoff;
+
 /* Nonzero if we are parsing a reflect-expression and shouldn't strip
    using-declarations.  */
 bool cp_preserve_using_decl;
@@ -3241,16 +3247,21 @@ static tree cp_parser_omp_loop_nest (cp_parser *, bool *);
 // Unevaluated Operand Guard
 //
 // Implementation of an RAII helper for unevaluated operand parsing.
-cp_unevaluated::cp_unevaluated ()
+cp_unevaluated::cp_unevaluated (bool typeid_operand)
+  : saved_cutoff (cp_unevaluated_typeid_cutoff)
 {
+  bool transparent = (cp_unevaluated_operand == cp_unevaluated_typeid_cutoff);
   ++cp_unevaluated_operand;
   ++c_inhibit_evaluation_warnings;
+  if (typeid_operand && transparent)
+    cp_unevaluated_typeid_cutoff = cp_unevaluated_operand;
 }
 
 cp_unevaluated::~cp_unevaluated ()
 {
   --c_inhibit_evaluation_warnings;
   --cp_unevaluated_operand;
+  cp_unevaluated_typeid_cutoff = saved_cutoff;
 }
 
 // -------------------------------------------------------------------------- //
@@ -8579,7 +8590,7 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
 	       (mark_used -> instantiation; lambda capture).  */
 	    cp_lexer_save_tokens (parser->lexer);
 	    {
-	      cp_unevaluated u;
+	      cp_unevaluated u (/* typeid_operand= */ true);
 	      expression = cp_parser_expression (parser, &idk);
 	    }
 	    /* If we're already within an unevaluated operand, everything
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index dd0efa54112..28845214eed 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12962,6 +12962,7 @@ instantiate_class_template (tree type)
   maybe_diagnose_erroneous_template (t ? TI_TEMPLATE (t) : templ);
 
   int saved_unevaluated_operand = cp_unevaluated_operand;
+  int saved_unevaluated_typeid_cutoff = cp_unevaluated_typeid_cutoff;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
   fn_context = decl_function_context (TYPE_MAIN_DECL (type));
@@ -12973,6 +12974,7 @@ instantiate_class_template (tree type)
   else
     {
       cp_unevaluated_operand = 0;
+      cp_unevaluated_typeid_cutoff = 0;
       c_inhibit_evaluation_warnings = 0;
     }
 
@@ -13390,6 +13392,7 @@ instantiate_class_template (tree type)
       /* Restore these before substituting into the lambda capture
 	 initializers.  */
       cp_unevaluated_operand = saved_unevaluated_operand;
+      cp_unevaluated_typeid_cutoff = saved_unevaluated_typeid_cutoff;
       c_inhibit_evaluation_warnings = saved_inhibit_evaluation_warnings;
     }
 
@@ -23098,7 +23101,7 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	    tree operand;
 	    tree uneval;
 	    {
-	      cp_unevaluated u;
+	      cp_unevaluated u (/* typeid_operand= */ true);
 	      uneval = RECUR (operand_0);
 	    }
 	    /* If we're already within an unevaluated operand, everything
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index bb952e2f62c..f1a52b196a4 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4682,15 +4682,21 @@ tree
 process_outer_var_ref (tree decl, tsubst_flags_t complain,
 		       bool odr_use/*=false*/)
 {
+  bool in_typeid_probe = false;
   if (cp_unevaluated_operand)
     {
       tree type = TREE_TYPE (decl);
       if (!dependent_type_p (type)
 	  && variably_modified_type_p (type, NULL_TREE))
 	/* VLAs are used even in unevaluated context.  */;
-      else
+      else if (cp_unevaluated_operand > cp_unevaluated_typeid_cutoff)
 	/* It's not a use (3.2) if we're in an unevaluated context.  */
 	return decl;
+      else
+	/* Inside a typeid operand's own probe: capture is still
+	   attempted below ([expr.prim.lambda.capture]/7), but a missing
+	   capture-default must not be diagnosed yet - see below.  */
+	in_typeid_probe = true;
     }
   if (decl == error_mark_node)
     return decl;
@@ -4777,6 +4783,12 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain,
       if (flag_contracts && processing_contract_condition)
 	set_contract_capture_flag (decl, true);
     }
+  /* Capture was attempted above; whether it's really needed depends on
+     an evaluated-ness we don't know yet.  Defer instead of diagnosing:
+     a later, non-probe pass will complain if the operand turns out
+     evaluated, and there's nothing to complain about if it doesn't.  */
+  else if (in_typeid_probe)
+    return var;
   /* Only an odr-use of an outer automatic variable causes an
      error, and a constant variable can decay to a prvalue
      constant without odr-use.  So don't complain yet.  */
diff --git a/gcc/testsuite/g++.dg/rtti/typeid-lambda-capture-pr127132.C b/gcc/testsuite/g++.dg/rtti/typeid-lambda-capture-pr127132.C
new file mode 100644
index 00000000000..ccb426db8a4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/typeid-lambda-capture-pr127132.C
@@ -0,0 +1,169 @@
+// PR c++/127132: typeid inside a generic lambda's capture-default failed
+// to capture, because capture incorrectly depended on whether the
+// typeid operand was evaluated ([expr.typeid]/4-5), rather than on
+// [expr.prim.lambda.capture]/7 (P0588R1 / CWG1468), which says capture
+// must ignore the effect of any enclosing typeid.  Regression against
+// the PR c++/125886 fix (r17-2200-g621661f7d2e9).
+// { dg-do compile { target c++14 } }
+// c++14 target: several cases below use a generic lambda's auto
+// parameter, valid from C++14 on.
+
+#include <typeinfo>
+
+void foo(const std::type_info &type);
+
+// [expr.typeid]/4: *c is a polymorphic glvalue, so typeid's operand is
+// evaluated, and the generic lambda's by-copy capture-default must
+// capture c for that use.  On trunk this capture is currently missing,
+// leaving a decl reference with nothing behind it once the lambda's
+// call operator is expanded - crashing at RTL expand time instead of
+// compiling cleanly.
+
+class C {
+  virtual ~C();
+};
+
+void
+bar (C *c)
+{
+  [=](auto a) { foo(typeid(*c)); }(1);
+}
+
+// [expr.prim.lambda.capture]/7 (P0588R1 / CWG1468): capture must ignore
+// the effect of enclosing typeid expressions.  n is not a polymorphic
+// glvalue, so the operand is unevaluated ([expr.typeid]/5), but n is
+// still captured.
+//
+// If this starts to fail that's OK, the compiler is allowed to optimize
+// away the capture, but we need to control it with -fabi-version.
+
+void
+capture_regardless_of_evaluated (int n)
+{
+  auto lam = [=] { (void) typeid (n); };
+  static_assert (sizeof (lam) == sizeof (int), "");
+}
+
+// Generic-lambda variant.
+
+void
+capture_regardless_of_evaluated_generic (int n)
+{
+  auto lam = [=](auto a) { (void) typeid (n); };
+  lam (0);
+  static_assert (sizeof (lam) == sizeof (int), "");
+}
+
+// No capture-default and a genuinely unevaluated operand: must not
+// diagnose "not captured".
+
+void
+no_capture_default_non_poly (int n)
+{
+  [] { (void) typeid (n); };
+}
+
+// [expr.prim.lambda.capture]/7: capture must not depend on whether the
+// typeid operand is evaluated.  F is final, so [expr.typeid]/4 leaves
+// typeid (f) unevaluated, but f is still captured.  Generic-lambda
+// variant of pr125886.C's final_glvalue.
+
+struct F final { virtual ~F (); };
+
+void
+final_glvalue_generic (F &f)
+{
+  auto lam = [=](auto a) { foo (typeid (f)); };
+  lam (1);
+  static_assert (sizeof (lam) == sizeof (F), "");
+}
+
+// D::i names no local entity, so [expr.prim.lambda.capture]/7 does not
+// apply and a DR613 non-static member id-expression stays unevaluated
+// (no capture).  Generic-lambda variant of pr125886.C's nsm.
+
+struct D { int i; };
+
+void
+nsm_generic ()
+{
+  auto lam = [=](auto a) { foo (typeid (D::i)); };
+  lam (1);
+  static_assert (sizeof (lam) == 1, "");
+}
+
+// sizeof (typeid (b)) is a genuinely unevaluated context wrapping
+// typeid, so the cutoff stays unraised and b is not captured.
+
+struct B { virtual ~B (); };
+
+void
+nested_unevaluated_lambda_size (B &b)
+{
+  auto lam = [=] { (void) sizeof (typeid (b)); };
+  static_assert (sizeof (lam) == 1, "");
+}
+
+// Nested typeid looks through every enclosing typeid
+// ([expr.prim.lambda.capture]/7, "any enclosing typeid expressions").
+
+void
+nested_typeid_capture (int x)
+{
+  auto lam = [=] { (void) typeid (typeid (x)); };
+  static_assert (sizeof (lam) == sizeof (int), "");
+}
+
+// Extremely nested typeid: look-through on each probe
+// (operand == cutoff -> cutoff += 1).
+
+void
+nested_typeid_capture_extreme (int x)
+{
+  auto lam = [=] {
+    (void) typeid (typeid (typeid (typeid (typeid (typeid (typeid (typeid (typeid (typeid (typeid (x)))))))))));
+  };
+  static_assert (sizeof (lam) == sizeof (int), "");
+}
+
+// sizeof does not raise the cutoff, so all 10 typeid probes stay above
+// it and x is not captured.
+
+void
+nested_typeid_capture_extreme_v2 (int x)
+{
+  auto lam = [=] { (void) sizeof (typeid (typeid (typeid (typeid (typeid (typeid (typeid (typeid (typeid (typeid (x))))))))))); };
+  static_assert (sizeof (lam) == 1, "");
+}
+
+// this-capture variant of capture_regardless_of_evaluated:
+// [expr.prim.lambda.capture]/7 applies to *this like any other local
+// entity.  [&] avoids the (unrelated) C++20 deprecation warning for
+// implicit this-capture via [=].
+
+struct E {
+  int i;
+  void this_capture_regardless_of_evaluated ();
+};
+
+void
+E::this_capture_regardless_of_evaluated ()
+{
+  auto lam = [&] { (void) typeid (this->i); };
+  static_assert (sizeof (lam) == sizeof (E *), "");
+}
+
+// Generic-lambda variant.
+
+struct E2 {
+  int i;
+  void this_capture_regardless_of_evaluated_generic ();
+};
+
+void
+E2::this_capture_regardless_of_evaluated_generic ()
+{
+  auto lam = [&](auto a) { (void) typeid (this->i); };
+  lam (1);
+  static_assert (sizeof (lam) == sizeof (E2 *), "");
+}
-- 
2.43.0

Reply via email to