Class views on an associative container

Hi list, I'm looking for a "multi-class container", with access to sub-containers of elements depending on their class. All elements are derived from the same base class, and I want to have "views" of containers of final classes, or even intermediate classes. This container would be associative, like a map. As instance if I have // Base class class A {} ; // Intermediate classes class B : public A {} ; // Final classes class C : public B {} ; class D : public B {} ; class E : public A {} ; I want a container that can hold any A-derived class, on which I could have views of sub containers of class C, D or E, and even a sub container of intermediate class B. I looked at multi-index, but no such feature seems to be feasible. And I have no need of multiple indices, as I access all theses elements by their ID which is a member of A. Any hint on how I could implement this? Cheers, SeskaPeel.

SeskaPeel wrote:
Hi list,
I'm looking for a "multi-class container", with access to sub-containers of elements depending on their class. All elements are derived from the same base class, and I want to have "views" of containers of final classes, or even intermediate classes. This container would be associative, like a map.
As instance if I have
// Base class
class A {} ;
// Intermediate classes
class B : public A {} ;
// Final classes
class C : public B {} ;
class D : public B {} ;
class E : public A {} ;
I want a container that can hold any A-derived class,
That is easy.
on which I could have views of sub containers of class C, D or E, and even a sub container of intermediate class B.
It's really hard to parse your requirements, Could you out-line the datastructure in code, please? -Thorsten

It's really hard to parse your requirements, Could you out-line the datastructure in code, please?
Then I'll explain what I have today, and how I want to improve it. First of all, to add a bit of context, I'm working on a 3D engine. There are a lot of "resources", being Mesh, Character, Camera, Material, etc. I have a very classic class hierarchy around these: * Resource: base class, holds the ID of the element (std::string) * Material: directly derives from Resource * GeomElem: derives from Resource, that is the base class for geometric elements, mainly holds the PRS matrix * Camera: derives from GeomElem, the PRS matrix is the point of view for the current frame * RenderElem: derives from GeomElem, intermediate class for geometric elements that will be rendered, holds a pointer to a Material and a bounding sphere because from this class we're talking of "volumetric elements" * Mesh: derives from RenderElem, holds triangles, points, etc. * Character: derives from RenderElem, has a bone hierarchy for skeletal animations And so on. As you understood, Resource is my class A, Material my class B, RenderElem my class C, Mesh my class D, and Character my class E. On these resource, I want to be able to query them by ID, delete them, add them, etc. And I want to be able to provide the final class type (like GetResource<Mesh>("MyMeshName") to have a faster lookup, or an intermediate class, like RenderElem, and have a lookup only in the RenderElem derived classes. That last feature with the intermediate class is what I am missing at this point. For the implementation details of my current version: * I have a simple wrapper to holds all my strongly typed resources, called ResourceHolder, that contains a std::map<const std::string *, Resource *>. The key is a pointer to the std::string contained in the value, that is the actual name of the Resource. * At startup, I "register" all my resources types using a std::string as type ID. I have a singleton class: template <class T> struct TypeToString { std::string m_Name ; } ; and I instanciate that template for all my Resource derived classes. This is useful for the next point along with another structure that helps me construct my correctly typed Resources at loading time. * Then I have a container of ResourceHolder, that is called ResourceManager, that holds every resource for the application. Everything the developer can do is implemented in ResourceManager. Mainly the GetResource function has a template override that accepts the type as template argument. This way, I can use the TypeToString singleton to get the std::string corresponding to the type, and thus find the ResourceHolder containing all the resources of this type. Then I lookup using the name of the resource in the correct ResourceHolder to find the desired Resource. If I don't use the template version of GetResource, then I will iterate over ALL the ResourceHolders contained in the ResourceManager. So, I want an über-container that will perform all these lookups using no type, the final type, or possibly the intermediate type. And I'll need access to views of these sub-containers, because often I need to use some for_each algorithm on a specific type (ask all Materials to load their textures, ask all RenderElems to prepare for rendering,...). This is something I can perform only on final types at this point. I don't like my structure, and would be pleased to get rid of it in benefit of a better one. But if I don't find a way, I'll implement some kind of TypeToString hierarchy so that I'll be able to know all derived types from an intermediate one. As a future improvement, I'll have to use some kind of "scope" in this structure to sort Resources by "levels" (and I'll have to get rid of the std::string as IDs too). Any clue on that improvement would be mostly appreciated too. Hope this clears things up, SeskaPeel. -----Message d'origine----- De : boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] De la part de Thorsten Ottosen Envoyé : mercredi 26 avril 2006 18:29 À : boost@lists.boost.org Objet : Re: [boost] Class views on an associative container SeskaPeel wrote:
Hi list,
I'm looking for a "multi-class container", with access to sub-containers of elements depending on their class. All elements are derived from the same base class, and I want to have "views" of containers of final classes, or even intermediate classes. This container would be associative, like a map.
As instance if I have
// Base class
class A {} ;
// Intermediate classes
class B : public A {} ;
// Final classes
class C : public B {} ;
class D : public B {} ;
class E : public A {} ;
I want a container that can hold any A-derived class,
That is easy.
on which I could have views of sub containers of class C, D or E, and even a sub container of intermediate class B.
-Thorsten _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

