2

In the D website there is an RPN calculator example presented at the frontend when you enter the website. Since D is my reference for powerful metaprogramming (besides Lisp), I was wonderting how to convert a piece of code from C++ to D. Pieces of code do not need to be identical, but should be similar. I discarded the use of macros:

Partial D Version:

Array!int stack;
void binop(string op)()
{
    stack[$ - 2] = mixin("stack[$ - 2] " ~
                         op ~ " stack[$ - 1]");
    stack.removeBack();
    writeln(stack[$ - 1]);
}

void process(in char[] token)
{
    alias Ops = AliasSeq!("+", "-", "*", "/", "%");
Lswitch:
    switch (token)
    {
        foreach (op; Ops)
        {
    case op:
            binop!op();
            break Lswitch;
        }

    case "=":
        writeln(stack[$ - 1]);
        stack.removeBack();
        break;

    default:
        stack.insertBack(token.to!int);
        break;
    }
}

Partial C++ version with the help of some utilities:

template <char Op>
void binop(std::vector<int> & s) {
   //Returns std::minus<>, std::plus<>, etc.
   using Op_t = opstr_to_func_object_t<Op>;

   s[s.size() - 2] = Op_t{}(s[s.size() - 2], s[s.size() - 1]);
   s.pop_back();
   std::cout << s[s.size() - 1] << '\n';
} 

template <char Op>
constexpr char op_name<Op> = std::integral_constant<char, Op>;

void process (std::string const & token, vector<int> & s) {
   using namespace std;
   constexpr auto ops = make_tuple(op_name<'+'>,
                                op_name<'-'>,
                                op_name<'*'>,
                                op_name<'/'>,
                                op_name<'%'>);

   Lswitch:
   switch (token[0]) {
     //Cannot expand cases inline

       // foreach([&](auto && elem) {
       //         constexpr auto op_str = decltype(elem)::value
       //
       //     }, Ops{});

      case "=":
          std::cout << s[s.size() - 1] << '\n';
          s.pop_back();
          break;
      default:
          s.push_back(stoi(token));
    }
 }

I have some questions:

  1. Is there a way to expand code inline and mix it with the environment or expand an optimized switch like the one in D without using macros? In D there is a foreach that expands the code inline for cases.

  2. Is there a way to simulate switch with the help of constexpr for strings?

  3. I used outside functions with template parameters because it seems that lambdas cannot specify template parameters, so I would have to use a verbose function object that captures the stack plus templated operator()(). Is there a way to workaround that and keep the functions process and binop in main?

I think the D version scores pretty high at the metaprogramming area, but would like to know how to do the best in C++.

Germán Diago
  • 7,473
  • 1
  • 36
  • 59
  • 1
    1. You can use variant for `operator` AST node and inline visitor ([here](https://github.com/tomilov/insituc/blob/master/include/insituc/shunting_yard_algorithm.hpp) you can find some intuitions). 2. You can use compile-time hashes for C-strings. – Tomilov Anatoliy Oct 20 '15 at 19:27

1 Answers1

2

Trying to translate some code of language 1 directly to language 2 is seldom the best solution if the goal is clean code and/or making use of the language features as much as possible. Rewriting your RPN calculator while ignoring the D implementation might be better... well, your questions:

1)
Tldr: Not directly.
A expanding case construct for switch in it's original syntax, without macros, just doesn't exist. (And something like a eval doesn't exist, C++ is a "hard compiled" language without any sort of interpreter and without embedded compiler in the program.)
But since everything is based on functors/lambdas here, why not make a map (std::map or anything else) to map symbols to functions, then just call the function for the symbol without using switch at all?

2)
Yes, this is possible.
switch alone only works with integers, but as you said yourself, a constexpr function taking a string and returning a int, with different strings getting mapped to different ints (hopefully), can be used.
The cases would look like

case myhashfunction("stringvalue"):  
...
constexpr unsigned long myhashfunction(const char *) {...}  

or, with user-defined literals, eg.

case "stringvalue"_hash:

Someone actually implemented CRC32: https://stackoverflow.com/a/9842857/3134621

3)
Tldr: If it should be inside the function, no.
While the auto keyword in lambdas has some template-like capabilities, it's not as powerful as full function templates (eg. no literals, no forcing the same type for multiple parameters etc.).
Making a functor class within a function, without the lambda syntax shortcut, isn't possible either: class definitions within function may not use templates.
So you'll need something outside of the function where the lambda/functor will be used.

Community
  • 1
  • 1
deviantfan
  • 11,268
  • 3
  • 32
  • 49
  • 1
    `no forcing the same type for multiple parameters`: due to parameter type deduction is strictly defined (in **C++14**) as left-to-right, then really we [can do it](http://coliru.stacked-crooked.com/a/f9946d65f7f9f14a). – Tomilov Anatoliy Oct 21 '15 at 15:34