Bug 103600 - Cannot use typeid result in constant expressions
Summary: Cannot use typeid result in constant expressions
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 12.0
: P3 normal
Target Milestone: 12.0
Assignee: Jakub Jelinek
URL:
Keywords: rejects-valid
: 102551 (view as bug list)
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2021-12-07 09:38 UTC by Jonathan Wakely
Modified: 2022-11-03 13:29 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-12-07 00:00:00


Attachments
gcc12-pr103600.patch (1.57 KB, patch)
2021-12-07 16:44 UTC, Jakub Jelinek
Details | Diff
libstdc++: Implement P1328 "Making std::type_info::operator== constexpr" (2.85 KB, patch)
2021-12-08 00:30 UTC, Jonathan Wakely
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Jonathan Wakely 2021-12-07 09:38:27 UTC
This should compile:

#include <typeinfo>
#if __cpp_lib_constexpr_typeinfo
constexpr bool b = typeid(int) == typeid(long);
#else
constexpr bool b = &typeid(int) == &typeid(long);
#endif

But GCC trunk (12.0.0 20211125) says:

ti.C:5:33: error: '(((const std::type_info*)(& _ZTIi)) == ((const std::type_info*)(& _ZTIl)))' is not a constant expression
    5 | constexpr bool b = &typeid(int) == &typeid(long);
      |                    ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~



This blocks http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1328r1.html (Making std::type_info::operator== constexpr) 

With the library changes in place compiling with -std=gnu++23 gives:

ti.C:3:32:   in 'constexpr' expansion of '((const std::type_info*)(& _ZTIi))->std::type_info::operator==(_ZTIl)'
ti.C:3:46: error: accessing value of '_ZTIi' through a 'const std::type_info' glvalue in a constant expression
    3 | constexpr bool b = typeid(int) == typeid(long);
      |                                              ^



Using clang++ -std=c++2b the same patched libstdc++ code gives:

ti.C:3:16: error: constexpr variable 'b' must be initialized by a constant expression
constexpr bool b = typeid(int) == typeid(long);
               ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/jwakely/gcc/latest/lib/gcc/x86_64-pc-linux-gnu/12.0.0/../../../../include/c++/12.0.0/typeinfo:195:9: note: read of object 'typeid(int).__name' whose value is not known
    if (__name == __arg.__name)
        ^
ti.C:3:32: note: in call to '&typeid(int)->operator==(typeid(long))'
constexpr bool b = typeid(int) == typeid(long);
                               ^

Which presumably means that the RTTI for built-in types (defined in libsupc++/fundamental_type_info.cc) needs to be made visible during constant evaluation.
Comment 1 Andrew Pinski 2021-12-07 09:51:51 UTC
I think this is a dup of bug 102551.
Comment 2 Andrew Pinski 2021-12-07 09:52:16 UTC
Or at least the 
constexpr bool b = &typeid(int) == &typeid(long);

example.
Comment 3 Jonathan Wakely 2021-12-07 10:01:24 UTC
Ah yes thanks. The requirement to make the RTTI visible during constant evaluation is new info.
Comment 4 Jakub Jelinek 2021-12-07 13:23:25 UTC
We handle e.g.
constexpr auto x = &typeid(int) == &typeid(int);
or
int a, b;
constexpr auto y = &a == &b;
The former by the (cmp @0 @0) folding in match.pd, the latter by
the address_compare match.pd patterns.
But, we already punt on e.g.
template <int N>
inline int a = N;
constexpr auto c = &a<0> == &a<1>;
because address_compare -> equal_address_to punts on those, decl_binds_to_current_def_p is false (they are comdat and can end up being defined from some other TU etc.).
And similarly it punts in the
&typeid(int) == &typeid(long)
case, neither _ZTIi nor _ZTIl are defined in the current TU (both are defined in libstdc++) and the code attempts to play safe, say if the actual definitions would be aliases of each other etc.

I guess at least for constexpr evaluation we want to have some C++ rules, both for typeid vars - dunno if we can rely on the get_tinfo_decl_direct created vars to be always different if they aren't the same VAR_DECL, or if we need to e.g. compute the name and compare those, or for stuff like inline vars or variable templates.  And the question is if cxx_eval_binary_expression should for EQ_EXPR/NE_EXPR repeat what the match.pd address_compare simplification does with its own address_compare, or if it should temporarily set some langhook and
let address_compare use that langhook to handle the special cases, or if a langhook should handle some of those cases always for C++.

Another case is the typeid(int) == typeid(long) comparison, operator== is I think
    bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
    {
      return ((__name == __arg.__name)                        
              || (__name[0] != '*' &&                         
                  __builtin_strcmp (__name, __arg.__name) == 0));
    }
so we'd need to be able to constant fold during constexpr evaluation typeid(int).__name to &_ZTSi when the typeinfo isn't defined locally and
handle &_ZTSwhatever similarly to &_ZTIwhatever, but further we even need to constant fold reading from those strings even when they aren't local..
Comment 5 Jonathan Wakely 2021-12-07 13:47:30 UTC
The new definition of operator== will be something like:

