0

I have a std::map where I store some arbitrary methods that I want to call later.

map<string, function<void(void)>> methods;

template<typename R, typename ...P>
void storeMethod(const string& name, const function<R(P...)>& method) {
    methods[name] = method;
}    

A the time of calling, I get the parameters to call the method with in a vector<void*>, where the first element is a pointer where I will store the return value.
How do I automatically cast these parameters to the corresponding types of the method I want to call?
Should I store the types in some way maybe?

void callMethod(const string& name, vector<void*> parameters) {
    auto method = methods[name];
    // How to call 'method' with all the parameters casted to the required types?
}

For example, if I orignally called storeMethod() with a function<int(string a, float b)>, I need a generic way to call it in callMethod() like this:

*((int*)parameters[0]) = method(*((string*)parameters[1]), *((float*)parameters[2]));
debevv
  • 145
  • 2
  • 8
  • 1
    why do you get a parameters as `vector` ? Using `void*` is sometimes needed when C is involved, but a `vector` is not something you ever need – 463035818_is_not_an_ai Jun 18 '20 at 07:55
  • 2
    `std::function` can store a function (and wrap it with type-erasure) that does not take any arguments and does not return anything. You cannot store inside a function that accepts arguments or returns. – Daniel Langr Jun 18 '20 at 08:00
  • In C++, you should use templates and parameters pack instead of this weird `std::vector` to forward parameters in a generic way. – Fareanor Jun 18 '20 at 08:01
  • _"If I orignally called `storeMethod()` with a `function`..."_ — That's not possible: https://godbolt.org/z/-y33BA. – Daniel Langr Jun 18 '20 at 08:07
  • @DanielLangr isnt there some specialization of `std::function` that can store functions of any signature? I remember something like that but cannot find it anymore – 463035818_is_not_an_ai Jun 18 '20 at 08:11
  • @idclev463035818 there's [this](https://stackoverflow.com/questions/45715219/store-functions-with-different-signatures-in-a-map/45718187#45718187), but it's horrible – Caleth Jun 18 '20 at 08:14
  • @idclev463035818 Don't know about specialization, but there is `std::any`. – Daniel Langr Jun 18 '20 at 08:15
  • @Caleth then I remember wrong. I am not a fan of `std::function` anyhow. Imho it is too often over- and misused. It looks tempting, but actually often it isnt really needed – 463035818_is_not_an_ai Jun 18 '20 at 08:16
  • please provide a bit more context. How do you store methods in the map? What functions to you want to store? How do you call them? Why are the parameters coming as a `vector`? Type safety is a good thing, its nothing you should give up for "convenience" – 463035818_is_not_an_ai Jun 18 '20 at 08:18
  • I would simply say that if you want the functionality described in the question, then C++ is definitely not the right programming language for this task. – Daniel Langr Jun 18 '20 at 08:19
  • - Parameters come as `void*` because I'm dealing with a C library - @Fa – debevv Jun 18 '20 at 08:20
  • 3
    "void* because I'm dealing with a C library" ... ok but a `vector` cannot come from a c library. How do you know what is the actual type? – 463035818_is_not_an_ai Jun 18 '20 at 08:21
  • I used `vector` just to simplify things, in reality is C struct with a C array and its size and some other stuff – debevv Jun 18 '20 at 08:22
  • 1
    Is the set of possible types that come from that C library limited to some predefined subset? – Daniel Langr Jun 18 '20 at 08:22
  • @Fareanor can you give me more info about this? – debevv Jun 18 '20 at 08:23
  • @DanielLangr no, it's a scripting library so functions should work with any type – debevv Jun 18 '20 at 08:24
  • If I correctly understand the parameters are generated at runtime as user input. In that case you can't use templates. – Thomas Sablik Jun 18 '20 at 08:27
  • @ThomasSablik in fact I was thinking about a solution based on storing the required types along with the function, then "iterate" them at the time of calling, Do you think it is possible in c++? – debevv Jun 18 '20 at 08:30
  • @DanielLangr it can be done in c++, it is a bit hacky and I wouldn't use it in production, but it is fun ;) – 463035818_is_not_an_ai Jun 18 '20 at 08:33
  • @debevv Take a look at the `OpaqueFunctionImpl` from the [Caleth's answer](https://stackoverflow.com/a/62445932/11455384), this is what I meant by the use of templates parameter pack. You can find the information you requested about it in the [documentation](https://en.cppreference.com/w/cpp/language/parameter_pack) – Fareanor Jun 18 '20 at 08:59

2 Answers2

1

You will have to wrap method in something that can remember the parameter types.

struct OpaqueFunction {
    virtual std::any call(const std::vector<std::any> &) = 0;
};

template <typename R, typename ... Args>
struct OpaqueFunctionImpl : OpaqueFunction {
    OpaqueFunctionImpl(std::function<R(Args...)> f) : f(std::move(f)) {}

    std::any call(const std::vector<std::any> & parameters) override {
        return call_impl(parameters, std::index_sequence_for<Args...>{});
    }
private:
    template <size_t... I>
    std::any call_impl(const std::vector<std::any> & parameters, std::index_sequence<I...>) {
        return f(std::any_cast<Args>(parameters.at(I))...);
    }
    std::function<R(Args...)> f;
};

class Methods {
    std::map<std::string, std::unique_ptr<OpaqueFunction>> methods;
public:
    template<typename R, typename ... Args>
    void storeMethod(std::string name, std::function<R(Args...)> method) {
        methods[std::move(name)] = std::make_unique<OpaqueFunctionImpl<R, Args...>>(std::move(method));
    }

    template<typename R>
    R callMethod(const std::string & name, const std::vector<std::any> & parameters) {
        return std::any_cast<R>(methods.at(name)->call(parameters));
    }
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
0

You can create a Callable type to functions that take different types of arguments and then, store that in a vector (using vector here for simplicity):

struct Callable {
    Callable(std::function<void()> f) : zero_(std::move(f)) {}
    Callable(std::function<void(int)> f) : one_(std::move(f)) {}

    void operator()() { zero_(); }
    void operator()(int x) { one_(x); }

    std::function<void()> zero_;
    std::function<void(int)> one_;
};

//vector of methods
std::vector<Callable> methods;

To store a method, you can either use a template-ized function or just use overloads. I am using overloads here:

void addMethod(std::function<void()> func)
{
    methods.push_back(Callable(func));
}

void addMethod(std::function<void(int)> func)
{
    methods.push_back(Callable(func));
}

And then to finally call a function:

template<typename ...Args>
void callMethod(int idx, Args ...args) {
    auto method = methods[idx];

    method(std::forward<Args>(args)...);
 }

Main:

int main()
{
    addMethod([](int x){
        std::cout << "I am function(int)" << x << '\n';
    });

    addMethod([](){
        std::cout << "I am just function()\n";
    });


    callMethod(0, 200);
    callMethod(1);
}

This is the simplest possible way I can think of to achieve this. Their might be better ways and I am really curious about them.

Try it out here: https://godbolt.org/z/HS5a7p

Waqar
  • 8,558
  • 4
  • 35
  • 43