C++ Type lookup table

Started by
6 comments, last by evolutional 17 years, 5 months ago
First off, some code from my experimental entity database system:- ValueData

struct IValueData
{
    virtual ValueDataType get_type() const = 0;
    virtual ~IValueData() { }
};

template < typename T >
class ValueData : public IValueData
{
public:
    explicit ValueData( T a_data, ValueDataType a_type );

    virtual const T get_data() { return m_value; }
    virtual void set_data( const T a_data ) { m_value = a_data; }

    ValueDataType get_type() const { return m_type; }

};

typedef boost::shared_ptr<IValueData>   IValueDataPtr;


And then the generic Value structures:-

class Value
{
public:
    explicit Value();    
    explicit Value( IValueDataPtr a_data );
   // explicit Value( const Value &a_src );
    virtual ~Value() { }

    IValueDataPtr get_data();
    void set_data( IValueDataPtr a_data );

    ValueDataType get_datatype() const;
};


I have created override 'helper' classes to allow easy use of typed data:- Example String:

class ValueData_String : public ValueData<std::string>
{
public:
    ValueData_String( const std::string &a_value ) : ValueData<std::string>( a_value, VT_STRING ) { }
    virtual ~ValueData_String() { }
};


ValueDataType is an int that allows the system to store a 'type' associated with the data (note that VT_STRING is just an int). The idea is that users can register their own types with the system and provided they adhere to the interface, the system can query attributes of them transparently. I want to be able to compare the values of two types. For this purpose I have created an interface and implemented it using templates:-

class ITest
	{
	public:
		virtual bool_t test( IValueDataPtr a_lhs, IValueDataPtr a_rhs ) = 0;
	};

	typedef boost::shared_ptr<ITest>   ITestPtr;

	template < class T1, class T2 >
	class EqualityTest : public ITest//< T1, T2 >
	{
	public:
		bool_t test( IValueDataPtr a_lhs, IValueDataPtr a_rhs )
		{
			T1 *v_left = (T1*)a_lhs.get();
			T2 *v_right = (T2*)a_rhs.get();

			if ( v_left->get_data() == v_right->get_data() )
			{
				return 1;
			}

			return 0;
		}
	};


To test for equality between two types:

ITestPtr value_test = ITestPtr( new EqualityTest<ValueData_String, ValueData_String>() );

if (value_test->test( blah, blah2 ) )
{
  // etc
}
This all works perfectly... except for one thing, I need to be able to map the VT_STRING int 'types' to real C++ types. In an ideal world, I'd want some form of lookup to say that VT_STRING maps to ValueData_String (likewise, VT_INT to ValueData_Integer, VT_NULL to ValueData_Null, etc). The problem is that I want to be able to allow for comparisons for VT_FLOAT to VT_INT, or VT_YOURUSERTYPE to VT_STRING - the current system allows this through the use of templates (provided the comparing types have appropriate equality operators). In pseduosyntax:-

ITest test = new EqualityTest( CPPTypeLookup::GetCPPType( VT_STRING ), CPPTypeLookup::GetCPPType( VT_YOURTYPE ) );
Could I employ this using the visitor pattern somehow? Any ideas?
Advertisement
I *think* what you're trying to do here is equivalent to Java's Class.newInstance(classname) mechanism. That is, given some string (say "VT_STRING"), you want to be able to call CPPTypeLookup::GetCPPType( "VT_STRING" ) and have it return a reference/pointer to a VT_STRING object.

The only way I know of to do this, is to use Factory pattern. Have a map between the type ints (or the type strings, take your pick) and a factory object that knows how to produce objects of that type. Something like:

class IValueDataFactory {
public:
IValueData* newInstance();
};

typedef std::map<int,IValueDataFactory*> IValueDataFactoryMap;

CPPTypeLookup::GetCPPType( int type ) {
// get the map from your type registry
IValueDataFactoryMap* map = registry.getTypeFactoryMap();
// FIXME: handle case where type isn't in the map
return map[type]->newInstance();
}

Does this help any? Or is it the registry mechanism itself you're stuck on?

As an aside, I'm assuming you already know about RTTI and operator== (or operator<) overloading and have your own reasons for bypassing them. If not, you might want to read up on them and see if it applies here.


