Jump to content
  • Advertisement
Sign in to follow this  
CoffeeMug

C++ metaprogramming help (in the name of all holy, this is mind boggling)

This topic is 4836 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

Background information: I'm writing an interpreter that translates all language specific functions to the following C++ function:
typedef boost::function<variant_ptr (const vector<variant_ptr> &args)> function_t;
Essentially when someone types the following in the scripting language
(add 3 4)
The symbol 'add' is looked up in the symbol table, appropriate function_t is found, 3 and 4 are converted to variants and passed to that function as arguments. So far so good. However, when I write a C++ function to implement 'add' I don't want to deal with variants. I merely want to write an 'add' function like so:
int add(int n1, int n2) { return n1 + n2; }
Problem at hand: create a registration function that takes a native C++ function and returns a function object function_t. For example:
// Function that wraps native functions in function_t
template<class fn_t>
function_t wrap_native_function(fn_t fn);

// Example of use:
int add(int n1, int n2) {return n1 + n2; }
symbol_table.put("add", wrap_native_function(add));
Now, how on earth do I make wrap_native_function generate an appropriate function object depending on the type of 'fn_t'? Boost offers a great type traits facility that lets one discover the number of arguments to a function, the types of arguments and the type of the return value. Additionally boost::mpl offers a large set of facilities for compile time metaprogramming. However, I can't figure out how to begin to create wrap_native_function. Boost::mpl is mind boggling. I am wondering if someone can provide a small example (or a set of examples) that would be helpful in this context and will get me started. I hope I'm being clear. Thanks in advance!

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by Andrew Russell
I'm afraid you'd probably have to come up with some kind of traits mechinism for each function signature.

I'm not sure what you mean. I already have the capability to examine the function object at compile time using boost::traits. I can figure out the return value type, the number or arguments, and the types of every argument. What else do I need?
Quote:
Original post by Andrew Russell
Your other option is to overload wrap_native_function like crazy.

That's how it would work, yes. In the example above wrap_native_function is overloaded for every different type of native function passed to it (it's just done using the template parameter). That's the easy part. Figuring out how to use mpl to actually write the wrapper to make sure the number of arguments and types are appropriate is the hard part.
Quote:
Original post by Andrew Russell
Finally, you could take a different approach.

Yeah, this is what I was considering as this is the easiest solution for me to implement. However, this will mean the same code will be written over and over and over again (checking the number of arguments, their types, etc.) by the writer of the functions. This is exactly what I'd like to avoid because the approach I described allows one to register existing C++ functions with no change whatsoever. Additionally, writing new function is trivial, you just use C++ and don't worry about any of the script specific issues. You could still write the functions the way you propose, but there's something beautiful about wrap_native_function [smile] I won't do it if it proves to be way too complicated but I'd like to do it for the excercise as much as for usability.

Share this post


Link to post
Share on other sites
Yeah, I deleted my post because I realised I wasn't actually answering your question or being helpful [smile]. I guess you got there first. Leave your reply if you like.

Share this post


Link to post
Share on other sites
In an effort to actually be helpful [smile] - here is what I came up with, I think it should be a "complete" solution once it's cleaned up a little. Enjoy:

template <class T>
T fromvariant(variant v)
{
// stuff, possibly use specialization
}

template <class T>
variant tovariant(T t)
{
// stuff, possibly use specialization
}

// the type for the wrapper functions
typedef variant wrapfunc(var_arg_list);

template <typename F, size_t A>
variant wrapedfunction(var_arg_list args)
{
assert(0); // I'm pretty sure the only way to do this is specialization
}

// zero arguments
template <typename F>
variant wrapedfunction<0>(var_arg_list args)
{
return tovariant<::boost::function_traits<F>::result_type>(F());
}

// one argument
template <typename F>
variant wrapedfunction<1>(var_arg_list args)
{
return tovariant<::boost::function_traits<F>::result_type>(
F(fromvariant<::boost::function_traits<F>::arg1_type>(args[1])));
}

// two arguments
template <typename F>
variant wrapedfunction<2>(var_arg_list args)
{
return tovariant<::boost::function_traits<F>::result_type>(
F(fromvariant<::boost::function_traits<F>::arg1_type>(args[1])
fromvariant<::boost::function_traits<F>::arg2_type>(args[2])));
}

// and so on for more arguments


template<typename F>
wrapfunc wrap_native_function()
{
return wrapedfunction<F, ::boost::function_traits<F>::arity>;
}


// Then call like this:
symbol_table.put("add", wrap_native_function<add>());




It's totally untested and probably got plenty of syntax errors (not to mention spelling errors), but I'm fairly sure the idea behind it is sound.

