I received some information concerning boost fusion. The implementation looks a little like what I posted here. I think boost covers most of what I needed to know about heterogeneous containers.
Thanks, James Smith --- On Mon, 10/26/09, Jim Smith <[email protected]> wrote: From: Jim Smith <[email protected]> Subject: [c-prog] Implementing a heterogeneous container in C++ To: [email protected] Date: Monday, October 26, 2009, 6:59 AM Hi, I decided to use some of the techniques explained in 'Modern C++ Design' by Andrei Alexandrescu to create a very simple heterogeneous container. Before reading this book I never considered how this type of container might be implemented. Some input from the group would be appreciated and I hope this posting is a learning experience for anyone not familiar with C++ meta-programming. I learned a lot from actually using the techniques described by Andrei. To implement the container I added data to the Typelist template that allowed it to be useful when instantiated: //This is a typelist as explained in 'Modern C++ Design' with some extra data template <class T, class U> struct Typelist { typedef T Head; typedef U Tail; T data; U NextTypeData; }; NextTypeData is the type of the next typelist. Each typelist has its own type and data it holds. The data contained in each Typelist object is of type T and the instance of that type is T data declared in the Typelist template. As explained in by Andrei in 'Modern C++ Design' the typelist are created by nesting the declarations inside each other: typedef Typelist<int, Typelist< string,Typelist< vector<string> ,Typelist< vector<double> ,NullType> > > > HeteroCont; The NullType is used to break the recursive unfolding of the typelist. The list is unfolded until the NullType specialization for the Typelist is reached. class NullType { }; In order for Typelist to be a container it must be instantiated. The datatypes declared in the template class will be created and they are of the types specified in the declaration: typedef Typelist<int, Typelist< string,Typelist< vector<string> ,Typelist< vector<double> ,NullType> > > > HeteroCont; HeteroCont MyTypes; Each Typelist object created will contain the data object of the type it's associated with in the declaration. This is the variable called 'data' of parameter type T. I guess the most confusing aspect of this implementation is understanding how to manipulate the data in the container. It's not possible to access the data of a specific type without expanding the container until you reach the type you're targeting. After you reach the type, the object of that type is available in the Typelist object. I unfolded the type using recursive calls to a template function. The template function ValueAt(int index,R& r, T& arg) is called recursively until the parameter index reaches 0 or the Typelist object unfolds into the NullType type parameter. The NullType template parameter causes the specialization for NullType to be called and the recursion ends with a return of 0. In addition to the convertibility checking template classes Andrei described and implemented, I created a template class called DoAssign to help with the assignment operations. The NextDataType object inside of the Typelist template is of parameter type T of the Typelist template, which is another Typelist containing the next type in the Typelist (original list of types). Each recursive call to ValueAt expands to the type of next type in the list because NextDataType is of the next type in the typelist chain. template<typename T, typename R> int ValueAt(int index,R& r, T& arg) { const int cv=Conversion< typename T::Head,R>:: exist; if(!index) { DoAssign<R,typename T::Head,cv> assign(r,arg. data); return cv ; } return ValueAt((index- 1),r,arg. NextTypeData) ; } This is the specialization for the NullType type that ends the recursion and returns 0. template<typename R>int ValueAt(int , R& , NullType&) { cerr << " NullType version called for ValueAt template function " << endl ; return 0; } I implemented a template class to help with assignment operations. To set a value of an object, get a reference to an object, or fetch a value from an object in the container an assignment operation is necessary. This is not possible if the objects are not convertible and even if they are convertible the compiler iterates through all types until the target is reached so, the assignment statement used to perform the needed operations more than likely will not always be valid to the compiler. So, the template class DoAssign takes care of this problem by having a specialization that does not perform the assignment if the types are not convertible. DoAssign performs the assignment based on the result of the conversion checking template class described in 'Modern C++ Design'. See ValueAt for an example of how I used DoAssign to perform the assignment. The template class DoAssign keeps the invalid assignment attempts out of the code. template <class D, class T, unsigned int flag> class DoAssign { public: DoAssign(D& dest, T& target) { dest=target; } }; This is the specialization of DoAssign that's used for types that are not convertible. template <class D, class T> class DoAssign<D,T, 0> { public: DoAssign(D& , T& ) { cerr << " Error: conversion not possible" << endl ; } }; I also implemented SetValueAt and GetValueAt template functions that use the same techniques as ValueAt in their definitions. GetValueAt uses another version of template class DoAssign called PtrDoAssign. PtrDoAssign does what DoAssign does, but works with pointers in order to get a reference to objects in the container. Here are the definitions: template <class D, class T, unsigned int flag> class PtrDoAssign { public: PtrDoAssign( D** dest, T* target) { *dest=target; } }; This is the specialization for types that are not convertible. template <class D, class T> class PtrDoAssign< D,T,0> { public: PtrDoAssign( D** , T* ) { cerr << " Error: pointer conversion not possible" << endl ; } }; template<typename V> int GetItemAt(const int , V** , NullType ) { cerr << " GetItemAt NullType called " << endl ; return 0; } template<typename V, typename T> int GetItemAt(const int index, V** ref, T& arg) { const int cv=Conversion< typename T::Head*,V*> ::exist; if(!index) { PtrDoAssign< V,typename T::Head,cv> assign(ref,& arg.data) ; return cv; } return GetItemAt((index- 1),ref, arg.NextTypeData) ; } template<typename V, typename T> int SetValueAt(const int index, V& value, T& arg) { const int cv=Conversion< typename T::Head,V>:: exist; if(!index) { DoAssign<typename T::Head,V,cv> assign(arg.data, value); return cv; } return SetValueAt(( index-1), value, arg.NextTypeData) ; } Best Regards, James Smith [Non-text portions of this message have been removed]
