具有用户定义函数的C ++ Math Parser [英] C++ Math Parser with user-defined function

查看:135
本文介绍了具有用户定义函数的C ++ Math Parser的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想实现一个带有用户定义函数的数学解析器。
有几个问题要解决。
例如, int eg(int a,int b){return a + b;} 是我要添加到解析器的函数。
第一:如何将所有函数存储到容器中?
std :: map< std :: string,boost :: any> func_map 可以是一个选择(通过 func_map [eg] = eg ),但是,很难调用这种类型的函数因为我必须使用 any_cast< T> boost :: any

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.

第二:如何处理重载的函数
这是真的,我可以通过 typeid ,但它远不是一个真正的实现。

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为这个问题提供了一个有趣的解决方案,但我找到了另一个方法。

muparserx provides an interesting solution for this problem, but I'm finding another method.

我不熟悉lambda表达式

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;
}


推荐答案


如何将所有函数存储到容器中?

How to store all the functions into a container?

要在某个容器中存储,它们必须是相同类型。 std :: function wrapper是一个不错的选择,因为这允许你使用甚至有状态的函数对象。因为你可能不希望所有函数采用相同数量的参数,所以你需要从静态主机类型系统中提取函数的arity。一个简单的解决方案是使用接受 std :: vector 的函数:

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 a std::vector:

// Arguments type to the function "interface"
using Arguments = std::vector<int> const &;
// the interface
using Function = std::function<int (Arguments)>;

但是你不希望你的用户编写需要手动解包参数的函数,这意味着自动化。

But you don't want your users to write functions that have to unpack their arguments manually, so it's sensible to automate that.

// Base case of packing a function.
// If it's taking a vector and no more
// arguments, then there's nothing left to
// pack.
template<
  std::size_t N,
  typename Fn>
Function pack(Fn && fn) {
 return
  [fn = std::forward<decltype(fn)>(fn)]
  (Arguments arguments)
  {
   if (N != arguments.size()) {
    throw
      std::string{"wrong number of arguments, expected "} +
      std::to_string(N) +
      std::string{" but got "} +
      std::to_string(arguments.size());
   }
   return fn(arguments);
  };
}

上述代码处理简单情况:已经接受向量的函数。对于所有其他函数,它们需要被打包并打包到新创建的函数中。这样做一个时间的参数使得这相对容易:

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:

// pack a function to a function that takes
// it's arguments from a vector, one argument after
// the other.
template<
  std::size_t N,
  typename Arg,
  typename... Args,
  typename Fn>
Function pack(Fn && fn) {
 return pack<N+1, Args...>(
  [fn = std::forward<decltype(fn)>(fn)]
  (Arguments arguments, Args const &... args)
  {
   return fn(
     arguments,
     arguments[N],
     args...);
  });
}

上面的代码仅适用于已经接受向量的对于普通函数,我们需要一个函数来将它们转换为以下特殊函数:

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:

// transform a function into one that takes its
// arguments from a vector
template<
  typename... Args,
  typename Fn>
Function pack_function(Fn && fn) {
 return pack<0, Args...>(
  [fn = std::forward<decltype(fn)>(fn)]
  (Arguments arguments, Args const &... args)
  {
   return fn(args...);
  });
}



使用此功能,您可以将任何函数打包为相同类型: / p>

Using this, you can pack any function up to be the same type:

Function fn =
  pack_function<int, int>([] (auto lhs, auto rhs) {return lhs - rhs;});

然后,您可以在地图中使用它们,并使用一些向量来调用它们,

You can then have them in a map, and call them using some vector, parsed from some input:

int main(int, char**) {
 std::map<std::string, Function> operations;
 operations ["add"] = pack_function<int, int>(add);
 operations ["sub"] = pack_function<int, int>(
   [](auto lhs, auto rhs) { return lhs - rhs;});
 operations ["sum"] = [] (auto summands) {
   int result = 0;
   for (auto e : summands) {
    result += e;
   }
   return result;
  };
 std::string line;
 while (std::getline(std::cin, line)) {
  std::istringstream command{line};
  std::string operation;
  command >> operation;
  std::vector<int> arguments {
    std::istream_iterator<int>{command},
    std::istream_iterator<int>{} };
  auto function = operations.find(operation);
  if (function != operations.end ()) {
   std::cout << line << " = ";
   try {
    std::cout << function->second(arguments);
   } catch (std::string const & error) {
    std::cout << error;
   }
   std::cout << std::endl;
  }
 }
 return 0;
}

