https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127834

            Bug ID: 127834
           Summary: final polymorphic typeid operand treated as
                    unevaluated
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: vladislav.semykin at gmail dot com
  Target Milestone: ---

A glvalue operand of typeid whose type is a final polymorphic class is
treated as an unevaluated operand.  [expr.typeid]/4 does not allow that.
final means the dynamic type of a live object equals the static type.
It does not make the class non-polymorphic, and it does not move the
operand into [expr.typeid]/5.

Two programs that should be ill-formed are accepted.

#include <typeinfo>
#include <utility>

struct F final { virtual ~F(); };

void missing_capture(F& r) {
  // r would be potentially evaluated if the typeid were ignored,
  // so the lambda must capture it.
  [] { (void) typeid(r); };
}

void declval_evaluated() {
  // The call is potentially evaluated, so declval's body is
  // instantiated and its static_assert fires.
  (void) typeid(std::declval<F>());
}


The non-final counterparts are already rejected, and the tests lock
that in:

- gcc/testsuite/g++.dg/cpp0x/pr125886.C, lambda_poly_capture:
  [] { typeid(b); } for non-final B is "b is not captured".
- gcc/testsuite/g++.dg/rtti/typeid14.C: typeid(std::declval<B>())
  for non-final B instantiates std::declval and hits
  static_assert(..., "declval() must not be used!") in
  libstdc++-v3/include/std/type_traits.

For final F both are silent.  pr125886.C final_glvalue documents
the current model in a comment: the operand is unevaluated, so there
is no vtable lookup and no odr-use.  That comment describes what the
front end does.  It is not what [expr.typeid]/4 requires.

Normative text:

- [expr.typeid]/4 (https://eel.is/c++draft/expr.typeid#4): when typeid
  is applied to a glvalue of polymorphic class type, the result is the
  type of the most derived object, and the expression is a
  potentially-evaluated expression (it is not an unevaluated operand).
- [expr.typeid]/5 (https://eel.is/c++draft/expr.typeid#5): the operand
  is an unevaluated operand only in every other case. That is the
  static type.
- [class.virtual]/1 (https://eel.is/c++draft/class.virtual#1): a class
  with a virtual member function is polymorphic. final is not an
  exception.
- [expr.prim.lambda.capture]/7
  (https://eel.is/c++draft/expr.prim.lambda.capture#7): an id-expression
  is potentially evaluated for implicit capture if it would be
  potentially evaluated ignoring any enclosing typeid. The
  missing_capture example is ill-formed even before the evaluation
  question is reached. declval_evaluated is the evaluation question
  itself: the call is potentially evaluated, so the function body is
  instantiated.

Trunk mechanism:

- resolves_to_fixed_type_p (gcc/cp/class.cc) returns 1 immediately
  for CLASSTYPE_FINAL.
- typeid_evaluated_p (gcc/cp/rtti.cc) is then false, so the
  two-pass parse keeps the unevaluated probe and never re-parses the
  operand evaluated.
- build_typeid therefore never treats the operand as evaluated.
  get_tinfo_ptr_dynamic returns the static type_info and does not
  load the vtable.

Using the static type_info for a live final object is a valid as-if
choice: the most derived type is the static type. Treating the operand
as unevaluated is not, because potential evaluation is observable
(declval, odr-use, and the missing-capture diagnostic).

Please keep this separate from the null-pointer bug.  typeid(*p) for
a null F* must throw std::bad_typeid ([expr.typeid]/3) even when
the dynamic type is known statically.  That throw can be fixed without
changing the "fixed type" model.  This report is the model: a final
polymorphic glvalue is still a potentially-evaluated operand.  I am
filing the null-check bug separately and will add its number to See
Also.

Also out of scope here: PR c++/127750, where the unevaluated probe
consumes a lambda when the operand really is evaluated under
[expr.typeid]/4.  That fix checkpoints lambda discriminators around the
probe. It does not change resolves_to_fixed_type_p.

Please assign this report to me.

Reply via email to