6

I'm trying to generate an argument list for a function call during runtime, but I can't think of a way to accomplish this in c++.

This is for a helper library I'm writing. I'm taking input data from the client over a network and using that data to make a call to a function pointer that the user has set previously. The function takes a string(of tokens, akin to printf), and a varying amount of arguments. What I need is a way to add more arguments depending on what data has been received from the client.

I'm storing the functions in a map of function pointers

typedef void (*varying_args_fp)(string,...);
map<string,varying_args_fp> func_map;

An example usage would be

void printall(string tokens, ...)
{
    va_list a_list;
    va_start(a_list, tokens);

    for each(auto x in tokens)
    {
        if (x == 'i')
        {
            cout << "Int: " << va_arg(a_list, int) << ' ';
        }
        else if(x == 'c')
        {
            cout << "Char: " << va_arg(a_list, char) << ' ';
        }
    }

    va_end(a_list);
}

func_map["printall"] = printall;
func_map["printall"]("iic",5,10,'x');
// prints "Int: 5 Int: 10 Char: x"

This works nicely when hardcoding the function call and it's arguments, but if I've received the data "CreateX 10 20", the program needs to be able to make the argument call itself. eg

// func_name = "CreateX", tokens = 'ii', first_arg = 10, second_arg = 20
func_map[func_name](tokens,first_arg,second_arg);

I can't predict how users are going to lay out the functions and code this beforehand.

If anyone has suggestions on accomplishing this task another way, feel free to suggest. I need the user to be able to "bind" a function to the library, and for the library to call it later after it has received data from a networked client, a callback in essence.

Trent
  • 571
  • 1
  • 7
  • 15
  • 1
    Run-time varying number of arguments? Not possible in C++ AFAIK, or it would be very bad anyway. The (syntactical) problem is not to receive them, but rather to pass them. It should be possible in assembler, though. In C++, you'd rather use a data structure to store the arguments and pass this structure, like a `std::list`. I'd suggest take a look at boost.spirit – dyp Apr 02 '13 at 12:36
  • This way of passing variable number of arguments is deprecated in C++ – Bartek Banachewicz Apr 02 '13 at 12:44
  • That was my initial thought, but if a function takes 2 different types of arguments, then I can't store them together. – Trent Apr 02 '13 at 12:45
  • 1
    You can use `boost::variant` or `boost::any` if you want different types of arguments – Tony The Lion Apr 02 '13 at 12:47
  • 2
    Perhaps you're trying to fulfill [Greenspun's 10th rule](http://en.wikipedia.org/wiki/Greenspun's_tenth_rule) – Peter Wood Apr 02 '13 at 12:57
  • You need another function that extracts the function name and passes the rest of the arguments as va_list to `printall`. Take a look at these to see how to forward your va_list. http://stackoverflow.com/questions/205529/c-c-passing-variable-number-of-arguments-around http://stackoverflow.com/questions/3530771/passing-variable-arguments-to-another-function-that-accepts-a-variable-argument –  Apr 02 '13 at 12:57
  • @PeterWood lisp style programming was my inspiration, be it for better or worse, for this design decision. – Trent Apr 02 '13 at 13:06
  • @BartekBanachewicz Where do you find that? I can't find anything in the C++ standard which says that it is deprecated. (It probably should be, and it can't be used with just any type, but it is still fully supported.) – James Kanze Apr 02 '13 at 13:07
  • @JamesKanze ok, you are right, "not recommended" would be a better fit. – Bartek Banachewicz Apr 02 '13 at 14:06

2 Answers2

6

Here is a C++11 solution. It does not support varargs functions like printall or printf, this is impossible with this technique and IMO impossible at all, or at the very least extremely tricky. Such function are difficult to use safely in an environment like yours anyway, since any bad request from any client could crash the server, with absolutely no recourse whatsoever. You probably should move to container-based interface for better safety and stability.

On the other hand, this method supports all (?) other functions uniformly.

#include <vector>
#include <iostream>
#include <functional>
#include <stdexcept>
#include <string>
#include <boost/any.hpp>


template <typename Ret, typename... Args>
Ret callfunc (std::function<Ret(Args...)> func, std::vector<boost::any> anyargs);

template <typename Ret>
Ret callfunc (std::function<Ret()> func, std::vector<boost::any> anyargs)
{
    if (anyargs.size() > 0)
        throw std::runtime_error("oops, argument list too long");
    return func();
}

