4

In my current setup, I have a

typedef std::function<void (MyClass&, std::vector<std::string>) MyFunction;
std::map<std::string, MyFunction> dispatch_map;

And I register my functions in it with a macro. However, I have a problem with this: the parameters are passed as a vector of strings, which I have to convert inside the functions. I would rather do this conversion outside the functions, at the dispatcher level. Is this possible? The function signatures are known at compile time, and never change at run time.

Tamás Szelei
  • 23,169
  • 18
  • 105
  • 180
  • Could you show the interfaces to give us a better idea? – Ylisar Jun 29 '12 at 09:32
  • NVM, think I understand your problem, it's possible to do what you want if you skip / change the macro. I'll try to make an more elaborate response. – Ylisar Jun 29 '12 at 09:34
  • BTW, could I see some of the transformations you want to do? It's possible to do it with `boost::phoenix`, but the expressions becomes pretty cumbersome. It's possible to use a helper if the transformations are similar to make it easier. – Ylisar Jun 29 '12 at 09:50
  • In almost all cases the conversions are like `boost::lexical_cast(str_value)` or to some other basic type. – Tamás Szelei Jun 29 '12 at 10:41
  • I wonder if any of the answers where useful to you. – Sebastian Mach Jan 16 '13 at 07:53
  • @phresnel Well, I initially tried to implement phresnel's suggestion, which I'm sure would have worked nicely, but I decided midway that it isn't worth the maintenance trouble later. If I had a large number of functions, I would have probably gone with that solution, but for the existing ~15 calls it wasn't such a big deal to write the casts by hand. – Tamás Szelei Jan 16 '13 at 18:40
  • @fish: I feel honored, though I would end up not using my own solution, too (for the same reasoning). It would still be nice to accept the answer that best fitted your question, even though you cancelled your plans :) – Sebastian Mach Jan 16 '13 at 18:45
  • I just did (and sorry for not realizing I was talking to you :)) – Tamás Szelei Jan 16 '13 at 18:46

5 Answers5

1

If you can use boost, then here's an example of what I think you're trying to do ( although might work with std as well, I stick with boost personally ):

typedef boost::function<void ( MyClass&, const std::vector<std::string>& ) MyFunction;
std::map<std::string, MyFunction> dispatch_map;
namespace phx = boost::phoenix;
namespace an = boost::phoenix::arg_names;
dispatch_map.insert( std::make_pair( "someKey", phx::bind( &MyClass::CallBack, an::_1, phx::bind( &boost::lexical_cast< int, std::string >, phx::at( an::_2, 0 ) ) ) ) );
dispatch_map["someKey"]( someClass, std::vector< std::string >() );

However, as this sort of nesting quickly becomes fairly unreadable, it's usually best to either create a helper ( free function, or better yet a lazy function ) that does the conversion.

Ylisar
  • 4,293
  • 21
  • 27
  • If I understand correctly, the output of phx::bind will be a functor that has the same signature as my callback, but wraps the string conversion in it. If that is so, I can't store it in a map of MyFunctions, since that's a different type. – Tamás Szelei Jun 29 '12 at 11:13
  • They're very relaxed, the result of `phx::bind` is convertible to pretty much any `boost::function<>` that would be able to invoke it. I'll add some clarification to better match your signature. – Ylisar Jun 29 '12 at 11:36
1

You can get pretty far with variadic templates and some template/virtual techniques. With the following codes, you'll be able to do something like:

std::string select_string (bool cond, std::string a, std::string b) {
    return cond ? a : b;
}

int main () {
    Registry reg;
    reg.set ("select_it", select_string);
    reg.invoke ("select_it", "1 John Wayne"));
    reg.invoke ("select_it", "0 John Wayne"));
}

output:

John
Wayne

Full implementation:

These codes are exemplary. You should optimize it to provide perfect forwarding less redundancy in parameter list expansion.

Headers and a test-function

#include <functional>
#include <string>
#include <sstream>
#include <istream>
#include <iostream>
#include <tuple>

std::string select_string (bool cond, std::string a, std::string b) {
    return cond ? a : b;
}

This helps us parsing a string and putting results into a tuple:

//----------------------------------------------------------------------------------

template <typename Tuple, int Curr, int Max> struct init_args_helper;

template <typename Tuple, int Max>
struct init_args_helper<Tuple, Max, Max> {
    void operator() (Tuple &, std::istream &) {}
};

