PxxxxR0
std::typeid_except

New Proposal,

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

Abstract

Add a new function to the standard library to facilitate the retrieval of the type_info from an exception_ptr.

   type_info const &typeid_except( exception_ptr const &p = current_exception() ) noexcept;
   
  Declared in header <exception>
  Declared in header <typeinfo>

1. Introduction

Inside a catch(...) block, we should have access to the type_info of what was thrown. Currently there is no portable way to get the type_info from a exception_ptr, and so if an object is thrown of intrinsic type or a non-polymorphic class type, and caught in a catch(...) block, the type of the exception object is unknown.

2. Motivation

2.1. catch(...)

A C++ program can link with a shared library which has incomplete or inaccurate documentation for the exceptions it throws, or the program may link at runtime using LoadLibrary or dlopen to link with a library which it knows very little about. Code in the main program may handle an exception thrown from within the shared library as follows:

#include <exception>  // exception, exception_ptr
#include <new>        // bad_alloc
#include <typeinfo>   // typeid, type_info

extern void SomeLibraryFunction(void) noexcept(false);

int main(void)
{
    try
    {
        SomeLibraryFunction();
    }
    catch(std::bad_alloc const&)
    {
        // Attempt safe shutdown when memory is depleted
    }
    catch(std::exception const &e)
    {
        std::type_info const &ti = typeid(e);
        // As 'std::exception' has a virtual destructor, it is
        // a polymorphic type, and so 'typeid' will give us the
        // RTTI of the derived class.
    }
    catch(...)
    {
        std::exception_ptr const p = std::current_exception();
        // We can get a pointer to the exception object, but
        // we have no idea of the type of what was thrown
    }
}

This proposal will allow us to access the type_info of the exception object referred by an exception_ptr, as follows:

    catch(...)
    {
        std::exception_ptr const p = std::current_exception();
        std::type_info const &ti = std::typeid_except(p);
    }

Once we have the type_info, we can perform checks on the name:

    catch(...)
    {
        std::exception_ptr const p = std::current_exception();
        std::type_info const &ti = std::typeid_except(p);
        if ( std::strstr( ti.name(), "FieryShadow" ) ) return EXIT_FAILURE;
    }

The name of the type will of course be mangled and therefore be different on different compilers, but if a class is named 'FieryShadow', then its mangled name will contain 'FieryShadow', hence the function std::strstr will work here. However, until a name mangling library is added to the standard library, code to distinguish specifically between type names (e.g. where one class is inside an anonymous namespace) will be platform-specific and non-portable.

2.2. Runtime plugins

A massive program such as 3D modelling software for a desktop computer may allow the installation of 3rd party plugins. These plugins come in the form of a dynamic shared library (e.g. '.dll' on MS-Windows, or '.so' on Apple-macOS) which is loaded at runtime using LoadLibrary or dlopen, and the plugin must export a list of functions expected by the main program, e.g. Plugin_GetName() and Plugin_GetCategory(). One of these exported functions could be Plugin_GetExceptions() as follows:

   std::type_info const *const *Plugin_GetExceptions(void) noexcept;
   

Plugin_GetExceptions() returns a pointer to a null-terminated array of pointers to the type_info's of bespoke exceptions thrown from the plugin. When the main program first loads the plugin, the main program populates a global array of these exception types, and the main program consults this array when an exception is caught:

catch(...)
{
    std::type_info const &ti = std::typeid_except( std::current_exception() );
    if ( nullptr != plugin_exceptions.find(&ti) )
    {
        // We have caught one of the 'registered' exception types
    }
}

From here, the main program can perform tasks specific to the exception type, such as calling a specific handler function exported by the plugin.

2.3. As a stepping stone

There has been recent discussion about making major additions to std::type_info, for example to record the sizeof, alignof and std::is_polymorphic_v of the type. There is the hope that if this current proposal is accepted, it will be used as a stepping stone for future papers, for example to log unknown exceptions and give a hexdump of the exception object. For example we could get a print-out such as the following:

Type of exception object: 5Dummy
Size of exception object: 32
  Hex: 46726f6773313233000000000000000000000000000000000000000000000000
ASCII: Frogs123  
from the following code:
struct Dummy {
    char buf[32];
    Dummy(void) : buf("Frogs123") {}
};

try
{
    throw Dummy();
}
catch(...)
{
    using std::sizeof_except;
    using std::get_exception_object_from_exception_pointer;

    std::exception_ptr const ep = std::current_exception();
    printf("Type of exception object: %s\n" , typeid_except(ep).name());

    if ( (0u==sizeof_except(ep)) || (-1==sizeof_except(ep)) ) return;
    printf("Size of exception object: %zu\n", sizeof_except(ep));

    char unsigned const *const p = get_exception_object_from_exception_pointer(ep);
    printf("  Hex: ");
    for ( size_t i = 0u; i < sizeof_except(ep); ++i )
    {
        printf("%02x", static_cast<unsigned>(p[i]) );
    }
    printf("\nASCII: ");
    for ( size_t i = 0u; i < sizeof_except(ep); ++i )
    {
        char c = p[i];
        if ( !isprint(c) || isspace(c) ) c = ' ';
        putchar(c);
    }
    putchar('\n');
}

This would greatly aid in logging exceptions in both Debug and Release builds, even where C++ code is linked with another language such as Java or C#.

3. Impact on the standard

This proposal is a pure library extension. It does not require changes to the standard components. Just one short paragraph is to be appended to Section 17.9.7 [support.exception.propagation]. The text addition is less than 30 words, and the addition has no effect on any other part of the standard.

4. Impact on existing code

No existing code becomes ill-formed. The behaviour of all exisiting code is unaffected by this addition to the standard library.

5. Design considerations

For simplicity, the default argument is 'current_exception()' so that it can be used as follows:

try
{
    throw 52.8L;
}
catch(...)
{
    std::type_info const &ti = std::typeid_except();
}

6. Proposed wording

The proposed wording is relative to [N4950].

In section 17.9.7 [support.exception.propagation], append a paragraph:

13   type_info const &typeid_except( exception_ptr const &p = current_exception() ) noexcept;
     Returns: A reference to a ‘type_info’ object for the exception object
              referred by ‘p’ (17.9.7.1), or ‘typeid(void)’ (7.6.1.8)
              if ‘p’ is a null pointer.

7. Acknowledgements

For their feedback and contributions on the mailing list std-proposals@lists.isocpp.org:

Thiago Macieira, Jens Maurer, Jason McKesson, Arthur O'Dwyer, Peter Olsson, Jan Schultke, Lénárd Szolnoki, Matthew Taylor, Ville Voutilainen, Sebastian Wittmeier and the late Edward Catmur (1982-2024).

References

Normative References

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

Informative References

[P0933R1]
Aaryaman Sagar. Runtime type introspection with std::exception_ptr. 7 February 2018. URL: https://wg21.link/p0933r1
[P1066R1]
Mathias Stearn. How to catch an exception_ptr without even try-ing. 6 October 2018. URL: https://wg21.link/p1066r1
[P2927R1]
Arthur O'Dwyer, Gor Nishanov. Inspecting exception_ptr. 14 February 2024. URL: https://wg21.link/p2927r1