Sign in to follow this  

[C++] Representing a list of template objects with undefined parameters

This topic is 3292 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to create a data / property editor for my visualization engine. For this, the information I want to obtain is similar to what Visual Studio's debugger displays about objects. Additionally, for each class, I need a list of member variables along with pointers to functions that modify them. I created a template class: template <class T, typename U> class FuntionPointer that saves a pointer to a member function of class T that has one argument of type U. Then, I created an ObjectData class to store some information about a member variable:
template <class T, typename U>
class ObjectData
{
	void* ptrToObject;
	STRING objectName;
public:
	FuntionPointer<T, U> SetData;
};
Information about class members can be represented as a list of ObjectData variables. The problem now is that there is no easy way to declare this list to contain template objects of different types. As classes can have variables (U) of different types it is not possible to declare an std::vector<ObjectData<T, U>> as T and U essentially become constants. I've tried to get rid of the second template argument and implement pointers to functions with 'hardcoded' input data types. Right now I have something like this:
template <class T>
class FunctionPointer
{
	UINT_PTR caller; //Pointer to the object of type T that calls the function
	OBJECT_STATE_TYPE selectedType; //Enumeration for some hardcoded types (UINT and float in this case)
	//Function pointer prototype definition
	typedef void (__thiscall T::* FUNCTION_SET_VALUE_UINT) (UINT);
	typedef void (__thiscall T::* FUNCTION_SET_VALUE_FLOAT) (float);
	//Function pointers
	//Should probablly use a union to save space
	FUNCTION_SET_VALUE_UINT functionPointerUint;
	FUNCTION_SET_VALUE_FLOAT functionPointerFloat;
public:
	FunctionPointer()
	{
		caller = 0;
		functionPointerUint = 0;
		functionPointerFloat = 0;
		selectedType = TYPE_UINT;
	}
	FunctionPointer(UINT_PTR Caller, FUNCTION_SET_VALUE_UINT setValue)
	{
		caller = Caller;
		functionPointerUint = setValue;
		functionPointerFloat = 0;
		selectedType = TYPE_UINT;
	}
	FunctionPointer(UINT_PTR Caller, FUNCTION_SET_VALUE_FLOAT setValue)
	{
		caller = Caller;
		functionPointerUint = 0;
		functionPointerFloat = setValue;
		selectedType = TYPE_FLOAT;
	}
	void Call(UINT value)
	{
		if(!caller)
			return;
		T* TCaller = reinterpret_cast<T*>(this->caller);
		if(selectedType == TYPE_UINT)
		{
			(TCaller->*(this->functionPointerUint))(value);
		}
	}
	void Call(float value)
	{
		if(!caller)
			return;
		T* TCaller = reinterpret_cast<T*>(this->caller);
		if(selectedType == TYPE_FLOAT)
		{
			(TCaller->*(this->functionPointerFloat))(value);
		}
	}
};
template <class T>
class ObjectData
{
	void* ptrToObject;
	STRING objectName;
public:
	FuntionPointer<T> SetData;
};

template <class T>
class ObjectDataList
{
	std::vector<ObjectData<T>> objectData;
	std::vector<ObjectDataList<T>> objectDataList;
};
The deal-breaker with this is the fact that ObjectDataList can only store variables of the types that I hardcode or base and derived classes of T. So for instance if I had the following class:
class SomeFloats
{
	float2 f2;
	float3 f3;
};
The only way to save SomeFloats::* (float2) and SomeFloats::* (float3) would be to explicitly hardcode those two types in FunctionPointer. This seems very wasteful and is infeasible for the number of classes I have. I guess if you abstract the details, my problem right now is to find a method to represent a list of various data types which may not be known at compile time. Clearly, it wont be possible to use templates and dynamic arrays as templates are resolved at compile time. Alternatively, can anyone think of a better way to implement a property editor for a large number of classes?

Share this post


Link to post
Share on other sites
Have you considered using a programming language that supports reflection? Both Java and C# can provide this functionality pretty much out-of-the box, to say nothing of dynamic scripting languages such as Python.

Share this post


Link to post
Share on other sites
Quote:
Original post by madmonkey28
Alternatively, can anyone think of a better way to implement a property editor for a large number of classes?

