Search code examples
c++dynamic-casttypeidtypeinfo

Can we determine at runtime if two type_info's would be castable?


Is there a way to determine from two const ::std::type_info objects, let's name them B and D if the type described by D is derived from type B?

I ask because I want to erase the type of an object I get but later on be able to check if it can be safely promoted.

void* data;
const ::std::type_info* D;

template<typename D>
void store(D&& object)
{
    D = &typeid(object);
    data = ::std::addressof(object);
}

template<typename B>
B& load()
{
    // if(typeid(B) != (*D)) throw ::std::bad_cast{};
    return *reinterpret_cast<B*>(data); // <- also problematic
}

I want to be able to use it like this:

class Base {};
class Derived : Base {};

Derived d;
store(d);
// ....
load<Base>();

Thus it is not suitable to just use an equality compare for the typeids. I am pretty sure this could be possible in a similar way that dynamic_cast can figure this out. What I want is that in every case where D& could be assigned to B& allowing B as the type argument of load() - without knowing D at that time.


Solution

  • I found a way to let the compiler and interal mechanism to figure it out for me. I don't have a problem with cross compiling, in that case ::std::type_info isn't consistent, either.

    typedef void (*throw_op)(void*);
    throw_op dataThrow;
    
    template<typename T>
    [[ noreturn ]] void throwing(void* data)
    {
        throw static_cast<T*>(data);
    }
    [[ noreturn ]] void bad_cast()
    {
        throw ::std::bad_cast{};
    }
    
    template<typename B>
    B& poly_load()
    {
        if(data == nullptr)
            bad_cast();
        try {
            dataThrow(data);
        } catch (B* ptr) {
            return *ptr;
        } catch (...) {
            bad_cast();
        }
    }
    

    All you have to do is add the following line to the store operation:

    dataThrow = throwing<D>;
    

    How does this work? It takes advantages of exceptions and how they are caught. Note here that this makes poly_load ?much? slower than the simple load function, thus I'll keep the both around.

    C++ says that when an exception of type D* is being thrown you can catch that exception with a catch-clause B* where B is any ancestor of D.

    Minimal example:

    struct Base {
        virtual ~Base() {}
        virtual void foo() = 0;
    };
    
    struct Derived : public virtual Base {
        void foo() override {
            ::std::cout << "Hello from Derived" << ::std::endl;
        }
    };
    
    int main() {
        Derived d{};
        store(d);
    
        // .....
    
        poly_load<Base>().foo();
    }