4

私の現在の設定では、

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

そして、マクロを使って関数を登録します。ただし、これには問題があります。パラメーターは文字列のベクトルとして渡され、関数内で変換する必要があります。ディスパッチャレベルで、関数の外部でこの変換を実行したいと思います。これは可能ですか?関数のシグネチャはコンパイル時に認識され、実行時に変更されることはありません。

4

5 に答える 5

1

ブーストを使用できる場合は、これがあなたがやろうとしていると思うことの例です(std同様に機能するかもしれませんが、私は個人的にブーストに固執します):

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

私があなたを正しく理解しているなら、あなたは登録void MyClass::Foo(int)して、からまたは適切なvoid MyClass::Bar(float)キャストがあることを受け入れたいと思います。std::stringintfloat

これを行うには、ヘルパークラスが必要です。

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 に答える
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;
}

また、パフォーマンスについては、mapの代わりにunordered_mapを使用することをお勧めします。また、通常のC関数しかない場合は、std::functionのオーバーヘッドを回避することをお勧めします。もちろん、これは、ディスパッチ時間が関数の実行時間と比較して重要である場合にのみ意味があります。

于 2012-06-29T15:20:14.527 に答える
-2

いいえ、C++にはこれを実行するための機能はありません。

于 2012-06-29T09:19:51.147 に答える