A 上述代码的现场演示在这里


如何处理重载的函数?这是真的,我可以通过typeid的方法区分重载的函数,但它远不是一个真正的实现。

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.

你不需要,如果你打包相关的信息到函数。 Btw, typeid 不应该用于诊断,因为它不能保证返回不同的字符串与不同的类型。

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.

现在,最后,为了处理不仅需要不同数量的参数,而且它们的参数类型也不同的函数,您需要将这些类型统一为一个。这通常被称为和类型,很容易实现在像Haskell的语言:

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:

data Sum = IVal Int | SVal String
-- A value of type Sum is either an Int or a String

C ++这很难实现,但是一个简单的草图可以看起来像这样:

In C++ this is a lot harder to achieve, but a simple sketch could look such:

struct Base {
  virtual ~Base() = 0;
};
inline Base::~Base() {}

template<typename Target>
struct Storage : public Base {
  Target value;
};

struct Any {
  std::unique_ptr<Base const> value;
  template<typename Target>
  Target const & as(void) const {
    return
      dynamic_cast<Storage<Target> const &>(*value).value;
   }
};

template<typename Target>
auto make_any(Target && value) {
  return Any{std::make_unique<Storage<Target>>(value)};
}

但这只是一个粗略的草图,因为有 boost :: any ,它们在这种情况下应该完美工作。注意,上面和boost :: any不是一个真正的和类型(他们可以是任何类型,而不只是一个给定的选择),但这不应该在你的情况下。

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 :)

支持我扩展了一点上面的草图,让它工作。代码远不是生产准备好,虽然:我扔的字符串,不跟我说完美的转发:D

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:

struct Base {
  virtual ~Base() = 0;
  virtual void print_to(std::ostream &) const = 0;
};
inline Base::~Base() {}

template<typename Target>
struct Storage : public Base {
  Target value;
  Storage (Target t) // screw perfect forwarding :D
   : value(std::forward<Target>(t)) {}

  void print_to(std::ostream & stream) const {
    stream << value;
  }
};

struct Any {
  std::shared_ptr<Base const> value;

  template<typename Target>
  Target const & as(void) const {
    return
      dynamic_cast<Storage<Target> const &>(*value).value;
   }
   template<typename T>
   operator T const &(void) const {
     return as<T>();
  }
   friend std::ostream & operator<<(std::ostream& stream, Any const & thing) {
     thing.value->print_to(stream);
     return stream;
   }
};

template<typename Target>
Any make_any(Target && value) {
  return Any{std::make_shared<Storage<typename std::remove_reference<Target>::type> const>(std::forward<Target>(value))};
}



我还写了一个小的解析函数,字面值转换为包含(在这种情况下)整数,双精度值或字符串值的任何值:

Any parse_literal(std::string const & literal) {
  try {
    std::size_t next;
    auto integer = std::stoi(literal, & next);
    if (next == literal.size()) {
      return make_any (integer);
    }
    auto floating = std::stod(literal, & next);
    if (next == literal. size()) {
      return make_any (floating);
    }
  } catch (std::invalid_argument const &) {}
  // not very sensible, string literals should better be
  // enclosed in some form of quotes, but that's the
  // job of the parser
  return make_any<std:: string> (std::string{literal});
}

std::istream & operator>>(std::istream & stream, Any & thing) {
  std::string raw;
  if (stream >> raw) {
    thing = parse_literal (raw);
  }
  return stream;
}

还提供 operator>> 可以继续使用 istream_iterator s输入。

By also providing operator>> it's possible to keep using istream_iterators for input.

包装功能函数也被修改:当将一个元素从参数向量传递给下一个函数时,执行从 Any 到相应参数类型的转换。这也可能失败,在这种情况下,捕获一个 std :: bad_cast ,并返回一个信息性消息。最内层函数( pack_function 中创建的lambda)将其结果包装到 make_any 调用中。

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 a std::bad_cast is caught and an informative message rethrown. The innermost function (the lambda created inside pack_function) wraps its result into an make_any call.

add 5 4 = 9
sub 3 2 = 1
add 1 2 3 = wrong number of arguments, expected 2 but got 3
add 4 = wrong number of arguments, expected 2 but got 1
sum 1 2 3 4 = 10
sum = 0
sub 3 1.5 = argument 1 has wrong type 
addf 3 3.4 = argument 0 has wrong type 
addf 3.0 3.4 = 6.4
hi Pete = Hello Pete, how are you?

