I want to implement a math parser with user-defined function.
There are several problems to be solved.
For example, int eg(int a,int b){return a+b;}
is the function I want to add to the parser.
First: How to store all the functions into a container?
std::map<std::string,boost::any> func_map
may be a choose (by func_map["eg"]=eg
". However, It's very hard to call the function in this kind of map, for I have to use any_cast<T>
to get the real function from the wrapper of boost::any
.
Second: How to handle the overloaded function?
It's true that I can distinguish the overloaded functions by the method of typeid
, but it's far from a real implementation.
Parsering expressions is not a difficult skill and the hardest part is described above.
muparserx provides an interesting solution for this problem, but I'm finding another method.
I'm not familiar with lambda expressions but may be it's an acceptable way.
Update: I need something like this:
int eg(int a,int b){ return a+b;}
int eg(int a,int b, string c){return a+b+c.length();}
double eh(string a){return length.size()/double(2);}
int main(){
multimap<string,PACKED_FUNC> func_map;
func_map.insert(make_pair("eg",pack_function<int,int>(eg));
func_map.insert(make_pair("eg",pack_function<int,int,string>(eg));
func_map.insert(make_pair("eh",pack_function<string>(eh));
auto p1=make_tuple(1,2);
int result1=apply("eg",PACK_TUPLE(p1));//result1=3
auto p2=tuple_cat(p1,make_tuple("test"));
int result2=apply("eg",PACK_TUPLE(p2));//result2=7
auto p3=make_tuple("testagain");
double result3=apply("eh",PACK_TUPLE(p3));//result3=4.5
return 0;
}
To store then inside some container, they must be of the same type. The
std::function
wrapper is a good choice, since this allows you to use even stateful function objects. Since you probably don't want all functions to take the same number of arguments, you need to "extract" the arity of the functions from the static host type system. An easy solution is to use functions that accept astd::vector
:But you don't want your users to write functions that have to unpack their arguments manually, so it's sensible to automate that.
The above code handles the easy case: A function that already accepts a vector. For all other functions they need to be wrapped and packed into a newly created function. Doing this one argument a time makes this relatively easy:
The above only works with (special) functions that already take a vector. For normal functions we need an function to turn them into such special functions:
Using this, you can pack any function up to be the same type:
You can then have them in a map, and call them using some vector, parsed from some input:
A live demo of the above code is here.
As you see, you don't need to, if you pack the relevant information into the function. Btw,
typeid
shouldn't be used for anything but diagnostics, as it's not guaranteed to return different strings with different types.Now, finally, to handle functions that don't only take a different number of arguments, but also differ in the types of their arguments, you need to unify those types into a single one. That's normally called a "sum type", and very easy to achieve in languages like Haskell:
In C++ this is a lot harder to achieve, but a simple sketch could look such:
But this is only a rough sketch, since there's boost::any which should work perfectly for this case. Note that the above and also boost::any are not quite like a real sum type (they can be any type, not just one from a given selection), but that shouldn't matter in your case.
I hope this gets you started :)
Since you had problems adding multi type support I expanded a bit on the above sketch and got it working. The code is far from being production ready, though: I'm throwing strings around and don't talk to me about perfect forwarding :D
The main change to the above
Any
class is the use of a shared pointer instead of a unique one. This is only because it saved me from writing copy and move constructors and assignment operators.Apart from that I added a member function to be able to print an
Any
value to a stream and added the respective operator:I also wrote a small "parsing" function which shows how to turn a raw literal into an
Any
value containing (in this case) either an integer, a double or a string value:By also providing
operator>>
it's possible to keep usingistream_iterator
s for input.The packing functions (or more precisely the functions returned by them) are also modified: When passing an element from the arguments vector to the next function, an conversion from
Any
to the respective argument type is performed. This may also fail, in which case astd::bad_cast
is caught and an informative message rethrown. The innermost function (the lambda created insidepack_function
) wraps its result into anmake_any
call.An example similar to the previous one can be found here. I need to add that this
Any
type doesn't support implicit type conversions, so when you have anAny
with anint
stored, you cannot pass that to an function expecting adouble
. Though this can be implemented (by manually providing a lot of conversion rules).But I also saw your update, so I took that code and applied the necessary modifications to run with my presented solution:
It doesn't use tuples, but you could write a (template recursive) function to access each element of a tuple, wrap it into an
Any
and pack it inside a vector.Also I'm not sure why the implicit conversion from
Any
doesn't work when initialising the result variables.Hm, converting it to use
boost::any
shouldn't be that difficult. First, themake_any
would just useboost::any
's constructor:In the pack function, the only thing that I'd guess needs to be changed is the "extraction" of the correct type from the current element in the arguments vector. Currently this is as simple as
arguments.at(N)
, relying on implicit conversion to the required type. Sinceboost::any
doesn't support implicit conversion, you need to useboost::any_cast
to get to the underlying value:And of course, if you use it like in the example you provided you also need to use
boost::any_cast
to access the result value.This should (in theory) do it, eventually you need to add some
std::remove_reference
"magic" to the template parameter of theboost::any_cast
calls, but I doubt that this is neccessary. (typename std::remove_reference<T>::type
instead of justT
)Though I currently cannot test any of the above.