You probably want to use templates to do type elision. Here's an example that may be similar to what you're looking for. The ObjectDatabase
class uses templates and polymorphism internally to do type elision so the classes used don't have any constraints on them (other than the normal constraints for being placed in a standard library container).
#include <iostream>
#include <typeinfo>
#include <deque>
#include <map>
#include <cassert>
using namespace std;
struct ObjectDatabase {
ObjectDatabase() { }
template<typename T>
T &allocate() {
deque<T> &a = getObjects<T>();
a.push_back(T());
return a.back();
}
template<typename T>
deque<T> &getObjects() {
CollectionBase *&el = m_obdb[typeid(T).name()];
if ( not el )
el = new Collection<T>();
Collection<T> *elc = dynamic_cast<Collection<T>*>(el);
assert(elc);
deque<T> &a = elc->elements;
return a;
}
~ObjectDatabase() {
for ( ObDB::iterator i=m_obdb.begin(); i!=m_obdb.end(); ++i)
delete i->second;
}
private:
ObjectDatabase(ObjectDatabase const &);
ObjectDatabase &operator=(ObjectDatabase const &);
struct CollectionBase {
virtual ~CollectionBase() { }
};
template<typename T>
struct Collection : CollectionBase {
deque<T> elements;
};
typedef map<string, CollectionBase *> ObDB;
ObDB m_obdb;
};
struct Foo {
Foo() : name("Generic Foo") { }
char const *name;
};
struct Bar {
string name;
};
int main() {
ObjectDatabase obdb;
obdb.allocate<Foo>().name = "My First Foo";
obdb.allocate<Bar>().name = "My First Bar";
{
Foo &f = obdb.allocate<Foo>();
f.name = "My Second Foo";
Bar &b = obdb.allocate<Bar>();
b.name = "My Second Bar";
}
obdb.allocate<Foo>();
obdb.allocate<Bar>();
{
cout << "Printing Foo Names\n";
deque<Foo> &foos = obdb.getObjects<Foo>();
for ( deque<Foo>::iterator i = foos.begin(); i!=foos.end(); ++i )
cout << " -> " << i->name << "\n";
}
{
cout << "Printing Bar Names\n";
deque<Bar> &bars = obdb.getObjects<Bar>();
for ( deque<Bar>::iterator i = bars.begin(); i!=bars.end(); ++i )
cout << " -> " << i->name << "\n";
}
}
When I run this program, I get this output:
Printing Foo Names
-> My First Foo
-> My Second Foo
-> Generic Foo
Printing Bar Names
-> My First Bar
-> My Second Bar
->
This shows that the individual objects are stored in containers specific to their own type. You'll notice that Foo
and Bar
are nothing special, just regular aggregates. (Foo
would even be a POD if it weren't for its default constructor.)
======== EDIT ========
If you don't want to use RTTI, you need to get rid of the typeid
and dynamic_cast
.
Getting rid of the dynamic_cast
is fairly simple --- you don't actually need it. You can use static_cast
instead; you just can't check that the derived type is correct with the assert()
anymore. (But if the type was wrong, it would be a bug anyway.)
The typeid
is a bit trickier, since that is used to construct an identifier to differentiate between different concrete types. But you can use some template magic and static objects to replace the string
(from type_info::name()
) with a simple void const *
pointer:
template<typename T>
struct TypeTag {
static char const tag;
};
template<typename T>
char const TypeTag<T>::tag = '\0';
template<typename T>
void const *get_typemarker() {
return &TypeTag<T>::tag;
}
Now we can use get_typemarker<T>()
to return a void const *
key into the map. We change the type of ObDB
's key from string
to void const *
and replace typeid(T).name()
with get_typemarker<T>()
. I've tested it and it gives the same output in my test program as the RTTI-enabled version.