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