I do it by making every property support a ToString/FromString virtual function. Then just convert all your class's info to a string, such as "(x, y)" for float2 and "(x, y, z)" for float3.

Share this post


Link to post
Share on other sites
Have you considered using boost libraries? It provides a few components that would help you here:
Boost function: provides a single method of storing any type of function pointer.
Boost bind: allows binding of values directly to function parameters.
Boost variant: "a type safe, generic, discriminated union container", basically does what your ObjectData class does, but in a type safe way.
If you haven't used boost before, it can be a bit scary to start with, but once you get used to it you will never look back.

/edit
Oops missed the "which may not be known at compile time" bit. Still just replace boost::variant with boost::any in the above!

[Edited by - bluntman on December 8, 2008 8:50:30 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by madmonkey28
I guess if you abstract the details, my problem right now is to find a method to represent a list of various data types which may not be known at compile time. Clearly, it wont be possible to use templates and dynamic arrays as templates are resolved at compile time.


I had to do something very similar recently. I had a template base class whose type was defined by the derived class. It looked something like this.
template <typename T>
class Foo
{
// ...
public:
void DoStuff();
// ...
};

class Bar : public Foo<SomeType>
{
// ...
// ...
};

In this case, class Foo handled stuff that all Bar's needed to do, but depended upon the some information (SomeType) that only the Bar's knew. That's why the base class, Foo, needed to be a template class. However, I also wanted the Bar's to be able to contain other Bar's. So, I changed the code to look like this.

class Base
{
// ...
public:
virtual void DoStuff() = 0;
};
template <typename T>
class Foo : Base
{
// ...
public:
void DoStuff();
// ...
};

class Bar : public Foo<SomeType>
{
// ...
private:
std::vector<Base*> m_Bars;
// ...
};

Now, each Bar can have a vector of other Bar's, even if they have different template parameters. I *think* this applies to what you were talking about and could work for you.

Share this post


Link to post
Share on other sites
Quote:
Original post by bluntman
/edit
Oops missed the "which may not be known at compile time" bit. Still just replace boost::variant with boost::any in the above!

boost::any looks like its something similar to what I need. It doesn't eliminate the problem of needing to hardcode all possible types in order to cast back though.
Quote:
Original post by Mantear
Now, each Bar can have a vector of other Bar's, even if they have different template parameters. I *think* this applies to what you were talking about and could work for you.

Your solution also seems to acomplish something similar to boost::any in my case. The problem with either of these methods is that the type of the object is lost after casting. This doesn't seem to be an issue in your case as the derived class takes no arguments.

Heres an abridged version of what I'm trying to accomplish again:
template <class T> class FunctionPointer; //Pointer to member function of class T
template <class T, typename U> class FunctionPointerType;//Argument of the function is of type U

template <class T>
class Leaf
{
TYPE hardCodedTypes;
FunctionPointer<T> fPtr;
//Pointer to parent BranchNode should go here...
};

template <class T, typename U>
class BranchNode
{
FunctionPointerType<T, U> fPtr;
std::vector<boost::any> branches; //Can also be a pointer to a virtual base class
std::vector<Leaf<T>> leaves;
//Pointer to parent BranchNode should go here...
};



To represent the following class as a BranchNode:
class VariousFloats
{
float f; //Leaf
float2 f2; //Branch
int ModifyFloat(float f_in);
int ModifyFloat2(float2 f2_in);
int ModifyVariousFloats(VariousFloats in);
};

//The root node
BranchNode<VariousFloats, VariousFloats> mainBranch(&VariousFloats::ModifyVariousFloats);
//As float2 is not a 'primitive' data structure it needs to be a branch so we can resolve it further
BranchNode<VariousFloats, float2> float2Branch(&VariousFloats::ModifyFloat2); //Add two leaves of type float to this branch node
//f can be a Leaf as we dont need to traverse it further
Leaf<VariousFloats> floatLeaf(TYPE_FLOAT, &VariousFloats::ModifyFloat);
//Add data to main branch - the type of the branch is now lost
mainBranch.branches.push_back(float2Branch);
mainBranch.branches.push_back(floatLeaf);



Using the leaf node is easy as its type can be completely determined. But what if I want to traverse the branches? I don't know what to cast each one to without checking against a list of all possible data types.
Reflection seems to be the right way to solve the problem but all my code is in C++.

I'm going to think of a different way to implement a property editor, possibly without providing access to a function pointer for ever single member of the class.

Share this post


Link to post
Share on other sites
Well what I did when I wanted to create a property editor was give responsibility for editing and validation to the types themselves, via a single interface:

struct Type
{
virtual void add_control(HWND controlParent);
}

struct Float : Type
{
float val;

virtual void add_control(HWND controlParent)
{
// create a control for editing the float value and embed it into controlParent, give it responsibility for validiating input etc
}
}

Then to add new types you just have to provide the API (the Type interface) and a window in which it can display its editing control.

I would suspect that you can probably nail down to a great degree of certainty the entire type list you want to be editable, in the end pretty much everything boils down to [float, int, bool, string] with some further specialisation such as color (a set of floats, or a 32 bit int), file name (a string).
But you get a better editor (from a usability viewpoint) if you allow full control of editing and validation to be handled by the type itself, rather than pigeon holing all types into a subset.

Share this post


Link to post
Share on other sites
Quote:
Original post by madmonkey28
boost::any looks like its something similar to what I need. It doesn't eliminate the problem of needing to hardcode all possible types in order to cast back though.


C++ doesn't support introspection. Unless you define how to access any type, you cannot inspect it.

There's two problems to solve:
- How to modify arbitrary type in consistent manner from property editor
- How to enumerate editable types.

User enters types in UI editor, so the input are strings.
struct Property {
Property(const char * n) : name(n) {}
virtual void set(const std::string & s) = 0;
virtual std::string get() const = 0;
const char * name;
};

template < class T >
struct TypedProperty {
TypedProperty(T * p, const char * name) : Property(name), ptr(p) {};
virtual void set(const std::string & s) {
from_string(s, *p);
}
virtual std::string get() const {
return to_string(*p);
}
T * ptr;
};
That solves the first problem. All you need to do is implement free functions from_string and to_string for fundamental types (ints, floats, strings)

Next problem occurs how to get a list of properties from given instance. Here, C++ doesn't help, so we need to define manual introspection:
template < class T, class V >
void visit(V & visitor, T & value, const char * name);

template < class V >
void visit(V & visitor, int & value, const char * name) {
visitor.visit(value, name);
};
Again, define these for fundamental types.

Next, define them for containers:
template < class V, class T, class A >
void visit(V & visitor, std::vector<T,A> & v, const char * name) {
visitor.beginType(v, name);
for (int i = 0; i < v.size(); i++) {
visit(visitor, v[i], "");
}
visitor.endType(v);
}


Then, for each structure you want to make editable, do the same:
struct Foo {
int x;
float y;
std::vector<double> q;
};

template < class V >
void visit(V & visitor, Foo & foo, const char * name) {
visitor.beginType(foo, name);
visit(visitor, foo.x, "x");
visit(visitor, foo.y, "y");
visit(visitor, foo.q, "q");
visitor.endType(foo);
}


So, one definition per type.

Of course, so far nothing is happening... The magic comes together in Visitor.
struct PropertyBuilder {
template < class T >
void visit(T & value, const char * name) {
Property * p = new TypedProperty<T>(&value, name);
properties.push_back(p);
}

template < class T >
void beginType(T &, const char *) {
// here, you would add a new child, to which properties would be added
}

template < class T >
void endType(T &, const char *) {
// here, the current subtree would be popped
}


std::vector<Property*> properties;
};


When all is said and done, the above would result in following:
Foo {
int x;
float y;
std::vector<double> q; // has 1.0, 2.0, 3.0
};

Foo foo;

PropertyBuilder pb;
visit(pb, foo);


Which would generate the following tree (tree isn't implemented:

+- x
+- y
+- q
+- ''
+- ''
+- ''
The elements of vector are unnamed, but that could be changed.

This requires you to write 1 PropertyBuilder which examines given instance and returns list of Properties.
Any type you wish to export requires a visit function that lists all the variables. There is *no way* around that in C++.
Your property editor UI needs to support *only* fundamental types (ints, doubles + string). String isn't fundamental, but you want a more convenient editor. All others are represented hierarchically.

The rest is the usual memory management etc.... Objects pointed-to by property outlive it, that's for you to ensure.

Share this post


Link to post
Share on other sites

This topic is 3292 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this