Can I create a function table regardless of the parameter signature?

I want to call all types of functions from one table. (Consider all return data types are invalid)

To illustrate what I am talking about, here is some code that clearly does not work.

#include <iostream>
#include <map>
#include <functional>

void foo(int x){std::cout<<x;}
void bar(){std::cout<<"bar";}


std::map<std::string, std::function<void()>> map = 
{
    {"foo", foo},
    {"bar", bar}
};

int main()
{
    map["foo"](2);
    map["bar"]();
}

      

I don't mind the C style solution.

+3


source to share


4 answers


You can declare an old-style C function pointer a function pointer of the type:

 foo(...);
 bar(...);

 std::map<void(*)(...)> map = 
 {
    {"foo", foo},
    {"bar", bar}
 };

      

but then foo and bar must follow the conditional convention with varaargs, va_start, etc. and you can pull the C POD out of the list. I don't know if it's worth the trouble. The caller still needs to know how many arguments to pass.



It looks like you might be rethinking your design.

If, for example, it is assumed that this is a command table for its CLI type, it is better to pass to std::vector<std::string>

each potential command and find out if the vector is of the correct size () for its purpose.

+1


source


If you abandon the type system entirely, you can use boost::any

as long as all types are exactly the same as everywhere else. Right now only works with explicitly creating all std :: functions, but I'm sure there is a workaround for that too (update added overloading for free functions):

class Functions
{
public:
    template <typename... T>
    void add_function(const std::string& name, void (*f)(T...))
    {   
        fs[name] = std::function<void(T...)>{f};
    }   


    template <typename... T>
    void add_function(const std::string& name, std::function<void(T...)> f)
    {   
        fs[name] = f;
    }   

    template <typename... T>
    void call(const std::string& name, T... args)
    {   
        auto it = fs.find(name);
        if (it != fs.end()) {
            auto f = boost::any_cast<std::function<void(T...)>>(&it->second);
            if (f) {
                (*f)(args...);
            }   
            else {
                std::cout << "invalid args for " << name << std::endl;
            }
        }   
        else {
            std::cout << "not found: " << name << std::endl;
        }   
    }   

private:
    std::map<std::string, boost::any> fs; 
};

void baz() {
    std::cout << "baz" << std::endl;
}

int main() {
    std::function<void()> foo = []{ std::cout << "foo" << std::endl; };
    std::function<void(int)> bar = [](int i){ std::cout << "bar(" << i << ")" << std::endl;     
};

    Functions f;
    f.add_function("foo", foo );
    f.add_function("bar", bar);
    f.add_function("baz", baz);

    f.call("foo");
    f.call("bar", 42);
    f.call("baz");
}

      



Functional, yes. A good idea? Also note that it f.call("bar", 42u)

will fail because you have to get each type exactly.

+1


source


I changed your approach a bit and didn't forget that this is just an example, I'm sure it won't compile like this, but it will give you an idea of ​​what I meant. You can register your functions with an additional structure and then call the appropriate parameter passing.

struct Funcs
{
    std::function<void(int)> _f1;
    std::function<void()>    _f2;

    template<typename args...>
    void call(std::string&& f_name, args...)
    {
        if(f_name == "foo")
          _f1(std::forward(args)...)
        if(f_name == "bar")
          _f2(std::forward(args)...)
    }
}


int main()
{
    Funcs f;
    f.call("foo", 2);
}

      

0


source


If you really want to keep a function and can always figure out how to call it correctly, you can extend Oncaphillis' approach and just go for using function pointers:

void foo(int);
float bar(double, struct baz);

std::map<void(*)()> map = {
    {"foo", (void(*)())foo},
    {"bar", (void(*)())bar}
};

      

Then you can discard them when using:

//code to make sure that map["foo"] is of type `void(*)(int)`
(*(void(*)(int))map["foo"])(42);

//code to make sure that map["bar"] is of type `float(*)(double, struct baz)`
float result = (*(float(*)(double, struct baz))map["foo"])(3.14159, (struct baz){ /*whatever*/});

      

As you can see, there is no point in calling any type of function in this way, not limited to variational ones. However, this approach is very error-prone as you completely eliminate the safety provided by the type system and your casts must be 100% correct. Strange things can happen if you don't. This is the same problem as when using boost::any

.

0


source







All Articles