C++ Logo

std-proposals

Advanced search

[std-proposals] Use the exception system to check for bases at runtime

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Tue, 2 Apr 2024 16:57:54 +0100
Use Case 1:


You have a variable of type std::any. You want to check if the
contained value is derived from 'std::exception', and if it is, you
want to return the 'what()' string. So you start off writing a
function as follows:

    char const *get_what(std::any const &a)
    {
        void *const pderived = pointer_from_any(a);
        if ( nullptr == pderived ) return "<no info>";
        auto *const pbase = base<std::exception>( pderived, a.type() );
        if ( nullptr == pbase ) return "<no info>";
        return pbase->what();
    }

So first of all, we have an invocation of a function called
"pointer_from_any" to get the address of the contained object, which
on GNU g++ can be coded as follows:

    void *pointer_from_any(std::any const &a)
    {
        if ( false == a.has_value() ) return nullptr;

        auto const manager = (void (*)(int, std::any const*, void*))
*(void**)&a;

        void *retval;

        manager(0, &a, &retval);

        return retval;
    }

Next we have an invocation of a function called 'base', which is a
template function with the following signature:

    template<class Base>
    Base *base(void *const parg, std::type_info const &ti);

You give this function two pieces of information:
    1) The address of an object (can be non-polymorphic or polymorphic)
    2) The type_info of the object

If the object has 'Base' as a base class, it returns a valid pointer
to the base class. Otherwise it returns a nullptr.

I have coded 'base' to make use of the exception handling system. I
looked at how GNU g++ implements 'make_exception_ptr' and
'rethrow_exception', and then I wrote the following function:

    template<class Base>
    Base *base(void *const parg, std::type_info const &ti)
    {
        using namespace __cxxabiv1;
        alignas(std::max_align_t) __cxa_refcounted_exception header =
{}; // all zeroes
        char *pobj = static_cast<char*>(static_cast<void*>(&header + 1));
        header.referenceCount = 123;
        header.exc.exceptionType = const_cast<std::type_info*>(&ti);
        header.exc.exceptionDestructor = [](void*){};
        header.exc.unexpectedHandler =
reinterpret_cast<void(*)(void)>( std::get_terminate() );
        header.exc.terminateHandler = std::get_terminate();
        __GXX_INIT_PRIMARY_EXCEPTION_CLASS(header.exc.unwindHeader.exception_class);
        header.exc.unwindHeader.exception_cleanup =
[](_Unwind_Reason_Code, _Unwind_Exception*){};

        try
        {
            std::rethrow_exception(
*static_cast<std::exception_ptr*>(static_cast<void*>(&pobj)) );
        }
        catch(Base &obj)
        {
            std::ptrdiff_t const delta = pobj - (char*)&obj;
            return (Base*)( (char*)parg - delta );
        }
        catch(...){}

        return nullptr;
    }

And so putting this altogether, what I'm peddling to you today is as follows:
    At runtime, if you give me the address of an object along with its
type_info, I can confirm for you if any given class is one of its base
classes, and if it is, I'll give you a pointer to the base class. The
pointer will be appropriately adjusted in cases of multiple
inheritance.

Here it is working up on GodBolt with GNU g++, LLVM clang++, and Intel ICX:

    https://godbolt.org/z/hvMr5WTK8

I'm hoping it can be coded for Microsoft too.

So a future standard of C++ could have the standalone template
function 'base', and also the class 'std::any' could have a member
function called 'base'.

Received on 2024-04-02 15:58:07