template <typename Tuple, int Curr, int Max>
struct init_args_helper {
    void operator() (Tuple &tup, std::istream &is) {
        is >> std::get<Curr>(tup);
        return init_args_helper<Tuple, Curr+1, Max>() (tup, is);
    }
};


template <int Max, typename Tuple>
void init_args (Tuple &tup, std::istream &ss)
{
    init_args_helper<Tuple, 0, Max>() (tup, ss);
}

This unfolds a function pointer and a tuple into a function call (by function-pointer):

//----------------------------------------------------------------------------------

template <int ParamIndex, int Max, typename Ret, typename ...Args>
struct unfold_helper;

template <int Max, typename Ret, typename ...Args>
struct unfold_helper<Max, Max, Ret, Args...> {
    template <typename Tuple, typename ...Params>
    Ret unfold (Ret (*fun) (Args...), Tuple tup, Params ...params)
    {
        return fun (params...);
    }
};

template <int ParamIndex, int Max, typename Ret, typename ...Args>
struct unfold_helper {
    template <typename Tuple, typename ...Params>
    Ret unfold (Ret (*fun) (Args...), Tuple tup, Params ...params)
    {
        return unfold_helper<ParamIndex+1, Max, Ret, Args...> ().
               unfold(fun, tup, params..., std::get<ParamIndex>(tup));
    }
};



template <typename Ret, typename ...Args>
Ret unfold (Ret (*fun) (Args...), std::tuple<Args...> tup) {
    return unfold_helper<0, sizeof...(Args), Ret, Args...> ().unfold(fun, tup);
}

This function puts it together:

//----------------------------------------------------------------------------------

template <typename Ret, typename ...Args>
Ret foo (Ret (*fun) (Args...), std::string mayhem) {

    // Use a stringstream for trivial parsing.
    std::istringstream ss;
    ss.str (mayhem);

    // Use a tuple to store our parameters somewhere.
    // We could later get some more performance by combining the parsing
    // and the calling.
    std::tuple<Args...> params;
    init_args<sizeof...(Args)> (params, ss);

    // This demondstrates expanding the tuple to full parameter lists.
    return unfold<Ret> (fun, params);
}

Here's our test:

int main () {
    std::cout << foo (select_string, "0 John Wayne") << '\n';
    std::cout << foo (select_string, "1 John Wayne") << '\n';
}

Warning: Code needs more verification upon parsing and should use std::function<> instead of naked function pointer


Based on above code, it is simple to write a function-registry:

class FunMeta {
public:
    virtual ~FunMeta () {}
    virtual boost::any call (std::string args) const = 0;
};

template <typename Ret, typename ...Args>
class ConcreteFunMeta : public FunMeta {
public:
    ConcreteFunMeta (Ret (*fun) (Args...)) : fun(fun) {}

    boost::any call (std::string args) const {
        // Use a stringstream for trivial parsing.
        std::istringstream ss;
        ss.str (args);

        // Use a tuple to store our parameters somewhere.
        // We could later get some more performance by combining the parsing
        // and the calling.
        std::tuple<Args...> params;
        init_args<sizeof...(Args)> (params, ss);

        // This demondstrates expanding the tuple to full parameter lists.
        return unfold<Ret> (fun, params);
    }

private:
    Ret (*fun) (Args...);
};

class Registry {
public:
    template <typename Ret, typename ...Args>
    void set (std::string name, Ret (*fun) (Args...)) {
        funs[name].reset (new ConcreteFunMeta<Ret, Args...> (fun));
    }

    boost::any invoke (std::string name, std::string args) const {
        const auto it = funs.find (name);
        if (it == funs.end())
            throw std::runtime_error ("meh");
        return it->second->call (args);
    }

private:
    // You could use a multimap to support function overloading.
    std::map<std::string, std::shared_ptr<FunMeta>> funs;
};

One could even think of supporting function overloading with this, using a multimap and dispatching decisions based on what content is on the passed arguments.

Here's how to use it:

int main () {
    Registry reg;
    reg.set ("select_it", select_string);
    std::cout << boost::any_cast<std::string> (reg.invoke ("select_it", "0 John Wayne")) << '\n'
              << boost::any_cast<std::string> (reg.invoke ("select_it", "1 John Wayne")) << '\n';
}
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
0