Tell me what you think.

[Edited by - Andrew Russell on May 23, 2005 11:42:45 PM]

Share this post


Link to post
Share on other sites
This is perhaps not so helpful or magical, but I was working on something like this just recently. Alas, I'm not terribly familiar with boost, but the way I did it was to specify the typing info in the function label.

My registration code looks more like:

symbol_table.put("add int int",wrap_native_function(add));


My equivalent of the symbol table class then parses the function label, and [with a lot of help] does all of the magic type conversions to and from the wrapped native function/functor.

Using templates would be tons faster and better. I'm just trying to get out of monolithic if/else parsing at this point... Hope this at least spawns some ideas.

Share this post


Link to post
Share on other sites
Whoops! Turns out you can't do partial template specialization on functions (silly me [smile]). A fixed version:

// the type for the wrapper functions
typedef variant wrapfunc(var_arg_list);

template <typename F, size_t A>
struct wrapper
{
}

// zero arguments
template <typename F>
struct wrapper<F, 0>
{
static variant func(var_arg_list args)
{
return tovariant<::boost::function_traits<F>::result_type>(F());
}
}

// one argument
template <typename F>
struct wrapper<F, 1>
{
static variant func(var_arg_list args)
{
return tovariant<::boost::function_traits<F>::result_type>(
F(fromvariant<::boost::function_traits<F>::arg1_type>(args[1])));
}
}

// two arguments
template <typename F>
struct wrapper<F, 2>
{
static variant func(var_arg_list args)
{
return tovariant<::boost::function_traits<F>::result_type>(
F(fromvariant<::boost::function_traits<F>::arg1_type>(args[1])
fromvariant<::boost::function_traits<F>::arg2_type>(args[2])));
}
}

// and so on for more arguments

template<typename F>
wrapfunc wrap_native_function()
{
return wrapper<F, ::boost::function_traits<F>::arity>::func;
}





EDIT: I've also remvoed the function, func, from the non-specialised wrapper class. This should give you a compiler error (rather than runtime) if the wrap fails.

[Edited by - Andrew Russell on May 24, 2005 8:37:40 AM]

Share this post


Link to post
Share on other sites
I don't suppose you would settle for passing the arguments as the variant list and just "boxing" the return type? Of course, you would provide utility functions used by the implementation functions to do unboxing, according to the implementation function's needs. Something like this:


// zero arguments
template <typename F>
struct wrapper<F>
{
variant func(var_arg_list args)
{
return tovariant<::boost::function_traits<F>::result_type>(F(args));
}
}

template <typename T>
std::vector<T> allfromvariant(var_arg_list v) {
std::vector<T> cast;
std::transform(v.begin(), v.end(), back_inserter(cast), fromvariant<T>);
return cast;
}

// Now, functions look like this:
int add_ints(var_arg_list things) {
// "as an added bonus", sum multiple things for free
int result = 0;
std::vector<int> numbers = allfromvariant<int>(things);
// Of course, for more interesting function signatures, we could fetch them
// one at a time via fromvariant<T>.
std::accumulate(numbers.begin(), numbers.end(), result);
return result;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Andrew Russell
Whoops! Turns out you can't do partial template specialization on functions (silly me [smile]). A fixed version:

*** Source Snippet Removed ***

EDIT: I've also remvoed the function, func, from the non-specialised wrapper class. This should give you a compiler error (rather than runtime) if the wrap fails.


Looks like a good idea, except you may need to make func a static member of wrapper in order for it to compile.

Share this post


Link to post
Share on other sites
This is all very interesting. I will work on the implementation tonight and will let you know how it went.

Telastyn: I was considering this as well. The benefits of the templated version are obvious though. The processing is done at compile time instead of runtime, the user doesn't have to worry about registration management (putting in the right parameters, etc.), the system is easier to use and more generic. The downsides are of course code bloat and complexity of the implementation, but that doesn't bother me as much.

Zahlman: this is also nice, except that it puts unnecessary burden on the implementor of functions. I think I'll actually use this for more complex functions that can't simply be expressed using native C++ types (for instance, functions that need to deal with the scripting language types directly).

Andrew: thanks, this looks like a great solution (if it actually works [grin]) My only gripe is with 'wrapper' structs: you're forced to manually write multiple versions for multiple parameter functions. This looks like a lot of useless copy and paste work (I know, it's no big deal since you're unlikely to ever deal with more than 5-10 arguments at most, but I'm doing it as much for academic excercise as for practical use). I'm curious how one could come up with a compile-time iteration consturct that automatically builds various versions of 'wrapper' for different number of parameters. I know it's possible, I just can't imagine where to begin.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!