类似于上一个示例的示例可以在这里找到。我需要补充一点,这个 Any 类型不支持隐式类型转换,所以当你有一个 Any int 存储,不能将其传递给期望使用 double 的函数。虽然这可以实现(通过手动提供很多转换规则)。

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 an Any with an int stored, you cannot pass that to an function expecting a double. Though this can be implemented (by manually providing a lot of conversion rules).

但我也看到了你的更新,所以我采取了这些代码,并应用必要的修改< a href =http://ideone.com/jyn3c4 =nofollow>使用我提供的解决方案:

But I also saw your update, so I took that code and applied the necessary modifications to run with my presented solution:

Any apply (multimap<string, Function> const & map, string const & name, Arguments arguments) {
 auto range = map.equal_range(name);
 for (auto function = range.first;
      function != range.second;
      ++function) {
  try {
   return (function->second)(arguments);
  } catch (string const &) {}
 }
 throw string {" no such function "};
}


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 a.size()/double(2);}
int main(){
 multimap<string, Function> func_map;
 func_map.insert(make_pair(
   "eg",pack_function<int,int>(
     static_cast<int(*)(int, int)>(&eg))));
 func_map.insert(make_pair(
   "eg",pack_function<int,int,string>(
     static_cast<int (*)(int, int, string)>(&eg))));
 func_map.insert(make_pair(
   "eh",pack_function<string>(eh)));

 // auto p1=make_tuple(1,2);
 // if you want tuples, just write a
 // function to covert them to a vector
 // of Any.
 Arguments p1 =
   {make_any (1), make_any (2)};
 int result1 =
   apply(func_map, "eg", p1).as<int>();

 vector<Any> p2{p1};
 p2.push_back(make_any<string> ("test"));
 int result2 =
   apply(func_map, "eg", p2).as<int>();


 Arguments p3 = {make_any<string>("testagain")};
 double result3 =
   apply(func_map, "eh", p3).as<double>();


 cout << result1 << endl;
 cout << result2 << endl;
 cout << result3 << endl;
 return 0;
}

它不使用元组,但你可以写一个函数访问元组的每个元素,将其包装到 Any 中并将其打包到向量中。

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.

我不知道为什么在初始化结果变量时 Any 的隐式转换不起作用。

Also I'm not sure why the implicit conversion from Any doesn't work when initialising the result variables.

Hm,转换它使用 boost :: any 应该不是那么困难。首先, make_any 只会使用 boost :: any 的构造函数:

Hm, converting it to use boost::any shouldn't be that difficult. First, the make_any would just use boost::any's constructor:

template<typename T>
boost::any make_any(T&& value) {
  return boost::any{std::forward<T>(value)};
}

在pack函数中,我想要的唯一的东西需要改变是从参数向量中的当前元素正确类型的提取。目前,这是简单的 arguments.at(N),依赖于隐式转换为所需的类型。由于 boost :: any 不支持隐式转换,因此您需要使用 boost :: any_cast 获取底层值:

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. Since boost::any doesn't support implicit conversion, you need to use boost::any_cast to get to the underlying value:

template<
  std::size_t N,
  typename Arg,
  typename... Args,
  typename Fn>
Function pack(Fn && fn) {
 return pack<N+1, Args...>(
  [fn = std::forward<decltype(fn)>(fn)]
  (Arguments arguments, Args const &... args)
  {
   try {
    return fn(
      arguments,
      boost::any_cast<Arg>(arguments.at(N)),
      args...);
   } catch (boost::bad_any_cast const &) { // throws different type of exception
    throw std::string{"argument "} + std::to_string (N) +
      std::string{" has wrong type "};
   }
  });
}

当然,如果你使用它,需要使用 boost :: any_cast 才能访问结果值。

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.

最后你需要为 boost :: any_cast 调用的模板参数添加一些 std :: remove_reference magic ,但我怀疑这是必要的。
typename std :: remove_reference< T> :: type 而不是 T

This should (in theory) do it, eventually you need to add some std::remove_reference "magic" to the template parameter of the boost::any_cast calls, but I doubt that this is neccessary. (typename std::remove_reference<T>::type instead of just T)

虽然我目前无法测试上述任何内容。

Though I currently cannot test any of the above.

这篇关于具有用户定义函数的C ++ Math Parser的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