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.
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();
}
Actually, you should be using an equality test on your type_info
instances.
reinterpret_cast
provides no guarantees except when casting back to the exact original type. Even
Derived* d = get_derived();
Base* b = reinterpret_cast<Base*>(d);
will not give a correct result (if the Base
subobject is not stored at offset zero within Derived
, which is guaranteed only for standard-layout types).
The complete rule is found in section 5.2.10:
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue
v
of object pointer type is converted to the object pointer type "pointer tocv
T
", the result isstatic_cast<
cv
T*>(static_cast<
cv
void*>(v))
. Converting a prvalue of type "pointer toT1
" to the type "pointer toT2
” (whereT1
andT2
are object types and where the alignment requirements ofT2
are no stricter than those ofT1
) and back to its original type yields the original pointer value.
Only static_cast
and dynamic_cast
can perform base subobject adjustments, and those don't kick in when either type is void*
(after type erasure).
However, it appears that the Boost developers have worked out all the difficulties. See boost::variant::polymorphic_get
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With