template <typename Ret, typename Arg0, typename... Args>
Ret callfunc (std::function<Ret(Arg0, Args...)> func, std::vector<boost::any> anyargs)
{
    if (anyargs.size() == 0)
        throw std::runtime_error("oops, argument list too short");
    Arg0 arg0 = boost::any_cast<Arg0>(anyargs[0]);
    anyargs.erase(anyargs.begin());
    std::function<Ret(Args... args)> lambda =
        ([=](Args... args) -> Ret {
         return func(arg0, args...);
    });
    return callfunc (lambda, anyargs);
}

template <typename Ret, typename... Args>
std::function<boost::any(std::vector<boost::any>)> adaptfunc (Ret (*func)(Args...)) {
    std::function<Ret(Args...)> stdfunc = func;
    std::function<boost::any(std::vector<boost::any>)> result =
        ([=](std::vector<boost::any> anyargs) -> boost::any {
         return boost::any(callfunc(stdfunc, anyargs));
         });
    return result;
}

Basically you call adaptfunc(your_function), where your_function is a function of any type (except varargs). In return you get an std::function object that accepts a vector of boost::any and returns a boost::any. You put this object in your func_map, or do whatever else you want with them.

Types of the arguments and their number are checked at the time of actual call.

Functions returning void are not supported out of the box, because boost::any<void> is not supported. This can be dealt with easily by wrapping the return type in a simple template and specializing for void. I've left it out for clarity.

Here's a test driver:

int func1 (int a)
{
    std::cout << "func1(" << a << ") = ";
    return 33;
}

int func2 (double a, std::string b)
{
    std::cout << "func2(" << a << ",\"" << b << "\") = ";
    return 7;
}

int func3 (std::string a, double b)
{
    std::cout << "func3(" << a << ",\"" << b << "\") = ";
    return 7;
}

int func4 (int a, int b)
{
    std::cout << "func4(" << a << "," << b << ") = ";
    return a+b;
}


int main ()
{
    std::vector<std::function<boost::any(std::vector<boost::any>)>> fcs = {
        adaptfunc(func1), adaptfunc(func2), adaptfunc(func3), adaptfunc(func4) };

    std::vector<std::vector<boost::any>> args =
    {{777}, {66.6, std::string("yeah right")}, {std::string("whatever"), 0.123}, {3, 2}};

    // correct calls will succeed
    for (int i = 0; i < fcs.size(); ++i)
        std::cout << boost::any_cast<int>(fcs[i](args[i])) << std::endl;

    // incorrect calls will throw
    for (int i = 0; i < fcs.size(); ++i)
        try {
            std::cout << boost::any_cast<int>(fcs[i](args[fcs.size()-1-i])) << std::endl;
        } catch (std::exception& e) {
            std::cout << "Could not call, got exception: " << e.what() << std::endl;
        }
}
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

As already mentioned by @TonyTheLion, you can use boost::variant or boost::any to select between types at runtime:

typedef std::function<void(const std::string&, const std::vector<boost::variant<char, int>>&)> varying_args_fn;
std::map<std::string, varying_args_fn> func_map;

The you can e.g. use a static visitor to distinguish between the types. Here is a full example, note that the tokens parameter is actually no longer necessary since boost::variant knows at runtime what type is stored in it:

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

#include <boost/variant.hpp>
#include <boost/any.hpp>

typedef std::function<void(const std::string&, const std::vector<boost::variant<char, int>>&)> varying_args_fn;

void printall(const std::string& tokens, const std::vector<boost::variant<char, int>>& args) {
  for (const auto& x : args) {
    struct : boost::static_visitor<> {
      void operator()(int i) {
        std::cout << "Int: " << i << ' ';
      }
      void operator()(char c) {
        std::cout << "Char: " << c << ' ';
      }
    } visitor;
    boost::apply_visitor(visitor, x);
  }
}

int main() {
  std::map<std::string, varying_args_fn> func_map;
  func_map["printall"] = printall;
  func_map["printall"]("iic", {5, 10, 'x'});
}
Philipp
  • 48,066
  • 12
  • 84
  • 109
  • Any reason to use a `vector` instead of a `list` here? – dyp Apr 02 '13 at 14:40
  • 1
    @DyP No specific reason, `list` would work as well. However, `vector` is sometimes considered the "default" sequential container (although others say that should rather be `deque`), and often leads to better performance due to its sequential memory layout. – Philipp Apr 02 '13 at 14:42