Skip to main content
gamedev.net
🔒 Locked 🎲 GameMaker

boost::function with anonymous object types

Started by Endar Apr 11, 2006 at 4:12 AM 8 replies 650+ views
Original Post
Endar
Endar
I'm using boost::function for the in-engine debug console that I have had so many design problems with over the past couple of weeks. I need to be able to register functions and call them, and I have decided upon boost::function because it seems like the best thing for it. The only problem is that it won't work when you try and put the object pointer in as a void*.

// member functions
boost::function<int (a* obj_ptr, float f)> f2;
f2 = &a::afunc;
a a_obj;
printf("\nf2 return: %d\n", f2(&a_obj, 10.3546f) );

// trying member functions with a void*
boost::function<int (void* obj_ptr, float f)> f3;
f3 = &a::afunc;
a another_obj;
printf("\nf3 return: %d\n", f3(&another_obj, 10.3546f) );

The first works, the second gives me a nice 110 line error which as far as I can tell, means "it doesn't work." This is going to turn out to be a problem as I'm going to need to be able to add member functions for different types of objects. I'd consider using a variant, except for the fact that whenever you want to add a function from a new type of object, you have to add that type to the variant template list and re-compile everything. Any ideas?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Enigma
Enigma
Boost.Bind?

boost::function< int (float) > function;
a a_obj;
function = boost::bind(&a::afunc, boost::ref(a_obj), _1);
std::cout << "\nfunction return: " << function(10.3546f);
b b_obj;
function = boost::bind(&b::bfunc, boost::ref(b_obj), _1);
std::cout << "\nfunction return: " << function(10.3546f);


Σnigma
Endar
Endar
Okay, hang on a second.

I'm having one of those "what the hell am I doing" moments (probably copyrighted by Mythbusters).

if boost::function and boost::function are different types of objects, how will I go about storing them all in a single array or list without doing something like attempting to keep them all as boost::variant?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Sharlin
Sharlin
Enigma: The function assigment operator already conveniently uses bind when appropriate, no problem there.

Endar: It's probably just too type-unsafe for the developers to consider supporting void pointers like that. Ugly things might happen if you accidentally called such a function with an object of a wrong type.

EDIT: You don't. You'd encapsulate the arguments and return value(s) into some generic scaffolding (say, a vector of variants), or, as I have done, pass them as text streams. My own console implementation has something like:

boost::function

where Input and Output are template arguments that are required to support stream extraction and insertion the way iostreams do. They default to argstream and ostream, the former being a light wrapper for istream with some convenience features.
Enigma
Enigma
Quote:
Original post by Sharlin
Enigma: The function assigment operator already conveniently uses bind when appropriate, no problem there.

I think you missed the point of my post. To my knowledge you cannot bind a variable to a function, and thus produce a function object whose type does not depend on the type of that variable, without an explicit call to bind (or another binding function like std::bind1st). I was using bind solely to remove the problematic unknown type (void *) parameter from the function signature.

Endar: What exactly is it that you are trying to do?
Endar
Endar
Sharlin: Well, having the return and the arguments as boost::variants, since the rest of my console is using variants, that is the best way, but I was thinking more along the lines of how do I keep a bunch of function objects that I can use for calling member functions? How am I going to deal with the pointer to the object?

Or am I not quite getting what you were talking about?

...

Are you saying that Output will contain the return value as a variant and Input is a vector/array of variants that contain the function arguments? The first of which being a pointer to an object?

If so, whenever you want to add a new object type, won't you have to add another type to the variant template list and recompile everything?


Engima: I want to be able to call an arbitrary function from my in-engine console. Obviously the function is going to have to be registered before-hand. But in regards to member functions, I don't want to have to physically add a pointer to a new object type the first time I want to call a function for a new object type. I just want to have a single line of code such as:
console->registerFunction<Engine*>(pointer_to_engine_object, params);

Something like that, or perhaps I'll have to create a boost::function object before I call 'registerFunction', but I don't want to have to find a boost::variant declaration in a file and add 'Engine*' to its template param list.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Sharlin
Sharlin
Ah, then you can do just like Enigma said in his first post. If you have the this object at hand when you register a new function, you can just bind it right there.

result foo(params) { } // a free functionstruct bar1 { result f(params) { } }; // a non-static member functionstruct bar2 { result f(params) { } }; // a non-static member functionstruct baz { result operator ()(params) { } }; // a function object...vector<function<result (params)> > functions;functions.push_back(foo);bar1* b1;bar2* b2;functions.push_back(bind(&bar::f, b1, _1));functions.push_back(bind(&bar::f, b2, _1));functions.push_back(baz());
Endar
Endar
Am I correct in thinking that I'm not able to use a method like that if I have, say 4 different functions with different parameter lists, and different return types?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Sharlin
Sharlin
Yep. As always, the standard library containers don't accept elements of more than one type.
Telastyn
Telastyn
But they can certainly contain a base type, or a constant type which contains a base type for the arbitrarily templatized functions.

In my C++ version of this sort of thing it actually goes [psuedo class code]:

struct TriggerInterface{    virtual void trigger()=0;   // note, mine doesn't need return values                                //  just one more indirection if you do.    virtual ~TriggerInterface(){}}template <typename F>struct Trigger: public TriggerInterface{    // Store function F, and do de-serialization into args    // Something like this [using external Template Type Info class]:    // shared_ptr<typename F::arg1_type>       a1(def<typename F::arg1_type>().unserialize(anyargs[0].c_str()));    // The shared_ptr ensures destruction after trigger, and since F    //  is a boost::function object, the type info is held in     //  F::argX_type}// 1 trigger for each arity.template <typename F, int ARGS>struct TriggerGenerationInterface{    TriggerInterface   *trigger;    ctor(F function, vector<string> args){        trigger=new...        // Create proper class based on template argument.    }}template <typename F>struct TriggerGenerator<F,1>{    ...}template <typename F>struct TriggerGenerator<F,2>{    ... and so on.}template <typename F>    // where F is a boost::function<...> objectstruct ArgAccumulator: public TriggerInterface{    F                   function;    vector<string>      args;      // functions to manipulate variables.    void Trigger(){        // F is a boost::function object, so it contains arity info:        TriggerGenerator<F,F::arity>    built_function(function,args);        built_function.trigger();    }}class BaseCmd{    string     Name;    virtual void Parse(string)=0;    // Helper functions}   template <typename F>class Cmd: public BaseCmd{    ArgAccumulator<F>    Scratch;    void Parse(string CmdLine){        //         // Common code to clear the arg accumulator,        //  pick out arguments and accumulate them.        //        // If parsing is successful, trigger the accumulated         //  function/arg group.        //    }}class CmdSet{    hash_map<string, BaseCmd>      Commands;    // functions to manipulate hash_map, and to parse    //  the first word for CmdLine delegation to the     //  BaseCmd.}// Usage:CmdSet ConsoleHandler=new CmdSet();ConsoleHandler.Add("quit", boost::function<void()>(QuitGame));ConsoleHandler.Add("volume", boost:function<void(float)>(boost::bind(    &sound_manager::SetVolume, SoundManager,_2)));ConsoleHandler.Parse("volume .75");  // would set the volume to 75%ConsoleHandler.Parse("foo");         // nothing, not in the hash_map



A bit messy, and I'm pretty certain with more black magic it could be streamlined quite a bit. My C# version is far cleaner and useful.

Hrm. Looking back that might not be very helpful at all. Meh, questions?

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.