3

I want the server to support registering any functions. may something like these:

server.register("Add", [](int a, int b){cout << a+b << endl;});
server.register("Echo", [](string str){ cout << str << endl; });
...
server.Call("Add", 12, 13);

However, how can I hold all these functions? They have different arguments. Is there any method like this: unordered_map<string, ?> functions_

Mengyu Chen
  • 137
  • 6

2 Answers2

3

Here's an implementation that uses any (demo):

#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <any>

using namespace std;

class server_t
{
public:

  template <typename F>
  void register_function(const string& name, F&& fun)
  {
    m[name] = function(forward<F>(fun));
  }

  template <typename... A>
  void call(const string& name, A&&... args)
  {
    auto it = m.find(name);
    if (it == m.end())
      throw logic_error(name + " not found");
    else
      any_cast<function<void(A...)>>(it->second)(forward<A>(args)...);
  }

private:
  map<string, any> m;
};

int main()
{
  server_t server;

  server.register_function("Add", [](int a, int b) { cout << a + b << endl; });
  server.register_function("Echo", [](string s) { cout << s << endl; });

  try
  {
    server.call("Add", 1, 2);
    server.call("Echo", string("Howdy!"));
    server.call("Subtract", 2, 1); // will throw: wrong name
    server.call("Add", 1, 2, 3); // will throw: wrong number of arguments
    server.call("Add", 1, 2.3); // will throw: wrong argument type (double instead of int)
  }
  catch (const exception& e)
  {
    cout << e.what();
  }
}

[EDIT]

"...if i want to register like this : server. Register(..., [](string s, string& t){ t=s; }). Then, a failure will occur."

call is not able to infer the types of arguments that the stored function expects. If you don't pass the exact types it expects, any_cast will fail - which is better than crashing the application. The following should work:

//...
server.register_function("Put", [](const string& s, string& d) { d = s; });

try
{
  const string s = "abc";
  string d;
  server.call("Put", s, d);
  server.call<const string&, string&>("Put", "xyz", d);
  //...
zdf
  • 4,382
  • 3
  • 18
  • 29
  • Thank you for your answer. I have tried it, and it can work well. However, if i want to register like this : server.register(..., [](string s, string& t){ t=s; }). Then, a failure will occur. the reason is that `it=m.find(name)` will be nullptr. – Mengyu Chen Apr 01 '23 at 10:05
2

Probably not the best solution, but something like the following works for your example:

#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
 
class Server {
private:
    using erasedType = void (*)();
    unordered_map<string, erasedType> funcs;
public:
    template<typename... Args>
    void Register(const string& name, void (*func)(Args...)) {
        funcs[name] = reinterpret_cast<erasedType>(func);
    }
 
    template<typename... Args>
    void Call(const string& name, Args... args) {
        using funcType = void (*)(Args...);
        auto func = reinterpret_cast<funcType>(funcs.at(name));
        return func(args...);
    }
};
 
int main() {
    Server server;
    server.Register("Add", +[](int a, int b){ cout << a+b << endl; });
    server.Register("Echo", +[](string str){ cout << str << endl; });
 
    server.Call("Add", 12, 13);
    server.Call("Echo", string("hello"));
 
    return 0;
}

Online Demo

Note, this approach works only for C-style function pointers, as a function pointer of one type is allowed to be casted to a function pointer of another type. This includes non-capturing lambdas, which are convertible to function pointers.

This approach will not work for other types of callables, including capturing lambdas, functors, etc.

However, be very careful with the parameters you pass to Call(). If you don't match the parameter types correctly, undefined behavior occurs. For instance, the need to pass "hello“ as std::string("Hello"), otherwise the Echo() lambda gets invoked as void(*)(const char*) instead of as void(*)(std::string), so its std::string parameter won't get constructed properly.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770