The factory pattern isn't really what I need to do...

I've looked into overriding operators however at the point of comparison all I know is that I'm comparing two IValueData objects from which I can retreive the implied type (eg: VT_STRING, VT_INT, etc). Sounds like a need some form of system in place that allows me to dispatch comparisons out to a function based on the VT_* type.

Eg: VT_STRING -> VT_STRING - OpEqual< ValueData_String, ValueData_String >( IValueData *a_lhs, IValueData *a_rhs );
VT_INT -> VT_STRING - OpEqual< ValueData_Int, ValueData_String >( IValueData *a_lhs, IValueData *a_rhs );

This still brings me to the problem - I know that the 'type' is a VT_STRING, but I don't know how to tell C++ that it casts to the ValueData_String... In theory, it shouldn't need to know, it should just dispatch the comparison out somewhere and let the compiler handle it.

I don't want to have to use RTTI as this is a library that can be linked to; I don't want to have to force people to use RTTI to use it.
I think typelist template magic is out of the question, since your dealing with runtime, mutable (I think), values.

Otherwise, you could have used TypeLists.
Check out Loki's TypeLists just to be sure.

Also, be sure to check out the (original) authors website: Alexandrescu especially the "Other Publications" section, in some articles it has some advanced use of the typelist (Discriminated Unions for one, which I think is something like what you want to do here).

I have to admit, his book, Modern C++ Design is the actual text where he explains these things properly.

In any case, if runtime, mutable, integers can work with templates, then
loki::TL::TypeAt<YourTypeList, int>::result
is what you want.

Result will hold the type you are looking for. If it's out of bounds, then I believe it will not compile. Which returns me to my original observation.

Oh well, maybe it is useful.


[EDIT] If I remember your journal correctly, then your creating a sort of database for a game (engine). Well, I would truly recommend the articles I mentioned (Discriminated Unions, all 3 parts). It has a database (types) example.
Thanks for the reply. Typelists are out as yes, it needs to be determinable at runtime instead of at compile time. I'll definitely read those articles as they look useful in this project at some point (I have Modern C++ Design too [grin]).

What I'm looking at doing is the following; given I have IValueData which is my unknown value data type and my ValueData_String / ValueData_Int / etc concrete implementations I can write a comparison interface for them. So:

class ICompFunc{public:	virtual bool Compare( IValueData *a_lhs, IValueData *a_rhs ) = 0;};


I can then specify comparison functions for each of my concrete types:-


class StringStringCompFunc : public ICompFunc{public:	bool Compare( IValueData *a_lhs, IValueData *a_rhs )	{		ValueData_String *lhs = (ValueData_String*)a_lhs;		ValueData_String *rhs = (ValueData_String*)a_rhs;		return (lhs->m_data == rhs->m_data);	}};class StringIntCompFunc : public ICompFunc{public:	bool Compare( IValueData *a_lhs, IValueData *a_rhs )	{		ValueData_String *lhs = (ValueData_String*)a_lhs;		ValueData_Int *rhs = (ValueData_Int*)a_rhs;		return (lhs->m_data == rhs->m_data);	}};


