Reputation: 5044
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.
Upvotes: 5
Views: 1139
Reputation: 137800
The self-answer with exceptions is pretty cool. Here's a self-contained version:
class polymorphic_erasure {
std::function< void() > throw_self;
public:
template< typename static_type >
polymorphic_erasure( static_type & o )
: throw_self( [ & o ] { throw & o; } )
{}
polymorphic_erasure()
: throw_self( [] { throw std::bad_cast(); } )
{}
template< typename want_type >
want_type & get() const {
try {
throw_self();
} catch ( want_type * result ) {
return * result;
} catch ( ... ) {}
throw std::bad_cast();
}
};
Demo: http://coliru.stacked-crooked.com/a/a12114a210c77a45
Note, though, that you can't assign with a polymorphic base type and then get
the derived object — you still need a dynamic_cast
for that. And there's no RTTI or polymorphism here. (Perhaps it needs a different name.) Although exceptions do use RTTI for classes, polymorphic_erasure
only throws pointers. The exception-based functionality is complementary: It classifies the object into a type hierarchy and nothing more.
Upvotes: 1
Reputation: 5044
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();
}
Upvotes: 3
Reputation: 283634
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
Upvotes: 1
Reputation: 6588
what about something like this:
void* data;
class Wrapper{ virtual ~Wrapper()=0; };
template<typename T> class SpecificWrapper: public Wrapper {
public:
T* value;
Wrapper(T* ptr): value(ptr){}
~Wrapper() {}
}
template<typename D>
void store(D&& object)
{
Wrapper* wrapper = new SpecificWrapper<D>(&object);
if(data!= ::std::nullptr)
delete reinterpret_cast<Wrapper*>(data);
data = (void*)wrapper;
}
template<typename B>
B& load()
{
//always safe because we know type being correct
Wrapper *w = reinterpret_cast<Wrapper*>(data);
SpecificWrapper<B> * w1 = dynamic_cast<SpecificWrapper<B>>(w);
if(w1==::std::nullptr) throw ::std::bad_cast{};
return w1->value;
}
The idea is to use a wrapper type hierarchy to do the type erasure while keeping type information. In this way you can statically determine that the type of the data
variable even if it is declared as void*
is always the top level Wrapper
class, allowing you to always perform safe casts. You need to be careful of the lifetime of the wrapper object though...
Upvotes: -1
Reputation: 6659
As far as I know, the only reliable and portable method for determining derivation is to use dynamic_cast in a try catch block. If it's cast-able, it won't throw a bad_cast exception. Do this test in the your store routine, if it doesn't throw, store the data, otherwise set it to NULL. Don't forget to check for that in your load routine.
Upvotes: 0