If I understand you correctly, you want to register void MyClass::Foo(int) and void MyClass::Bar(float), accepting that there will be a cast from std::string to int or float as appropriate.

To do this, you need a helper class:

class Argument {
  std::string s;
  Argument(std::string const& s) : s(s) { }
  template<typename T> operator T { return boost::lexical_cast<T>(s); }
};

This makes it possible to wrap both void MyClass::Foo(int) and void MyClass::Bar(float) in a std::function<void(MyClass, Argument))>.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • using boost::any or boost::variant would be better i think, i cant remember which one is the right one for this job right now though – smerlin Jun 29 '12 at 11:52
  • @smerlin: Both types can _hold_ any argument, but neither has an `operator T`. – MSalters Jun 29 '12 at 11:54
  • Yes, but using them instead of std::string inside your Argument class will provide means of error (=type missmatch) detection, which boost::lexical_cast does not offer. – smerlin Jun 29 '12 at 12:05
  • @smerlin: Since you start with a `std::vector`, you can't have compile-time type checking anyway. Only at run time can you detect that `std::string("5.3")` isn't a proper argument for `void MyClass::Foo(int)` – MSalters Jun 29 '12 at 12:54
0

Interesting problme. This is indeen not trivial in C++, I wrote a self-contained implementation in C++11. It is possible to do the same in C++03 but the code would be (even) less readable.

#include <iostream>
#include <sstream>
#include <string>
#include <functional>
#include <vector>
#include <cassert>
#include <map>
using namespace std;

// string to target type conversion. Can replace with boost::lexical_cast.
template<class T> T fromString(const string& str)
{ stringstream s(str); T r; s >> r; return r; }

// recursive construction of function call with converted arguments
template<class... Types> struct Rec;
template<> struct Rec<> { // no parameters
    template<class F> static void call
    (const F& f, const vector<string>&, int) { f(); }
};
template<class Type> struct Rec< Type > { // one parameter
    template<class F> static void call
    (const F& f, const vector<string>& arg, int index) {
        f(fromString<Type>(arg[index]));
    }
};
template<class FirstType, class... NextTypes>
struct Rec< FirstType, NextTypes... > { // many parameters
    template<class F> static void call
    (const F& f, const vector<string>& arg, int index) {
        Rec<NextTypes...>::call(
            bind1st(f, fromString<FirstType>(arg[index])), // convert 1st param
            arg,
            index + 1
        );
    }
};

template<class... Types> void call // std::function call with strings
(const function<void(Types...)>& f, const vector<string>& args) {
    assert(args.size() == sizeof...(Types));
    Rec<Types...>::call(f, args, 0);
}
template<class... Types> void call // c function call with strings
(void (*f)(Types...), const vector<string>& args) {
    call(function<void(Types...)>(f), args);
}

// transformas arbitrary function to take strings parameters
template<class F> function<void(const vector<string>&)> wrap(const F& f) { 
    return [&] (const vector<string>& args) -> void { call(f, args); };
}

// the dynamic dispatch table and registration routines
map<string, function<void(const vector<string>&)> > table;
template<class F> void registerFunc(const string& name, const F& f) {
    table.insert(make_pair(name, wrap(f)));
}
#define smartRegister(F) registerFunc(#F, F)

// some dummy functions
void f(int x, float y) { cout << "f: " << x << ", " << y << endl; }
void g(float x) { cout << "g: " << x << endl; }

// demo to show it all works;)
int main() {
    smartRegister(f);
    smartRegister(g);
    table["f"]({"1", "2.0"});
    return 0;
}

Also, for performances, it's better to use unordered_map instead of map, and maybe avoid std::function overhead if you only have regular C functions. Of course this is only meaningful if dispatch time is significant compared to functions run-times.

Antoine
  • 13,494
  • 6
  • 40
  • 52
-2

No, C++ provides no facility for this to occur.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 2
    Can you elaborate why? I know this is the reflex that is triggered when someone mentions dynamism and C++ in the same sentence, but I'm not looking for reflection or anything like that. I'm OK with some boilerplate, I need to write code that interoperates with something. – Tamás Szelei Jun 29 '12 at 09:30
  • 2
    Of course it does. While the invocation is dynamic, the function themselves - and therefore the argument conversions - are statically determined. – MSalters Jun 29 '12 at 11:30