#if __GXX_TYPEINFO_EQUALITY_INLINE || __cplusplus > 202002L
  _GLIBCXX23_CONSTEXPR inline bool
  type_info::operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
  {
    if (__name == __arg.__name)
      return true;

    if (!std::__is_constant_evaluated())
      return false;

#if !__GXX_TYPEINFO_EQUALITY_INLINE
    // ABI requires comparisons to be non-inline.
    return __equal(__arg);
#elif !__GXX_MERGED_TYPEINFO_NAMES
    // Need to do string comparison.
    return __name[0] != '*' && __builtin_strcmp (__name, __arg.name()) == 0;
#else
    return false;
#endif
  }
# endif


i.e. no strcmp for the constant evaluation case. During constant evaluation I think we only need to handle types that are completely defined in the current TU, so we can ignore aliasing of _ZTi symbols, and we can ignore the problem of non-unique std::type_info objects. Within the TU they will be unique.
Comment 6 Jakub Jelinek 2021-12-07 16:44:22 UTC
Created attachment 51945 [details]
gcc12-pr103600.patch

Untested patch that should make typeid(x) == typeid(y) and &typeid(x) == &typeid(y) work, but don't want to duplicate <typeinfo>, so for a testcase I'd need to wait (at least for the former case) until the libstdc++ change lands.
Comment 7 Jonathan Wakely 2021-12-08 00:30:58 UTC
Created attachment 51946 [details]
libstdc++: Implement P1328 "Making std::type_info::operator== constexpr"

Jakub, that patch works for me. Here's the libstdc++ patch to make operator== constexpr, which passes with your patch applied.
Comment 8 GCC Commits 2022-01-03 10:24:31 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:134442b2178a164ed4580255a0de007dda19b855

commit r12-6183-g134442b2178a164ed4580255a0de007dda19b855
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Mon Jan 3 11:21:00 2022 +0100

    c++: Support &typeid(x) == &typeid(y) and typeid(x) == typeid(y) in constant evaluation [PR103600]
    
    If the tinfo vars are emitted in the current TU, they are emitted at the end
    of the compilation, and for some types they are exported from
    libstdc++/libsupc++ and not emitted in the current TU at all.
    
    The following patch allows constant folding of comparisons of typeid
    addresses and makes it possible to implement P1328R1 - making type_info
    operator== constexpr (Jonathan has a patch for that).
    
    As mentioned in the PR, the varpool/middle-end code is trying to be
    conservative with address comparisons of different vars if those vars
    don't bind locally, because of possible aliases in other TUs etc.
    and so while match.pd folds &typeid(int) == &typeid(int) because
    it is equality comparison with the same operands, for different typeids
    it doesn't fold it.
    
    On Wed, Dec 08, 2021 at 08:53:03AM -0500, Jason Merrill wrote:
    > Would it make sense to assume that DECL_ARTIFICIAL variables can't be
    > aliases?  If not, could we have some way of marking a variable as
    > non-aliasing, perhaps an attribute?
    
    I think DECL_ARTIFICIAL vars generally can overlap.
    
    The following patch adds a GCC internal attribute "non overlapping"
    and uses it in symtab_node::equal_address_to.
    Not sure what plans has Honza in that area and whether it would be useful
    to make the attribute public and let users assert that some variable will
    never overlap with other variables, won't have aliases etc.
    
    > During constant evaluation, the operator== could compare the type_info
    > address instead of the __name address, reducing this to the previous
    > problem.
    
    Ah, indeed, good idea.  FYI, clang++ seems to constant fold
    &typeid(x) != &typeid(y) already, so Jonathan could use it even for
    clang++ in the constexpr operator==.  But it folds even
    extern int &a, &b;
    constexpr bool c = &a != &b;
    regardless of whether some other TU has
    int a;
    int b __attribute__((alias (a));
    or not.
    
    2022-01-03  Jakub Jelinek  <jakub@redhat.com>
    
            PR c++/103600
    gcc/
            * symtab.c (symtab_node::equal_address_to): Return 0 if one of
            VAR_DECLs has "non overlapping" attribute and rs1 != rs2.
    gcc/c-family/
            * c-attribs.c (handle_non_overlapping_attribute): New function.
            (c_common_attribute_table): Add "non overlapping" attribute.
    gcc/cp/
            * rtti.c (get_tinfo_decl_direct): Add "non overlapping" attribute
            to DECL_TINFO_P VAR_DECLs.
    gcc/testsuite/
            * g++.dg/cpp0x/constexpr-typeid2.C: New test.
Comment 9 Jakub Jelinek 2022-01-03 10:34:49 UTC
Sorry for the delay, I've missed Honza's
https://gcc.gnu.org/pipermail/gcc-patches/2021-December/586501.html
mail (didn't reach my mailbox but is in the archives).
Comment 10 Patrick Palka 2022-01-06 14:49:04 UTC
*** Bug 102551 has been marked as a duplicate of this bug. ***
Comment 11 Patrick Palka 2022-11-03 13:29:06 UTC
Thus fixed since GCC 12