4

在我目前的设置中,我有一个

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

我用宏在其中注册了我的函数。但是,我有一个问题:参数作为字符串向量传递,我必须在函数内部进行转换。我宁愿在调度程序级别的函数之外进行这种转换。这可能吗?函数签名在编译时是已知的,并且在运行时永远不会改变。

4

5 回答 5

1

如果您可以使用 boost,那么这是我认为您正在尝试做的一个示例(尽管也可以使用std,但我个人坚持使用 boost):

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 >() );

然而,由于这种嵌套很快变得相当不可读,通常最好创建一个帮助器(自由函数,或者更好的是一个惰性函数)来进行转换。

于 2012-06-29T11:06:28.133 回答
1

您可以使用可变参数模板和一些模板/虚拟技术走得很远。使用以下代码,您将能够执行以下操作:

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"));
}

输出:

John
Wayne

全面实施:

这些代码是示例性的。您应该优化它以在参数列表扩展中提供完美的转发更少的冗余。

标头和测试功能

#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;
}

这有助于我们解析字符串并将结果放入元组中:

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

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);
}

这将函数指针和元组展开为函数调用(通过函数指针):

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

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);
}

这个函数把它放在一起

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

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);
}

这是我们的测试:

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

警告:代码在解析时需要更多验证,应该使用std::function<>而不是裸函数指针


根据上面的代码,编写一个函数注册表很简单:

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;
};

甚至可以考虑支持函数重载,使用多映射并根据传递的参数上的内容进行调度决策。

以下是如何使用它:

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';
}
于 2012-06-29T12:45:44.200 回答
0

有趣的问题。这在 C++ 中并不是微不足道的,我在 C++11 中编写了一个独立的实现。在 C++03 中也可以这样做,但代码的可读性(甚至)会更差。

#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;
}

此外,对于性能,最好使用 unordered_map 而不是 map,如果您只有常规 C 函数,可能会避免 std::function 开销。当然,这只有在调度时间与函数运行时间相比很重要的情况下才有意义。

于 2012-06-29T15:20:14.527 回答
0

如果我对您的理解正确,您想注册void MyClass::Foo(int),并且void MyClass::Bar(float)接受将有一个演员 from std::stringtointfloat视情况而定。

为此,您需要一个辅助类:

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

这使得可以将void MyClass::Foo(int)和都包装void MyClass::Bar(float)在一个std::function<void(MyClass, Argument))>.

于 2012-06-29T11:37:34.020 回答
-2

不,C++ 没有为这种情况提供任何便利。

于 2012-06-29T09:19:51.147 回答