SeskaPeel <seskapeel <at> gmail.com> writes:
Hi list,
I'm looking for a "multi-class container", with access to sub-containers of elements depending on their class. All elements are derived from the same base class, and I want to have "views" of containers of final classes, or even intermediate classes. This container would be associative, like a map.
As instance if I have
// Base class
class A {} ;
// Intermediate classes
class B : public A {} ;
// Final classes
class C : public B {} ;
class D : public B {} ;
class E : public A {} ;
I want a container that can hold any A-derived class, on which I could have views of sub containers of class C, D or E, and even a sub container of intermediate class B.
I looked at multi-index, but no such feature seems to be feasible. And I have no need of multiple indices, as I access all theses elements by their ID which is a member of A.
Hello, With a little imagination, you can use Boost.MultiIndex, and specially the key extraction framework it provides, to achieve something similar to what you want (if I'm getting you right): ******BEGIN CODE****** #include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/identity.hpp> #include <boost/multi_index/member.hpp> #include <boost/multi_index/composite_key.hpp> #include <boost/shared_ptr.hpp> #include <iostream> #include <typeinfo> #include <utility> struct A { A(int id):id(id){} virtual ~A(){} int id; }; struct B:A { B(int id):A(id){} }; struct C:A { C(int id):A(id){} }; struct D:A { D(int id):A(id){} }; struct E:A { E(int id):A(id){} }; using namespace boost::multi_index; template<typename Base> struct type_info_extractor { typedef std::type_info result_type; template<typename T> const std::type_info& operator()(const T& t)const { // identity<> is used so that we can accept chained // pointers to Base as argument. identity<Base> i; return typeid(i(t)); } }; struct type_info_compare { bool operator()( const std::type_info& x,const std::type_info& y)const { return x.before(y); } }; typedef multi_index_container< boost::shared_ptr<A>, indexed_by< // This index sorts by typeid, then by id. // Use it to extract subviews based on the // class of the element. ordered_non_unique< composite_key< A, type_info_extractor<A>, member<A,int,&A::id> >, composite_key_compare< type_info_compare, std::less<int> > >, // Regular index which does not differentiate // between element typeid's. ordered_non_unique< member<A,int,&A::id> >
multi_class_container;
template<typename InputIterator> static void dump(InputIterator first,InputIterator last) { while(first!=last){ std::cout<<(*first++)->id<<" "; } std::cout<<std::endl; } template<typename InputIterator> static void dump( const std::pair<InputIterator,InputIterator>& p) { dump(p.first,p.second); } int main() { typedef boost::shared_ptr<A> element_ptr; // populate the container multi_class_container m; m.insert(element_ptr(new A(0))); m.insert(element_ptr(new A(1))); m.insert(element_ptr(new A(2))); m.insert(element_ptr(new B(3))); m.insert(element_ptr(new B(4))); m.insert(element_ptr(new C(5))); m.insert(element_ptr(new C(6))); m.insert(element_ptr(new D(7))); m.insert(element_ptr(new E(8))); m.insert(element_ptr(new E(9))); // extract the different views based on typeid std::cout<<"A: ";dump(m.equal_range(typeid(A))); std::cout<<"B: ";dump(m.equal_range(typeid(B))); std::cout<<"C: ";dump(m.equal_range(typeid(C))); std::cout<<"D: ";dump(m.equal_range(typeid(D))); std::cout<<"E: ";dump(m.equal_range(typeid(E))); // do a normal query without discriminating typeid's std::cout<<"not less than five: "; dump(m.get<1>().lower_bound(5),m.get<1>().end()); } ******END CODE****** The idea is that composite_key<> can be used to have the elements sorted first by their class type, then by id, so that subviews are easily achieved by looking up for the typeid of the class you're interested in. If you still need typeid-agnostic semantics, just add additional indices like the second index in the example above. What this approach can't do is to produce subviews of intermediate types as you also request, since typeid(x) returns a type_info object referring to the final class of x. Does this help? Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Joaquin M Lopez Munoz <joaquin <at> tid.es> writes:
Hello,
With a little imagination, you can use Boost.MultiIndex, and specially the key extraction framework it provides, to achieve something similar to what you want (if I'm getting you right):
A small update: actually, you can write type_info_extractor in a more direct way: template<typename Base> struct type_info_extractor { typedef std::type_info result_type; const std::type_info& operator()(const Base& x)const { return typeid(x); } }; because composite_key<> takes care itself of the chained ptr dereferencing stuff needed to go from shared_ptr<A> to const A&. Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

What this approach can't do is to produce subviews of intermediate types as you also request, since typeid(x) returns a type_info object referring to the final class of x.
Does this help?
Well, it's better than my current version. Still it lacks the intermediate type views, and that's a must. So it helps showing me that I should have used multi_index from the start, but not how I can add this feature. Yet it's really nice to have an elaborate answer. After thinking a bit more on the "scope" improvement I was explaining in my last post, it turns out this is something I'll need to implement right now. So I'll add an even more complex feature to this container, that would be to add some kind of path (say GamesIDo/MyFirstGame/Level1) so that resources could be shared among several levels (as instance a character reaching the end of a level ... the entire level would have to be unloaded, whereas the character would need to be kept, so the character resource would be in GamesIDo/MyFirstGame/). Queries on this container could take an additional input path to speed things up. This is something actually missing for our whole industry. Cheers, SeskaPeel.

Seska Peel ha escrito:
What this approach can't do is to produce subviews of intermediate types as you also request, since typeid(x) returns a type_info object referring to the final class of x.
Does this help?
Well, it's better than my current version. Still it lacks the intermediate type views, and that's a must.
Yesterday night I was playing a little more with this, and come up with a way to have intermediate types too: the problem with sorting elements by type_info::before() is that the resulting order has nothing to do with the base-derived relations among the types involved. What we need to query intermediate types is a type ordering which packs a given type and its descendants together, in formal terms, if T<Q where Q is not derived from T and T' is derived from T then T'<Q. This is impossible to achieve unless the user explicitly marks the base-derivation relations in the hierarchy (the essential reason being that type_info does not provide an API to query for derivation.) So, the attached code (compiled with GCC 3.2) exercises the idea. Instead of the simple type_info_extractor, we use now a more elaborate extractor named hierarchical_type_order_extractor whose associated ordering respects hierarchy as described above. Base-derivation info must be provided by the user at run-time, like the following: // inform that B is derived from A // invoke before first use of hierarchical_type_order hierarchical_type_order::declare_base_and_derived<A,B>(); Using this new ordering, we can query for final types as before: // list all elements of type B dump(m.equal_range(typeid(B))); and *also* for intermediate types: // list all elements of type B or derived from B dump(m.equal_range(is_a(typeid(B)))); Note the is_a(...) construct. This kind of magic relies in B.MI support for so-called *compatible sorting criteria*: http://boost.org/libs/multi_index/doc/tutorial.html#special_lookup Two caveats: 1. This only works with single-inheritance hierarchies. I don't know if it can be extended to multiple inheritance, probably not. 2. Note that in the example the index uses a composite key on (type order, id). When you query for a final type, the resulting elements are thus sorted by id: equal_range(typeid(B)) --> 3 4 equal_range(typeid(C)) --> 5 6 equal_range(typeid(C1)) --> 10 equal_range(typeid(D)) --> 7 equal_range(typeid(E)) --> 8 9 But when you use intermediate types, the id order is not necessarily preserved: // B and derived types are: B, C, C1, D, E // note the "misplaced" 10! equal_range(is_a(typeid(B))) --> 3 4 5 6 10 7 8 9 If you think about it, there is no way to have one single index correctly id-sorted for every final type and also for every intermediate type. This unfortunately means that, although you can do this: // list all B's with id==5 equal_range(make_tuple(typeid(B),5)); you *cannot* do this: // wrong: try to list all B-or-derived elements with id==5 equal_range(make_tuple(is_a(typeid(B)),5)); Other than these two caveats, I think this approach could serve your purposes.
After thinking a bit more on the "scope" improvement I was explaining in my last post, it turns out this is something I'll need to implement right now. So I'll add an even more complex feature to this container, that would be to add some kind of path (say GamesIDo/MyFirstGame/Level1) so that resources could be shared among several levels (as instance a character reaching the end of a level ... the entire level would have to be unloaded, whereas the character would need to be kept, so the character resource would be in GamesIDo/MyFirstGame/). Queries on this container could take an additional input path to speed things up.
The path thing can be plugged into B.MI by designing an appropriate extractor and taking advantage of the compatible sorting criteria stuff referred to above. Then, you can use it on its own index or combined with other extractors via composite_key. If you decide to go this way and implement such a path-based key extraction code, don't hesitate to contact me, privately or thru the list, for further assistance. Best, Joaquín M López Muñoz Telefónica, Investigación y Desarrollo
participants (5)
-
Joaquin M Lopez Munoz
-
Joaquín Mª López Muñoz
-
Seska Peel
-
SeskaPeel
-
Thorsten Ottosen