PxxxxR0
Extend std::type_info

New Proposal,

This version:
http://virjacode.com/papers/extend_type_info001.htm
Latest version:
http://virjacode.com/papers/extend_type_info.htm
Author:
TPK Healy <healytpk@vir7ja7code7.com> (Remove all sevens from email address)
Audience:
SG18
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21

Abstract

Add more information to std::type_info without causing an ABI break.

Note: This paper is a very early draft just to stir conversation.

1. Introduction

The standard library class, std::type_info, is useful in its current form however it is lacking some vital information. The addition of a member function bases() would return a pair of iterators [first,last) to the type_info for all the base classes.

class type_info_iterator {
public:
  type_info const &operator*(void) const noexcept;
  type_info const *operator->(void) const noexcept;
  type_info_iterator &operator++(void) noexcept;
  ptrdiff_t operator-(type_info_iterator const &rhs) noexcept;
  bool operator!=(type_info_iterator const &rhs) const noexcept;
};

class type_info {
public:
  bool is_polymorphic() const noexcept;
  pair< type_info_iterator, type_info_iterator > bases(void) const noexcept;
};

2. Possible implementation

2.1. GNU g++, LLVM clang, Intel ICX

#include <typeinfo>   // type_info
#include <utility>    // pair
#include <cxxabi.h>   // __cxxabiv1::__base_class_type_info

class type_info_iterator {
    friend class type_info;
    __cxxabiv1::__base_class_type_info const *p;
public:
    type_info const &operator*(void) const noexcept
    {
        return *p->__base_type;
    }
    type_info const *operator->(void) const noexcept
    {
        return &**this;
    }
    type_info_iterator &operator++(void) noexcept
    {
        ++p;
        return *this;
    }
    size_t operator-(type_info_iterator const &rhs) noexcept
    {
        return this->p - rhs.p;
    }
    bool operator!=(type_info_iterator const &rhs) const noexcept
    {
        return this->p != rhs.p;
    }
};

pair<type_info_iterator,type_info_iterator> type_info::bases(type_info const &ti) const noexcept
{
    using namespace __cxxabiv1;
    auto const *const c = dynamic_cast<__class_type_info const *>(&ti);
    if ( nullptr == c ) return {};

    auto const *const sic = dynamic_cast<__si_class_type_info  const *>(&ti);
    
    if ( nullptr != sic )
    {
        pair<type_info_iterator,type_info_iterator> retval;
        auto const *const q = (__cxxabiv1::__base_class_type_info*)sic->__base_type;
        retval.first.p  = q;
        retval.second.p = q + 1;
        return retval;
    }

    auto const *const vmi = dynamic_cast<__vmi_class_type_info  const *>(&ti);

    if ( nullptr != vmi )
    {
        pair<type_info_iterator,type_info_iterator> retval;
        retval.first.p  = vmi->__base_info;
        retval.second.p = vmi->__base_info + vmi->__base_count;
        return retval;
    }

    return {};
}

2.2. Microsoft Visual C++

#include <cassert>     // assert
#include <typeinfo>    // type_info
#include <utility>     // pair
#include <rttidata.h>  // _RTTICompleteObjectLocator, _RTTIClassHierarchyDescriptor

using std::pair, std::type_info;

_RTTICompleteObjectLocator const *type_info_to_locator(type_info const *const pti) noexcept
{
    using std::int32_t, std::uintptr_t;

    // Just in case 'pti' is an unaligned
    // address, subtract bytes to align it
    uintptr_t addr = reinterpret_cast<uintptr_t>(static_cast<void const*>(pti));
    addr -= addr % alignof(uint32_t);

    // Now we have an aligned address
    int32_t const *p = static_cast<int32_t*>(reinterpret_cast<void*>(addr));

    for ( --p; ; --p )
    {
        // If we have found the _RTTICompleteObjectLocator,
        // the pointer is pointing to its last element,
        // so we subtract 20 bytes to bring the pointer
        // to the beginning of the _RTTICompleteObjectLocator
        _RTTICompleteObjectLocator const &locator
            = *(_RTTICompleteObjectLocator*)(p - 5);

        char const *const base = (char*)&locator - locator.pSelf;

        // If we have found the _RTTICompleteObjectLocator,
        // we can calculate where we expect the type_info
        // to be located at:
        char const *const p_expected_type_info = base + locator.pTypeDescriptor;

        // Now let's see if we've really found it
        if ( (char*)pti != p_expected_type_info ) continue;

        // Looks like we found it! But let's just
        // perform a few more checks to be sure:
        if ( COL_SIG_REV1 != locator.signature ) continue;

        // Let's make sure the hierarchy struct
        // is where it's supposed to be
        auto const &hierarchy = *(_RTTIClassHierarchyDescriptor*)(base + locator.pClassDescriptor);

        // Hierarchy struct should always have
        // a signature set to 0
        if ( 0u != hierarchy.signature ) continue;

        // The hierarchy attributes are limited
        // in what they can be set to
        if ( hierarchy.attributes > (CHD_MULTINH|CHD_VIRTINH|CHD_AMBIGUOUS) ) continue;

        // Ok we've done enough checking, we've found the RTTI
        return &locator;
    }
}

class type_info_iterator {
    friend class type_info;
    char const *base  = nullptr;
    int32_t const *pp = nullptr;
public:
    type_info const &operator*(void) const noexcept
    {
        auto const &baseclass = *(_RTTIBaseClassDescriptor*)(base + *pp);
        return *(type_info*)(base + baseclass.pTypeDescriptor);
    }
    type_info const *operator->(void) const noexcept
    {
        return &**this;
    }
    type_info_iterator &operator++(void) noexcept
    {
        ++pp;
        return *this;
    }
    ptrdiff_t operator-(type_info_iterator const &rhs) noexcept
    {
        assert( this->base == rhs.base );
        return this->pp - rhs.pp;
    }
    bool operator!=(type_info_iterator const &rhs) const noexcept
    {
        return !((this->base == rhs.base) && (this->pp == rhs.pp));
    }
};

pair<type_info_iterator,type_info_iterator> type_info::bases(type_info const &ti) const noexcept
{
    _RTTICompleteObjectLocator const &locator = *type_info_to_locator(&ti);
    char *const base = (char*)&locator - locator.pSelf;

    _RTTIClassHierarchyDescriptor const &hierarchy = *(_RTTIClassHierarchyDescriptor*)(base + locator.pClassDescriptor);

    assert( 0u != hierarchy.signature );

    if ( 0 == hierarchy.numBaseClasses ) return {};

    int32_t const *nbaseclass = (int32_t*)(base + hierarchy.pBaseClassArray);
    pair<type_info_iterator,type_info_iterator> retval;
    retval.first .base = retval.second.base = base;
    retval.first .pp   = nbaseclass;
    retval.second.pp   = nbaseclass + hierarchy.numBaseClasses;

    return retval;
}

3. Design considerations

The aim is to add as much information as possible to std::type_info without causing an ABI break on any extant compilers, specifically paying attention to the Itanium ABI (used by GNU, LLVM, Intel) and the Microsoft ABI (used in Visual C++).

4. Proposed wording

The proposed wording is relative to [N4950].

In subclause __________

1 -- I'll write this later

5. Impact on the standard

This proposal is a library extension. The addition has no effect on any other part of the standard.

6. Impact on existing code

No existing code becomes ill-formed. The behaviour of all existing code is unaffected.

References

Normative References

[N4950]
Thomas Köppe. Working Draft, Standard for Programming Language C++. 10 May 2023. URL: https://wg21.link/n4950