This v2 handles the non-template case via reparse. Two cases are left for
follow-up, and I'd appreciate your guidance on whether they belong in this
PR or separately:

1. Template-dependent operand: the decision needs to happen at instantiation
time in tsubst (TYPEID_EXPR). My first attempt with an unconditional
cp_unevaluated there regressed typeid10.C (PR c++/25466), so it needs the
same type-dependent two-pass logic.
2. Polymorphic glvalue inside a lambda: should be odr-used and captured,
but currently isn't.

For completeness: the template case is broken on current trunk in the same
way - wrongly triggers static_assert, just like the non-template case did
before this patch, here is the link: https://godbolt.org/z/Thnf6Mrx1


пт, 26 июн. 2026 г. в 21:22, Vladislav Semykin <[email protected]
>:

> > Thanks, but please post this to gcc-patches so that others can comment
> too.
>
> Yes, of course, sorry for that.
>
> пт, 26 июн. 2026 г. в 21:19, Marek Polacek <[email protected]>:
>
>> On Fri, Jun 26, 2026 at 08:59:33PM +0300, Vladislav Semykin wrote:
>> > Hello!
>> > This v2 handles the non-template case via reparse. Two cases are left
>> for
>> > follow-up, and I'd appreciate your guidance on whether they belong in
>> this
>> > PR or separately:
>>
>> Thanks, but please post this to gcc-patches so that others can comment
>> too.
>>
>> > 1. Template-dependent operand: the decision needs to happen at
>> instantiation
>> > time in tsubst (TYPEID_EXPR). My first attempt with an unconditional
>> > cp_unevaluated there regressed typeid10.C (PR c++/25466), so it needs
>> the
>> > same type-dependent two-pass logic.
>>
>> I suspect this should be part of the patch.
>>
>> > 2. Polymorphic glvalue inside a lambda: should be odr-used and captured,
>> > but currently isn't.
>>
>> This probably doesn't have to be though.
>>
>> > For completeness: the template case is broken on current trunk in the
>> same
>> > way - wrongly triggers static_assert, just like the non-template case
>> did
>> > before this patch, here is the link: https://godbolt.org/z/Thnf6Mrx1
>> >
>> >
>> >
>> > чт, 25 июн. 2026 г. в 08:25, Vladislav Semykin <
>> [email protected]
>> > >:
>> >
>> > > Thanks for the review!
>> > > You're right, I missed the polymorphic case. I'll look into parsing
>> the
>> > > operand twice using tentative parsing, similar to sizeof.
>> > >
>> > > чт, 25 июн. 2026 г. в 00:21, Marek Polacek <[email protected]>:
>> > >
>> > >> On Wed, Jun 24, 2026 at 11:00:39PM +0300, Vladislav Semykin wrote:
>> > >> >
>> > >>
>> > >> > From 9b1d5ef0c5ad93636bbf1dc22c6007b7d935e44c Mon Sep 17 00:00:00
>> 2001
>> > >> > From: Vladislav Semykin <[email protected]>
>> > >> > Date: Wed, 24 Jun 2026 22:47:58 +0300
>> > >> > Subject: [PATCH] cp: fix unevaluated operand context for typeid
>> > >> >
>> > >> > PR c++/125886 - std::declval in unevaluated typeid operand
>> incorrectly
>> > >> triggers __declval_protector
>> > >> >
>> > >> >       PR c++/125886
>> > >> >       PR 125886
>> > >>
>> > >> Only the first PR line should be here.
>> > >>
>> > >> > gcc/cp/ChangeLog:
>> > >> >
>> > >> >       * cp-tree.h (cp_unevaluated_operand): Mention typeid in
>> comment.
>> > >> >       * parser.cc (cp_parser_postfix_expression): Set
>> > >> cp_unevaluated_operand
>> > >> > and c_inhibit_evaluation_warnings before parsing typeid expression
>> > >> operand.
>> > >> >
>> > >> > gcc/testsuite/ChangeLog:
>> > >> >
>> > >> >       * g++.dg/cpp0x/pr125886.C: New test.
>> > >> >
>> > >> > Signed-off-by: Vladislav Semykin <[email protected]>
>> > >> > ---
>> > >> >  gcc/cp/cp-tree.h                      | 5 +++--
>> > >> >  gcc/cp/parser.cc                      | 5 +++++
>> > >> >  gcc/testsuite/g++.dg/cpp0x/pr125886.C | 8 ++++++++
>> > >> >  3 files changed, 16 insertions(+), 2 deletions(-)
>> > >> >  create mode 100644 gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> > >> >
>> > >> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>> > >> > index 132139f9d..25021c62d 100644
>> > >> > --- a/gcc/cp/cp-tree.h
>> > >> > +++ b/gcc/cp/cp-tree.h
>> > >> > @@ -6339,8 +6339,9 @@ extern bool comparing_contracts;
>> > >> >  extern bool cp_preserve_using_decl;
>> > >> >
>> > >> >  /* Nonzero if we are parsing an unevaluated operand: an operand to
>> > >> > -   sizeof, typeof, or alignof.  This is a count since operands to
>> > >> > -   sizeof can be nested.  */
>> > >> > +   sizeof, typeof, alignof, or typeid (when the operand is not
>> > >> > +   of polymorphic class type).  This is a count since operands
>> > >> > +   to sizeof can be nested.  */
>> > >> >
>> > >> >  extern int cp_unevaluated_operand;
>> > >> >
>> > >> > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
>> > >> > index 8194106c6..d426ff655 100644
>> > >> > --- a/gcc/cp/parser.cc
>> > >> > +++ b/gcc/cp/parser.cc
>> > >> > @@ -8571,8 +8571,13 @@ cp_parser_postfix_expression (cp_parser
>> *parser,
>> > >> bool address_p, bool cast_p,
>> > >> >         {
>> > >> >           tree expression;
>> > >> >
>> > >> > +      /* As for sizeof and noexcept before parsing.  */
>> > >> > +      ++cp_unevaluated_operand;
>> > >> > +      ++c_inhibit_evaluation_warnings;
>> > >> >           /* Look for an expression.  */
>> > >> >           expression = cp_parser_expression (parser, & idk);
>> > >> > +      --cp_unevaluated_operand;
>> > >> > +      --c_inhibit_evaluation_warnings;
>> > >>
>> > >> Unfortunately this doesn't look correct: as per [expr.typeid], only
>> typeid
>> > >> applied to a non-polymorphic expression is unevaluated.  When the
>> operand
>> > >> is a glvalue of polymorphic class type, it is evaluated.  This test
>> should
>> > >> still emit an error:
>> > >>
>> > >> class A { virtual ~A(); };
>> > >> void foo() { char const *a = typeid(std::declval<A>()).name(); }
>> > >>
>> > >> I haven't tried it but we may have to parse the operand twice: first
>> > >> as an unevaluated operand and, in case of a polymorphic class type,
>> > >> a second time, this time as evaluated operand.  But at this point
>> it's
>> > >> just my guess.
>> > >>
>> > >> >           /* Compute its typeid.  */
>> > >> >           postfix_expression = build_typeid (expression,
>> > >> tf_warning_or_error);
>> > >> >           /* Look for the `)' token.  */
>> > >> > diff --git a/gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> > >> b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> > >> > new file mode 100644
>> > >> > index 000000000..70db1e89e
>> > >> > --- /dev/null
>> > >> > +++ b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> > >> > @@ -0,0 +1,8 @@
>> > >> > +/* { dg-do compile } */
>> > >> > +/* { dg-options "-std=c++11" } */
>> > >> > +
>> > >> > +#include <typeinfo>
>> > >> > +#include <utility>
>> > >> > +
>> > >> > +class A {};
>> > >> > +void foo() { char const *a = typeid(std::declval<A>()).name(); }
>> > >> > --
>> > >> > 2.43.0
>> > >> >
>> > >>
>> > >>
>> > >> Marek
>> > >>
>> > >>
>>
>> > From aed304a308a9d33191285b0f23a45a2ad7070b75 Mon Sep 17 00:00:00 2001
>> > From: Vladislav Semykin <[email protected]>
>> > Date: Fri, 26 Jun 2026 20:50:15 +0300
>> > Subject: [PATCH] cp: fix unevaluated operand context for typeid [PR
>> >  c++/125886]
>> >
>> > Signed-off-by: Vladislav Semykin <[email protected]>
>> > ---
>> >  gcc/cp/cp-tree.h                        |  4 ++-
>> >  gcc/cp/parser.cc                        | 41 ++++++++++++++++++++++---
>> >  gcc/testsuite/g++.dg/cpp0x/pr125886-2.C | 10 ++++++
>> >  gcc/testsuite/g++.dg/cpp0x/pr125886.C   | 21 +++++++++++++
>> >  4 files changed, 71 insertions(+), 5 deletions(-)
>> >  create mode 100644 gcc/testsuite/g++.dg/cpp0x/pr125886-2.C
>> >  create mode 100644 gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> >
>> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>> > index 132139f9d..ccf2a5f5b 100644
>> > --- a/gcc/cp/cp-tree.h
>> > +++ b/gcc/cp/cp-tree.h
>> > @@ -6340,7 +6340,9 @@ extern bool cp_preserve_using_decl;
>> >
>> >  /* Nonzero if we are parsing an unevaluated operand: an operand to
>> >     sizeof, typeof, or alignof.  This is a count since operands to
>> > -   sizeof can be nested.  */
>> > +   sizeof can be nested.  For typeid, the unevaluated/evaluated
>> > +   decision depends on the operand type ([expr.typeid]/4-5) and is
>> > +   taken in cp_parser_postfix_expression (RID_TYPEID) via reparse.  */
>> >
>> >  extern int cp_unevaluated_operand;
>> >
>> > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
>> > index 8194106c6..08793e470 100644
>> > --- a/gcc/cp/parser.cc
>> > +++ b/gcc/cp/parser.cc
>> > @@ -292,7 +292,10 @@ static void missing_template_diag
>> >  static FILE *cp_lexer_debug_stream;
>> >
>> >  /* Nonzero if we are parsing an unevaluated operand: an operand to
>> > -   sizeof, typeof, or alignof.  */
>> > +   sizeof, typeof, or alignof.  This is a count since operands to
>> > +   sizeof can be nested.  For typeid, the unevaluated/evaluated
>> > +   decision depends on the operand type ([expr.typeid]/4-5) and is
>> > +   taken in cp_parser_postfix_expression (RID_TYPEID) via reparse.  */
>> >  int cp_unevaluated_operand;
>> >
>> >  /* Nonzero if we are parsing a reflect-expression and shouldn't strip
>> > @@ -8569,10 +8572,40 @@ cp_parser_postfix_expression (cp_parser
>> *parser, bool address_p, bool cast_p,
>> >       /* Otherwise, fall back to the expression variant.  */
>> >       else
>> >         {
>> > -         tree expression;
>> > +      cp_lexer_save_tokens (parser->lexer);
>> > +      ++cp_unevaluated_operand;
>> > +      ++c_inhibit_evaluation_warnings;
>> > +      tree expression = cp_parser_expression(parser, &idk);
>> > +      --c_inhibit_evaluation_warnings;
>> > +      --cp_unevaluated_operand;
>> > +
>> > +      bool need_eval = false;
>> > +      if (expression != error_mark_node
>> > +            && processing_template_decl == 0)
>> > +        {
>> > +        /* Reuse the same predicate as build_typeid (rtti.cc) so
>> > +           there is a single source of truth for the [expr.typeid]/4
>> > +           condition.  */
>> > +          int nonnull = 0;
>> > +          tree t = TREE_TYPE (expression);
>> > +          need_eval = (CLASS_TYPE_P (t)
>> > +                        && TYPE_POLYMORPHIC_P (t)
>> > +                        && !resolves_to_fixed_type_p (expression,
>> &nonnull)
>> > +                        && !nonnull
>> > +                        /* Only glvalues are evaluated.  */
>> > +                        /* prvalues are unevaluated even for
>> > +                           polymorphic types.  */
>> > +                        && glvalue_p (expression));
>> > +        }
>> > +      if (need_eval)
>> > +        {
>> > +          /* Roll back to unevaluated parse.  */
>> > +          cp_lexer_rollback_tokens (parser->lexer);
>> > +          expression = cp_parser_expression (parser, &idk);
>> > +        }
>> > +      else
>> > +        cp_lexer_commit_tokens (parser->lexer);
>> >
>> > -         /* Look for an expression.  */
>> > -         expression = cp_parser_expression (parser, & idk);
>> >           /* Compute its typeid.  */
>> >           postfix_expression = build_typeid (expression,
>> tf_warning_or_error);
>> >           /* Look for the `)' token.  */
>> > diff --git a/gcc/testsuite/g++.dg/cpp0x/pr125886-2.C
>> b/gcc/testsuite/g++.dg/cpp0x/pr125886-2.C
>> > new file mode 100644
>> > index 000000000..f5e565fc0
>> > --- /dev/null
>> > +++ b/gcc/testsuite/g++.dg/cpp0x/pr125886-2.C
>> > @@ -0,0 +1,10 @@
>> > +/* { dg-do compile } */
>> > +/* { dg-options "-std=c++11" } */
>> > +
>> > +#include <typeinfo>
>> > +#include <utility>
>> > +
>> > +// Polymorphic glvalue: [expr.typeid]/4 evaluated -> declval<B>() is
>> > +// instantiated and __declval_protector's static_assert fires.
>> > +class B { virtual ~B(); };
>> > +void bar() { char const *b = typeid(std::declval<B>()).name(); } // {
>> dg-error "static assertion failed: declval" "" { target *-*-* } 0 }
>> > diff --git a/gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> > new file mode 100644
>> > index 000000000..9e9673d02
>> > --- /dev/null
>> > +++ b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
>> > @@ -0,0 +1,21 @@
>> > +/* { dg-do compile } */
>> > +/* { dg-options "-std=c++11" } */
>> > +
>> > +#include <typeinfo>
>> > +#include <utility>
>> > +
>> > +// 1. Non-polymorphic: [expr.typeid]/5 unevaluated -> declval<A>() is
>> ok.
>> > +class A {};
>> > +void foo() { char const *a = typeid(std::declval<A>()).name(); }
>> > +
>> > +// 2. Non-polymorphic lambda operand: unevaluated, no capture needed.
>> > +void qux(int n) { [&]{ typeid(n); }; }
>> > +
>> > +// 3. final-class polymorphic glvalue: resolves_to_fixed_type_p ->
>> static
>> > +// typeid, unevaluated (no vtable lookup, no odr-use).
>> > +class F final { virtual ~F(); };
>> > +void fin(F& f) { typeid(f); }
>> > +
>> > +// TODO: The template-dependent operand and polymorphic
>> > +//       lambda-capture cases are not yet handled and
>> > +//       will be addressed in a follow-up.
>> > --
>> > 2.43.0
>> >
>>
>>
>> Marek
>>
>>
From aed304a308a9d33191285b0f23a45a2ad7070b75 Mon Sep 17 00:00:00 2001
From: Vladislav Semykin <[email protected]>
Date: Fri, 26 Jun 2026 20:50:15 +0300
Subject: [PATCH] cp: fix unevaluated operand context for typeid [PR
 c++/125886]

Signed-off-by: Vladislav Semykin <[email protected]>
---
 gcc/cp/cp-tree.h                        |  4 ++-
 gcc/cp/parser.cc                        | 41 ++++++++++++++++++++++---
 gcc/testsuite/g++.dg/cpp0x/pr125886-2.C | 10 ++++++
 gcc/testsuite/g++.dg/cpp0x/pr125886.C   | 21 +++++++++++++
 4 files changed, 71 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/pr125886-2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/pr125886.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 132139f9d..ccf2a5f5b 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6340,7 +6340,9 @@ extern bool cp_preserve_using_decl;
 
 /* Nonzero if we are parsing an unevaluated operand: an operand to
    sizeof, typeof, or alignof.  This is a count since operands to
-   sizeof can be nested.  */
+   sizeof can be nested.  For typeid, the unevaluated/evaluated
+   decision depends on the operand type ([expr.typeid]/4-5) and is
+   taken in cp_parser_postfix_expression (RID_TYPEID) via reparse.  */
 
 extern int cp_unevaluated_operand;
 
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 8194106c6..08793e470 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -292,7 +292,10 @@ static void missing_template_diag
 static FILE *cp_lexer_debug_stream;
 
 /* Nonzero if we are parsing an unevaluated operand: an operand to
-   sizeof, typeof, or alignof.  */
+   sizeof, typeof, or alignof.  This is a count since operands to
+   sizeof can be nested.  For typeid, the unevaluated/evaluated
+   decision depends on the operand type ([expr.typeid]/4-5) and is
+   taken in cp_parser_postfix_expression (RID_TYPEID) via reparse.  */
 int cp_unevaluated_operand;
 
 /* Nonzero if we are parsing a reflect-expression and shouldn't strip
@@ -8569,10 +8572,40 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
 	/* Otherwise, fall back to the expression variant.  */
 	else
 	  {
-	    tree expression;
+      cp_lexer_save_tokens (parser->lexer);
+      ++cp_unevaluated_operand;
+      ++c_inhibit_evaluation_warnings;
+      tree expression = cp_parser_expression(parser, &idk);
+      --c_inhibit_evaluation_warnings;
+      --cp_unevaluated_operand;
+
+      bool need_eval = false;
+      if (expression != error_mark_node
+            && processing_template_decl == 0)
+        {
+        /* Reuse the same predicate as build_typeid (rtti.cc) so
+           there is a single source of truth for the [expr.typeid]/4
+           condition.  */
+          int nonnull = 0;
+          tree t = TREE_TYPE (expression);
+          need_eval = (CLASS_TYPE_P (t)
+                        && TYPE_POLYMORPHIC_P (t)
+                        && !resolves_to_fixed_type_p (expression, &nonnull)
+                        && !nonnull
+                        /* Only glvalues are evaluated.  */
+                        /* prvalues are unevaluated even for 
+                           polymorphic types.  */
+                        && glvalue_p (expression));
+        }
+      if (need_eval)
+        {
+          /* Roll back to unevaluated parse.  */
+          cp_lexer_rollback_tokens (parser->lexer);
+          expression = cp_parser_expression (parser, &idk);
+        }
+      else
+        cp_lexer_commit_tokens (parser->lexer);
 
-	    /* Look for an expression.  */
-	    expression = cp_parser_expression (parser, & idk);
 	    /* Compute its typeid.  */
 	    postfix_expression = build_typeid (expression, tf_warning_or_error);
 	    /* Look for the `)' token.  */
diff --git a/gcc/testsuite/g++.dg/cpp0x/pr125886-2.C b/gcc/testsuite/g++.dg/cpp0x/pr125886-2.C
new file mode 100644
index 000000000..f5e565fc0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/pr125886-2.C
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c++11" } */
+
+#include <typeinfo>
+#include <utility>
+
+// Polymorphic glvalue: [expr.typeid]/4 evaluated -> declval<B>() is
+// instantiated and __declval_protector's static_assert fires.
+class B { virtual ~B(); };
+void bar() { char const *b = typeid(std::declval<B>()).name(); } // { dg-error "static assertion failed: declval" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/pr125886.C b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
new file mode 100644
index 000000000..9e9673d02
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c++11" } */
+
+#include <typeinfo>
+#include <utility>
+
+// 1. Non-polymorphic: [expr.typeid]/5 unevaluated -> declval<A>() is ok.
+class A {};
+void foo() { char const *a = typeid(std::declval<A>()).name(); }
+
+// 2. Non-polymorphic lambda operand: unevaluated, no capture needed.
+void qux(int n) { [&]{ typeid(n); }; }
+
+// 3. final-class polymorphic glvalue: resolves_to_fixed_type_p -> static
+// typeid, unevaluated (no vtable lookup, no odr-use).
+class F final { virtual ~F(); };
+void fin(F& f) { typeid(f); }
+
+// TODO: The template-dependent operand and polymorphic 
+//       lambda-capture cases are not yet handled and
+//       will be addressed in a follow-up.
-- 
2.43.0

Reply via email to