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]

Reply via email to