(Note: I have already specified some global operators that let me do std::string to int comparisons (and other types where compatible or not).

Given that I can now compare two ValueData types in a specific combination, I need a way to store these at runtime. The solution is to use a couple of maps, with the LHS keyed on the VT_* type.

So:

        typedef std::map< ValueType, ICompFunc *>		TypeLookupMapRHS;	typedef std::pair< ValueType, ICompFunc *>		TypeLookupPairRHS;	typedef std::map< ValueType, TypeLookupMapRHS > TypeLookupMapLHS;	typedef std::pair< ValueType, TypeLookupMapRHS > TypeLookupPairLHS;	TypeLookupMapLHS lookup;	TypeLookupMapRHS rhs;        // set up VT_STRING -> VT_INT comparison lookup	rhs.insert( TypeLookupPairRHS( VT_INT, new StringIntCompFunc() ) );	lookup.insert( TypeLookupPairLHS( VT_STRING, rhs ) );


Then to retreive the VT_STRING / VT_INT comparison:-

ICompFunc *c = lookup.find( VT_STRING )->second.find( VT_INT )->second;

if ( c->Compare( &a, &b ) )
{
std::cout << "Matched";
}


The only thing I need to do is guard against unspecified comparisons eg: VT_STRING -> VT_INT would fail here as we have no compare function, nor do we have an entry in the lookup map.

This /should/ work, I'm hoping. It just means that anyone wishing to allow their type to be compared to another will have to write a custom ICompFunc implementation pair. I will also provide the IValueData interface with a ToString() style function to allow simple string comparisons without having to write more code.
This *may* work:

If I read it right (got a spot of headache), you have an explicit string->int comparison function.

Now, why wouldn't it not be possible to create a template function that always returns false?

If I remember things correctly, then your explicit functions will qualify as better matches and they get picked.

So, if you compare String->InvalidType the template function will be initialized for String/InvalidType and it will return false.
Here's a more refined implementation for anyone who's interested:

class ICompFunc{public:	virtual bool Compare( IValueData *a_lhs, IValueData *a_rhs ) = 0;};template< class T1, class T2 >class CompFunc : public ICompFunc{public:	bool Compare( IValueData *a_lhs, IValueData *a_rhs )	{		T1 *lhs = (T1*)a_lhs;		T2 *rhs = (T2*)a_rhs;		return (lhs->m_data == rhs->m_data);	}};


typedef int	ValueType;const ValueType VT_INT	= 1;const ValueType VT_STRING = 2;typedef std::map< ValueType, ICompFunc *>		TypeLookupMapRHS;typedef TypeLookupMapRHS::iterator				TypeLookupMapRHSItr;typedef std::pair< ValueType, ICompFunc *>		TypeLookupPairRHS;typedef std::map< ValueType, TypeLookupMapRHS > TypeLookupMapLHS;typedef TypeLookupMapLHS::iterator				TypeLookupMapLHSItr;typedef std::pair< ValueType, TypeLookupMapRHS > TypeLookupPairLHS;typedef TypeLookupMapLHS TypeLookupMap;


ICompFunc *GetCompFunc( TypeLookupMap &a_map, ValueType a_lhs_type, ValueType a_rhs_type ){	ICompFunc *cmp = 0;	TypeLookupMapLHSItr lhs_i;	TypeLookupMapRHSItr rhs_i;	lhs_i = a_map.find( a_lhs_type );	if ( lhs_i != a_map.end() )	{		rhs_i = (*lhs_i).second.find( a_rhs_type );		if (rhs_i != (*lhs_i).second.end())		{			cmp = (*rhs_i).second;		}	}	return cmp;}




And to setup/test:

ValueData_String a( "123" );	ValueData_Int b( 123 );	ICompFunc *cmp = 0; ;	TypeLookupMapLHS lookup;	TypeLookupMapRHS rhs;	// Set up VT_STRING -> * comparisons	rhs.insert( TypeLookupPairRHS( VT_INT, new CompFunc< ValueData_String, ValueData_Int >() ) );	rhs.insert( TypeLookupPairRHS( VT_STRING, new CompFunc< ValueData_String, ValueData_String >() ) );	lookup.insert( TypeLookupPairLHS( VT_STRING, rhs ) );	rhs.clear();	// Set up VT_INT -> * comparisons	rhs.insert( TypeLookupPairRHS( VT_INT, new CompFunc< ValueData_Int, ValueData_Int >() ) );	rhs.insert( TypeLookupPairRHS( VT_STRING, new CompFunc< ValueData_Int, ValueData_String >() ) );	lookup.insert( TypeLookupPairLHS( VT_INT, rhs ) );	cmp = GetCompFunc( lookup, VT_INT, VT_STRING );	if ( cmp != 0 && cmp->Compare( &b, &a ) )	{		std::cout << "Matched" << std::endl;	}



Seems to work perfectly :D
Quote:Original post by mldaalder
If I read it right (got a spot of headache), you have an explicit string->int comparison function.


Good point, I don't even *need* my operators anymore, I could just use the templated function to compare implementations of the IValueData interface.

Thanks =)

This topic is closed to new replies.

Advertisement