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

Started by
16 comments, last by Fruny 18 years, 11 months ago
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!
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.
Ah, the wonder of disappearing posts [smile] Let me know if you want me to delete the reply.
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.
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 functionstypedef 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 argumentstemplate <typename F>variant wrapedfunction<0>(var_arg_list args){    return tovariant<::boost::function_traits<F>::result_type>(F());}// one argumenttemplate <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 argumentstemplate <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 argumentstemplate<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]
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.
Whoops! Turns out you can't do partial template specialization on functions (silly me [smile]). A fixed version:

// the type for the wrapper functionstypedef variant wrapfunc(var_arg_list);template <typename F, size_t A>struct wrapper{}// zero argumentstemplate <typename F>struct wrapper<F, 0>{    static variant func(var_arg_list args)    {        return tovariant<::boost::function_traits<F>::result_type>(F());    }}// one argumenttemplate <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 argumentstemplate <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 argumentstemplate<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]
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 argumentstemplate <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;}
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.
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.

This topic is closed to new replies.

Advertisement