2

I'm doing a parsser for a class call virtual_machine, I'm trying to build a vectors of function for it, but some of those function on vm takes arguments (different numbers/types of arguments) can i still put those in my vector of fuction since they where only void (*f)();

here's the code


class Virtual_Machine {
    public:
      /***/
        void clear();
        void pop();
        void clear();
        void assert(std::string const &value);
        void push(eOperandType const &e, std::string const &str);
      /***/
}

class Parser {
    public:
       /***/
        void prepare();
        void add_func_no_arg(void (Virtual_Machine::*f)(), std::string comand);

    private:
        Virtual_Machine vm;
        std::vector<std::string> command_no_arg;
        std::vector<void (Virtual_Machine::*)()> func_no_arg;
        /***/

};

void Parser::add_func_no_arg(void (Virtual_Machine::*f)(), std::string comand)
{
    command_no_arg.push_back(comand);
    func_no_arg.push_back(f);

}

void Parser::prepare()
{
    add_func_no_arg(&Virtual_Machine::dump,"dump");
    add_func_no_arg(&Virtual_Machine::pop,"pop");
    add_func_no_arg(&Virtual_Machine::clear,"clear");
}
void Parser::use_exemple()
{
    // dump :
    (vm.*(func_no_arg[0]))();
}

this is ok but now I will to kwon if it's possible to add push() and assert() toi my vector of funtions , what do i need to do ? i was thinking maybe templat but i realy don't see how

  • https://stackoverflow.com/a/32041381/1116364 – Daniel Jour Jul 15 '20 at 20:11
  • A vector (or array) can only hold 1 type of element. To do what you are attempting, all of the stored functions need to have the same signatue. You could make your vector hold `std::function` elements that point to lambdas or `std::bind()`'ed objects to hide away any extra parameters. Or alternatively make the vector hold `std::variant` or `std::any` instead. Otherwise, redesign your interface to let all stored functions have a single signature that takes a vector/array parameters or variadic parameters as input. – Remy Lebeau Jul 15 '20 at 20:45
  • Suppose, for the sake of argument, that you somehow managed to store function pointers of different types in the vector. How do you plan to use that vector? How do you expect to know which elements need arguments to be called, and which don't? – Igor Tandetnik Jul 16 '20 at 03:51
  • I can not see a use case for the stuff! If you have a function pointer for a functin which takes "sometimes" arguments, how you will get them? At this point you need some kind of switch in your code to put the args into the call to the function. That reverses the intend of your code. As this, the design will not work, fully independent of which kind of solution you have for your vector. – Klaus Jul 16 '20 at 07:21
  • @Klaus: I assume the use case is to add something like a scriptable interface to the vm. The parser class could read in some text file containg the string aliases set in `prepare` and the respective arguments. – Stefan Scheller Jul 16 '20 at 07:26
  • @StefanScheller: Quite clear, but it must be implemented! And at this point somewhere args must be put to the functions depending of their type. – Klaus Jul 16 '20 at 07:30
  • @Klaus I think the conversion to the corresponding types could be done in the methods of `Virtual_Machine` (where they are known). The parser could then generically read a different number of string arguments and call the corresponding methods without actually knowing the final types. – Stefan Scheller Jul 16 '20 at 07:39

1 Answers1

-1

Daniel Jour's extensive linked answer shows how hiding away the arguments can be achieved with packing and boost::any and is probably a better solution (but I didn't fully understand it). As alternative you can use variadic functions as in the example below.

Please take my code with a grain of salt, I encountered some restrictions and I am not an expert on the topic:

  • you cannot receive reference type arguments through ...
  • const arguments are promoted to non-const ones (in my test const int got promoted to int)
  • I assume variadic functions are a security risk. Please check/ sanitize the input if the function arguments are provided by a user.

Here is the code example (note that I renamed Virtual_Machine::assert to Virtual_Machine::vmassert to use assert from <cassert>):

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <cstdarg>

class Virtual_Machine
{
    private:
        std::string name;

    public:
        Virtual_Machine(std::string n) : name(n) {}
        void clear(size_t nargs, ...){std::cout << "Clear " << name << std::endl;};
        void dump(size_t nargs, ...){std::cout << "Dump" << std::endl;};
        void vmassert(size_t nargs, ...)
        {
            va_list args;
            va_start(args, nargs);

            assert(nargs == 1);
            const std::string value(va_arg(args, std::string));
            std::cout << "Assert " << value << std:: endl;

            va_end(args);
        }
        void push(size_t nargs, ...)
        {
            va_list args;
            va_start(args, nargs);

            assert(nargs == 2);
            const int e(va_arg(args, int));
            const std::string str(va_arg(args, std::string));
            std::cout << "Push " << e << ", " << str << std:: endl;
            va_end(args);
        }
};

class Parser
{
    private:
        Virtual_Machine vm;
        std::vector<std::string> commands;
        std::vector<void (Virtual_Machine::*)(size_t, ...)> funcs;

    public:
        Parser(std::string vm_name) : vm(vm_name) {}
        void add_func(
                void (Virtual_Machine::* f)(size_t, ...),
                std::string command)
        {
            commands.push_back(command);
            funcs.push_back(f);
        }
        void prepare()
        {
            add_func(&Virtual_Machine::clear, "clear");
            add_func(&Virtual_Machine::dump, "dump");
            add_func(&Virtual_Machine::vmassert, "assert");
            add_func(&Virtual_Machine::push, "push");
        }
        void test()
        {
            (vm.*(funcs[0]))(0);
            (vm.*(funcs[1]))(0);
            (vm.*(funcs[2]))(1, std::string("abc"));
            (vm.*(funcs[3]))(2, 42, std::string("def"));
        }
};

int main()
{
    Parser a("vm_a");

    a.prepare();
    a.test();

    return 0;
}

I changed the signature of all Virtual_Machine member functions to (size_t nargs, ...) in this way you have a uniform type for all functions that you can use to declare Parser::funcs. Each function now has to be called with the number of arguments followed by the actual arguments.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Stefan Scheller
  • 953
  • 1
  • 12
  • 22