C++17 STL Cookbook
上QQ阅读APP看书,第一时间看更新

How to do it...

In this section, we will read a mathematical expression in RPN from the standard input, and then feed it into a function that evaluates it. In the end, we print the numeric result back to the user.

  1. We will use a lot of helpers from the STL, so there are a few includes:
      #include <iostream>
#include <stack>
#include <iterator>
#include <map>
#include <sstream>
#include <cassert>
#include <vector>
#include <stdexcept>
#include <cmath>
  1. And we do also declare that we are using namespace std in order to spare us some typing.
      using namespace std;
  1. Then, we immediately start implementing our RPN parser. It will accept an iterator pair, which denotes the beginning and end of a mathematical expression in string form, which will be consumed token by token.
      template <typename IT>
double evaluate_rpn(IT it, IT end)
{
  1. While we iterate through the tokens, we need to memorize all operands on the way until we see an operation. This is where we need a stack. All the numbers will be parsed and saved in double precision floating point, so it's going to be a stack of double values.
          stack<double> val_stack;
  1. In order to comfortably access elements on the stack, we implement a helper. It modifies the stack by pulling the highest item from it and then returns that item. This way we can perform this task in one single step later.
          auto pop_stack ([&](){ 
auto r (val_stack.top());
val_stack.pop();
return r;
});
  1. Another preparation is to define all the supported mathematical operations. We save them in a map, which associates every operation token with the actual operation. The operations are represented by callable lambdas, which take two operands, add or multiply them, for example, and then return the result.
          map<string, double (*)(double, double)> ops {
{"+", [](double a, double b) { return a + b; }},
{"-", [](double a, double b) { return a - b; }},
{"*", [](double a, double b) { return a * b; }},
{"/", [](double a, double b) { return a / b; }},
{"^", [](double a, double b) { return pow(a, b); }},
{"%", [](double a, double b) { return fmod(a, b); }},
};
  1. Now we can finally iterate through the input. Assuming that the input iterators give us strings, we feed a new std::stringstream per token, because it can parse numbers.
          for (; it != end; ++it) {
stringstream ss {*it};
  1. Now with every token, we try to get a double value out of it. If that succeeds, we have an operand, which we push on the stack.
              if (double val; ss >> val) {
val_stack.push(val);
}
  1. If it does not succeed, it must be something other than an operator; in that case, it can only be an operand. Knowing that all the operations we support are binary, we need to pop the last two operands from the stack.
              else {
const auto r {pop_stack()};
const auto l {pop_stack()};
  1. Now we get the operand from dereferencing the iterator it, which already emits strings. By querying the ops map, we get a lambda object which accepts the two operands l and r as parameters.
                  try {
const auto & op (ops.at(*it));
const double result {op(l, r)};
val_stack.push(result);
}
  1. We surrounded the application of the math part with a try clause, so we can catch possibly occurring exceptions. The at call of the map will throw an out_of_range exception in case the user provides a mathematical operation we don't know of. In that case, we will rethrow a different exception, which says invalid argument and carries the operation string which was unknown to us.
                  catch (const out_of_range &) {
throw invalid_argument(*it);
}
  1. That's already it. As soon as the loop terminates, we have the final result on the stack. So we return just that. (At this point, we could assert if the stack size is 1. If it wasn't, then there would be missing operations.)
              }
}

return val_stack.top();
}
  1. Now we can use our little RPN parser. In order to do this, we wrap the standard input into an std::istream_iterator pair, and feed that into the RPN parser function. Finally, we print the result:
      int main()
{
try {
cout << evaluate_rpn(istream_iterator<string>{cin}, {})
<< '\n';
}
  1. We do again have that line wrapped into a try clause because there's still the possibility that the user input contains operations we did not implement. In that case, we must catch the exception which we throw in such cases, and print an error message:
          catch (const invalid_argument &e) {
cout << "Invalid operator: " << e.what() << '\n';
}
}
  1. After compiling the program, we can play around with it. The input "3 1 2 + * 2 /" represents the expression ( 3 * (1 + 2) ) / 2 and yields the correct result:
      $ echo "3 1 2 + * 2 /" | ./rpn_calculator
4.5