Hey guys, so i'm writing a command system for my server and i ran in to a bit of trouble.
//sv cmds list
std::map<std::string, void*> sv_cmd_list;
//declare cmd utility funcs
static int cl_cmd(SOCKET cl_sock, const char* buff);
static void reg_cmd(const char* name, void* fn);
//declare raw cmd funcs
LPVOID message_cl_cmd(SOCKET cl_sock, const char* msg);
int main()
{
reg_cmd("message", (void*)messsage_cl_cmd);
return 0;
}
//define our cmd utility funcs
static int cl_cmd(SOCKET cl_sock, const char* buff)
{
return send(cl_sock, buff, strlen(buff), 0);
}
static void reg_cmd(const char* name, void* fn)
{
sv_cmd_list[name] = fn;
}
//define our raw cmd funcs
LPVOID message_cl_cmd(SOCKET cl_sock, const char* msg) {
if (cl_cmd(cl_sock, msg) == SOCKET_ERROR) {
printf("[error] failed to execute command 'message' (winsock api error %d)\n", WSAGetLastError());
} else {
printf("command 'message' executed sccessfully (msg sent: %s)\n", msg);
}
return 0;
}
Now, the goal here is so that all i have to do is call reg_cmd("name", fn_ptr); and im good. The problem I've ran into is calling, I'm not exactly sure how to do so.
I was thinking something along the lines of sv_cmd_list[ strInputCmd ]( args ); but obviously this won't work.
any ideas on how i could call via the func pointer?
The issue is you are storing the function pointers as void* (and note this will not work with member function pointers, which may be triple the size of void*!). What you want is to map some arbitrary function to be called with arbitrary arguments and an arbitrary result.
The issue is that this is not very easy to do, and there's no really ready made solution. An option is to use std::function, or an actual function pointer, but that won't work for capturing lambdas or calling the member function of a specific class instance. The problem with something like std::function is that it expects a specific interface, the argument type and return type. Your command table does not seem to have an interface of this sort.
There are two types of checks that need to be done. An arity check (you have the correct amount of arguments) and a type check. The arity check is quite easy, count the number of args in the function, but the type check is not. In fact, the type check is impossible in the general case, such as when you want to store a template function (including lambdas with auto parameters). Of course, if we only deal with concrete types the problem is solvable albeit a pain to implement if you don't rely on RTTI, because you will probably run into ODR violations unless you are very careful.
I hope that was some useful food for thought, I have a basic sketched out idea on how to do this and can point you in the right direction if you'd like (it involves delegate functions, sorting by arity, and a RTTI-free type to integer mapping).
Well, the goal here is to make everything dynamic with no hard coded stuff where i'll need to add a nested if for every command i add. So, I don't necessarily need to do it this way, this just seemed like the EASIEST way to accomplish my goal of avoiding adding hard coded if statements for every single command i add as opposed to it being as easy as reg_cmd( args );
Any advice or some example code to put me in the right direction would be much appreciated.
It might be easier to embed Lua instead to be honest, it would handle most of this for you.
I'll write up a small snippet on how to accomplish this in a bit, got a lot of work on my plate right now.
I can embed lua on my own, i've done it before, and you're actually not the first person to recommend this option to me. However, it seems like overkill to embed a lua engine in my server (which is only like 350 lines atm lol) just to avoid a hacky c++ workaround.
Thoughts?
If you scale back the functions that are accepted to be generalized in some way (only accept strings for example) then it becomes much easier. However building a fully generalized function mapper can be quite complicated, and at that point you're a few steps away from an interpreted language (not exactly of course but you're entering that arena).
If you only really care about function pointers and aren't afraid of bad type input you can do this:
std::map<std::string, void*> sv_cmd_list;
template <typename Ret, typename... FuncArgs, typename... Args>
Ret call(const std::string& name, Args&&... args) {
auto func = static_cast<void(*)(FuncArgs...)>(sv_cmd_list[name]);
return func(std::forward<Args>(args)...);
}
// Usage
int foo(float);
call<int, float>("foo", 3.4f);
// Can be improved to something like this:
call<int(float)>("foo", 3.4f);
I was thinking something along the lines of sv_cmd_list[ strInputCmd ]( args ); but obviously this won't work.
Why won't this work?
I figured it out, and made it much more simple actually. I took advantage of the 'decltype' function and did
(decletype(&catalyst_command_function)(sv_cmd_list[ str_parsed_cmd_from_input ]))(arg1, arg2, arg3);
If you only have one interface (which I have discussed earlier) then you can store them as void(*)(const char *,void*) instead of void*.
Sorry, you need to Log In to post a reply to this thread.