2

I can't and won't bore you with the details, but my system has these specific requirements:

  1. Actions must be called and registered at runtime.
  2. Each Action can have multiple targets, and these targets should be processed in a loop.
  3. Actions may accept special arguments, but any potentially available action can be invoked through a generalized Call function at runtime.
  4. If incorrect argument count or types are passed to the Call function, it should return false instead of causing a runtime crash.

Conceptually, this is quite simple: We register an action, and then we call the action by its name and with arguments of given type. This would mean the call can be done from anywhere in code at runtime.

Smallest reproducible example: https://onlinegdb.com/qNErEq_85

Currently, an example Action looks like this:

bool DoAction(const int Target, ...)
{
    va_list args;
    va_start(args, Target); // Initialize the va_list with the last named parameter

    // Assume the first argument after Targets is a std::string
    const char* stringArg = va_arg(args, const char*); // Get the string argument
    std::string myString(stringArg); // Convert it to std::string

    va_end(args); // Clean up the va_list

    std::cout << myString << " \n";

    return true;
}

This is how the Call() function looks like:

bool Call(const int Target, ...) override
{
    std::cout << "called";
    
    va_list args;
    va_start(args, Target);

    bool returnValue = (Instance->*ActionFunction)(Target, args);

    va_end(args);

    return returnValue;
}

The critical issue with this implementation is that va_list consumes the arguments, so they cannot be nested.

I have tried replacing virtual with a template, but the std::map<std::string, Action*> RegisteredActions; still requires derivation. dynamic_cast into the derived type is however not possible, because Call() should not take any templates or arguments other than the function name and function arguments (otherwise there would not be much point to using this system).

Solutions I have not tried:

  1. Hard-coding specific argument types. This should work, but then there wouldn't be much of a point to using this system.
  2. Make Call() a macro. This could work, but I haven't figured out how.
  3. Not doing this.

There are no questions about this in the site, it's mostly about asking about specific compilation errors. My question is how to implement a specific type of runtime functionality.

How do I create nested variadic functions?

7
  • side note: unless you really need it, you could get rid of the singleton machinery by using static member methods instead. This would make the code a little bit more readable. Commented Oct 16, 2024 at 8:25
  • Is it mandatory for you that DoAction takes an ellipsis instead of template variadics ? Commented Oct 16, 2024 at 8:34
  • 1
    Are you familiar with variadic template functions and parameter packs? You should really avoid "C"'s va_args (they are not typesafe) Commented Oct 16, 2024 at 8:52
  • variadic template functions cannot be virtual Commented Oct 16, 2024 at 9:41
  • You have a comment saying the first argument is a std::string, and then you immediately assume it's really a const char *. Which is used at the call site? They're not interchangeable just because one is constructible from the other... Commented Oct 16, 2024 at 11:10

2 Answers 2

2

You might get rid of C variadic and use types from C++:

std::map<std::string, std::function<bool(int, std::vector<std::any>)>> RegisteredActions{
    { "FooAction", &FooAction},
    // ...
};

RegisteredActions["BarAction"] = &BarAction;

Function looks like:

bool FooAction(int target, std::vector<std::any> v) {
    if (v.size() != 1) { // check size
        return false;
    }
    if (auto* s = std::any_cast<std::string>(&v[0])) { // check type
        std::cout << target << ": " << *s << std::endl;
        return true;
    }
    return false;
}

Note: Care with type and type conversion: const char* is not std::string

assert(FooAction(42, "const char*") == false);
assert(FooAction(42, "std::string"s) == true);

Call look like:

int target = 42;
std::vector<std::any> args = { "Hello"s };
const auto it = RegisteredActions.find(Name);
if (it == RegisteredActions.end())
{
    // ERROR: No action of name has been registered
    throw std::runtime_error("Action not found");
}
auto res = it->second(target, args);

Demo

Sign up to request clarification or add additional context in comments.

Comments

0

Even tho the design itself is complex and could be simplified, Here is my try.

Instead of using va_list, the system is now full of variadic templates. The whole call stack had to be changed.

The Call Function now looks like

template <typename... Args>
std::vector<int> Call(const std::string& Name, const std::vector<int>& Targets, Args... args) {
    std::vector<int> FailedTargets;

    auto it = RegisteredActions<Args...>.find(Name);
    if (it == RegisteredActions<Args...>.end()) {
        std::cout << "ERROR: No action registered with name: " << Name << "\n";
        return FailedTargets;
    }

    for (int Target : Targets) {
        if (!(it->second->Call(Target, args...))) {
            FailedTargets.push_back(Target);
        }
    }
    return FailedTargets;
}

Now you have to specify the expected Action Parameters to be fired correctly for each target. A mismatch between the arguments and the action parameters will not lead the code to crash. It will be dealt as if the action was not registered

    RegisterAction<FooObject, const char*, const char*>("FooAction", &FooObject::DoAction);
    RegisterAction<FooObject, const char*, int, const char*>("BarAction", &FooObject::DoAction);
    Call("FooAction", {0, 1}, "hey there", "im moe");
    Call("BarAction", {0}, "im ", 9, "yo");

Live code on compiler explorer

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.