See code below for the details, but the underlying scenario is as follows. I have a container (a session) that I can place objects in and pull out from.
Similar to:
std::shared_ptr<Tiger> t = ...;
session.store("tigers/1", t);
std::shared_ptr<Tiger> t2 = session.load<Tiger>("tigers/1");
With both functions defined as:
class Session {
template<class T>
void store(std::string id, std::shared_ptr<T> instance);
template<class T>
std::shared_ptr<T> load(std::string id);
}
Note that a session can store heterogeneous types, but at store
and load
time I statically known what the type of the variable is.
My problem is that I run into a situation where the user wants to put a Tiger
into the session but checks out a base type, instead. For example:
session.load<Animal>("tigers/1");
Right now, I'm effectively storing the data as void*
inside the session and use reinterpret_cast
to get them back to the user provided type. This... works, as long as everything is trivial, but when we get to a slightly more complex situation, we run into issues.
Here is the full code demonstrating my issue:
struct Animal
{
virtual void Pet() const = 0;
};
struct IJumpable
{
virtual void Jump() const = 0;
};
struct Tiger : Animal, IJumpable
{
void Pet() const override
{
std::cout << "Pet\n";
}
void Jump() const override
{
std::cout << "Jump\n";
}
};
int main()
{
auto cat = std::make_shared<Tiger>();
// how the data is stored inside the session
auto any_ptr = std::static_pointer_cast<void>(cat);
// how we get the data out of the session
auto namable = std::static_pointer_cast<IJumpable>(any_ptr);
namable->Jump();
std::cout << std::endl;
}
If you run this code, you'll see that it runs, but instead of calling Jump
, it calls to Pet
. I understand that this is because of the wrong virtual method table being used, since I'm effectively calling reinterpret_cast
on `void*.
My question is if there is a good way to handle this scenario in C++. I've looked around and didn't see anything that matches what I need.
Everything I found about heterogeneous containers always assumed a shared base class, which I don't have nor want. Is this possible?
You could make the user provide you with the correct casting trek to follow:
class Session {
template<class T>
void store(std::string id, std::shared_ptr<T> instance);
template<class T>
std::shared_ptr<T> load(std::string id);
template<class Stored, class Retrieved>
std::shared_ptr<Retrieved> load_as(std::string id) {
auto stored = load<Stored>(id);
return std::static_pointer_cast<Retrieved>(stored);
}
}
This makes a messy usage at the caller site, but the information must come from somewhere:
auto shere_khan = make_shared<Tiger>();
session.store("tigers/1", shere_khan);
auto bagheera = session.load_as<Tiger, IJumpable>("tigers/1");
Solution courtesy of my brother who happens to be a C++ expert with no stackoverflow :)
Here is a void_ptr
implementation that enables polymorphic casting using exception handling to discover types. The performance should be close to that of a dynamic_cast. You should be able to optimize the above using std::type_index
and caching the offsets.
#include <stdio.h>
class void_ptr {
void* obj;
void (*discover_type)(void*);
template<typename T>
static void throw_typed_object(void* obj)
{
T* t = static_cast<T*>(obj);
throw t;
}
public:
void_ptr() : obj(0) {}
template<typename T>
void_ptr(T* t) : obj(t), discover_type(throw_typed_object<T>)
{
}
template<typename T>
T* cast() const
{
try {
discover_type(obj);
} catch(T* t) {
return t;
} catch(...) {
}
return 0;
}
};
struct Animal {
virtual ~Animal() {}
virtual const char* name() { return "Animal"; }
};
struct Speaker {
virtual ~Speaker() {}
virtual const char* speak() { return "hello"; }
};
struct Lion : public Animal, public Speaker {
virtual const char* name() { return "Lion"; }
virtual const char* speak() { return "Roar"; }
};
int main()
{
void_ptr ptr(new Lion());
Animal* a = ptr.cast<Animal>();
Speaker* s = ptr.cast<Speaker>();
printf("%s\n", a->name());
printf("%s\n", s->speak());
}
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