0

I am searching for a solution to my problem.

I have a situation where I create a library. My library lets users add commands and store them. Each command should have a function pointer which can be set to a user created callback function.

What I want is a scenario where the callback functions can be any type. What I mean by any type is any kind of return type with any types of arguments.

My datastruct would look something like this:

struct Command{
    uint64_t alias;
    anytype/amount args...;
    anytype (*f_ptr);
};

Then my library would go over an array of commands and call the correct command with its corresponding callback.

I found a solution here, but the functions used are not supported on C++11. How should I approach this problem?

Further information

It might be good to have some background information.

The library is built on top of MQTT. I want the user to be able to specify commands it can receive from MQTT. I want to give the user full functionality by letting them think of their own commands.

If a message comes in through MQTT and it corresponds with the self-created command I want to execute their assigned callback function to let them handle their own commands.

Again, since I want to give as much functionality as possible I want to allow any type of callback function. This is when I came up with the command struct solution.

J.Meulenbeld
  • 185
  • 1
  • 17
  • Remember that C++ is a strongly typed compiled language. That means all types must really be known at compile-time. – Some programmer dude Sep 05 '22 at 07:15
  • I understand that, The user of the library would specify the callback function, therefore it should be known at compile time. I am not planning on letting them change the callbacks later in the program. – J.Meulenbeld Sep 05 '22 at 07:18
  • Do you know the argument *values* already in the moment you want to store the function pointer? – Klaus Sep 05 '22 at 07:18
  • @Klaus I am planning to allow any type of argument, my idea is to then let them pass the values or other types **once** at the creation of the command – J.Meulenbeld Sep 05 '22 at 07:20
  • 1
    This seems like a [XY problem](https://xyproblem.info/). If you want to add commands to your program, you need a different approach for managing types. However, my recommendation for your initial approach would be to use an enum of possible types and do the casting of the result of a function that returns a (void*) type pointer. – Miguel Sep 05 '22 at 07:20
  • 1
    if you are not going to use the returned values then you can restrict yourself to functions with void return. – 463035818_is_not_an_ai Sep 05 '22 at 07:21
  • @Miguel Could you elaborate a bit about the enum approach? I don't quite get where I would use the enum types. I agree with **463035818_is_not_a_number** that I would not need a return type, just the arguments – J.Meulenbeld Sep 05 '22 at 07:33
  • 5
    Consider replacing your design with the GOF command design pattern. Let your users create the appropriate subclass of command and setup their own constructor parameters and execute() function which uses those constructor parameters. Then in your code just call execute at the appropriate time. As an alternative, you can let them set up a lambda function which you then call at the right time. – Gonen I Sep 05 '22 at 07:34
  • @GonenI I like this approach, I will look into it! – J.Meulenbeld Sep 05 '22 at 07:37
  • *"but the functions used are not supported on C++11"* -- to which functions are you referring? The accepted answer for that other question uses `std::bind` and `std::function`, both of which were introduced in C++11. – JaMiT Sep 05 '22 at 07:42
  • @JaMiT I am referring to `std::invoke_result_t`, and the `auto call()` function – J.Meulenbeld Sep 05 '22 at 07:46
  • 1
    for me it sounds a capturing lambda can do all of what you want without any line of own written code at all. – Klaus Sep 05 '22 at 07:47
  • 1
    @J.Meulenbeld You're not making any attempt to use the value returned by a callback, are you? Just replace the answer's generic `ReturnType` and `auto` with `void`, and you're good to go. That is, store the callback in a `std::function` (this works even if the callback nominally returns a value by ignoring the returned value). – JaMiT Sep 05 '22 at 07:54
  • @Klaus do you perhaps have an example of how this would work? How would I accept a generic lambda and store it? – J.Meulenbeld Sep 05 '22 at 07:56
  • @JaMiT You are right about the return type, the main thing I am trying to figure out is to allow any type of argument for the user. This could be an int, double or even a class. I tried it with the `std::function` But this callback function doesn't allow any arguments if I am correct. – J.Meulenbeld Sep 05 '22 at 07:58
  • 3
    @J.Meulenbeld -- Are you trying to do something [like this?](https://godbolt.org/z/oz3W5GjMM). As long as the class is derived from `Base`, a user has full control over how the class is created, what the parameters are, etc. This is basically what was already mentioned with the `execute` function, but instead overloads the call operator (`operator()`) – PaulMcKenzie Sep 05 '22 at 08:41
  • You can encapsulate commands in classes, also the method arguments can be classes and the methos itself con receive a vector of argumets (base classes). You can encapsulate the return value of the method too. Something like `BaseReturnValue * execute(vector args);` – Marco Beninca Sep 05 '22 at 08:55
  • @J.Meulenbeld *"But this callback function doesn't allow any arguments if I am correct."* -- well, it does not allow your library to supply any arguments, but your library has no arguments to supply, right? At least, no arguments other than those supplied by the user as part of creating a `Command`? Same thing as in that accepted answer -- instead of your library specifying how the arguments are stored, let `std::bind` (or a lambda, at the user's option) handle that detail. Don't micromanage. – JaMiT Sep 05 '22 at 18:08
  • @J.Meulenbeld Who drives the types: the caller or callee? Are there overloads? Depending on the answer, a very quick solution might be possible. – lorro Sep 05 '22 at 20:26
  • Unless you can give a reason why your structure cannot be `struct Command{ uint64_t alias; std::function fun; };` (letting the user decide how to bind the arguments into the functor -- lambda captures typically are convenient), I'm inclined to close this as a duplicate of the linked question. – JaMiT Sep 07 '22 at 01:44

0 Answers0