Let's say I multiple functions with variable arguments:
void greetWorld() {
cout << "Hello World!" << endl;
}
void greetName(const string& name) {
cout << "Hello " << name << "!" << endl;
}
void printAddition(const int lhs, const int rhs) {
cout << "Addition: " << to_string(lhs + rhs) << endl;
}
And these are stored in a map of std::strings to functions (functions being stored as a polymorphic class).
template<typename... Args>
class DerivedFunction;
class BaseFunction {
public:
template<typename... Args>
void operator()(Args... args) const {
(*static_cast<const DerivedFunction<Args...>*>(this))(args...);
}
};
template<typename... Args>
class DerivedFunction : public BaseFunction {
public:
DerivedFunction(void(*function)(Args...)) {
this->function = function;
}
void operator()(Args... args) const {
function(args...);
}
private:
void(*function)(Args...);
};
template<typename... Args>
unique_ptr<DerivedFunction<Args...>> make_function(
void(*function)(Args...)
) {
return std::make_unique<DerivedFunction<Args...>>(function);
}
int main() {
unordered_map<string, unique_ptr<BaseFunction>> function_map;
function_map.insert({ "greetWorld", make_function(&greetWorld) });
function_map.insert({ "greetName", make_function(&greetName) });
function_map.insert({ "printAddition", make_function(&printAddition) });
...
}
I can call the functions at compile time like:
int main() {
...
(*function_map.at("greetWorld"))();
(*function_map.at("greetName"))("Foo"s);
(*function_map.at("printAddition"))(1, 2);
}
If I then have a string, or stream like:
greetWorld
greetName string Foo
printAddition int 1 int 2
What would be a good way to call the functions? I can not figure out any way to cast a type at runtime.
Why?
I am trying to implement some kind of remote call procedure for learning purposes. I do not want to use an external library as I am trying to learn how to implement this with the C++ standard library for the sake of understanding C++ more.
What have I tried?
Not much. I've tested creating functions that take a std::vector of std::anys as an argument, and then had the function any_cast them to the type they are. Whilst this does work, it does not look nice, it requires duplicates of all functions, I would rather be able to write functions with meaningful arguments than ambigious.
Minimum Example
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
using namespace std;
void greetWorld() {
cout << "Hello World!" << endl;
}
void greetName(const string& name) {
cout << "Hello " << name << "!" << endl;
}
void printAddition(const int lhs, const int rhs) {
cout << "Addition: " << to_string(lhs + rhs) << endl;
}
template<typename... Args>
class DerivedFunction;
class BaseFunction {
public:
template<typename... Args>
void operator()(Args... args) const {
(*static_cast<const DerivedFunction<Args...>*>(this))(args...);
}
};
template<typename... Args>
class DerivedFunction : public BaseFunction {
public:
DerivedFunction(void(*function)(Args...)) {
this->function = function;
}
void operator()(Args... args) const {
function(args...);
}
private:
void(*function)(Args...);
};
template<typename... Args>
unique_ptr<DerivedFunction<Args...>> make_function(
void(*function)(Args...)
) {
return std::make_unique<DerivedFunction<Args...>>(function);
}
int main() {
unordered_map<string, unique_ptr<BaseFunction>> function_map;
function_map.insert({ "greetWorld", make_function(&greetWorld) });
function_map.insert({ "greetName", make_function(&greetName) });
function_map.insert({ "printAddition", make_function(&printAddition) });
cout << "Calling functions at compile time." << endl << endl;
(*function_map.at("greetWorld"))();
(*function_map.at("greetName"))("Foo"s);
(*function_map.at("printAddition"))(1, 2);
//cout << endl << "Calling functions at runtime." << endl << endl;
//string runtime =
// "greetWorld\n"
// "greetName string Foo\n"
// "printAddition int 1 int 2";
//
// todo: call functions
}
Solved.
If you apply the accepted solution, you can call functions from the text like I had wanted. Here is new code for an example Tcp server and client. The client sends function names and arguments as a string to the server. The server then executes these. Exactly what I wanted.
struct FunctionNameAndArguments {
string function_name;
vector<RPC> arguments;
};
FunctionNameAndArguments parseFunctionNameAndArguments(
const string& function_name_and_arguments_string
) {
istringstream ss(function_name_and_arguments_string);
FunctionNameAndArguments function_name_and_arguments;
// function name
ss >> function_name_and_arguments.function_name;
// arguments
auto& arguments = function_name_and_arguments.arguments;
while (!ss.eof()) {
string function_type;
ss >> function_type;
// integer
if (function_type == "int") {
int value;
ss >> value;
arguments.push_back(value);
}
// string
else if (function_type == "string") {
string value;
ss >> value;
arguments.push_back(value);
}
else {
throw exception("unknown argument type");
}
}
return function_name_and_arguments;
}
int main() {
unordered_map<string, RPCHandler> functions = {
{ "greetWorld", make_invoker(&greetWorld) },
{ "greetName", make_invoker(&greetName) },
{ "printAddition", make_invoker(&printAddition) }
};
char server;
cout << "Server? (y/n): " << endl;
cin >> server;
// server
if (server == 'y') {
// accept client
TcpListener listen;
listen.listen(25565);
TcpSocket client;
listen.accept(client);
size_t received;
// receive size of string
size_t size;
client.receive(&size, sizeof(size), received);
// receive function name and arguments as string
string function_name_and_arguments_string;
function_name_and_arguments_string.resize(size);
client.receive(
function_name_and_arguments_string.data(),
size,
received
);
// go through each line
istringstream lines(function_name_and_arguments_string);
string line;
while (getline(lines, line)) {
// parse function name and arguments
auto [function_name, arguments] = parseFunctionNameAndArguments(
line
);
// call function
functions.at(function_name)(
arguments
);
}
}
// client
else {
// connect to server
TcpSocket server;
server.connect("localhost", 25565);
// function calls string
const string function_calls =
"greetWorld\n"
"greetName string Foo\n"
"printAddition int 1 int 2";
size_t size = function_calls.size();
// send size of string
server.send(&size, sizeof(size));
// send function calls string
server.send(function_calls.data(), size);
}
}