web-dev-qa-db-ja.com

異なる署名を持つstd :: functionのベクトル

署名の異なるコールバック関数がいくつかあります。理想的には、これらをベクトルに入れて、特定の条件に応じて適切なものを呼び出したいと思います。

例えば.

void func1(const std::string& value);

void func2(const std::string& value, int min, int max);

const std::vector<std::function<void(std::string)>> functions
{
    func1,
    func2,
};

上記は不可能だと思いますが、他に検討すべき点はないかと思います。私はまだ何も見つけることができず、std::bindで実験しましたが、私が望むものを達成することができませんでした。

そのようなことは可能ですか?

14
ksl

間違ったタイプのベクトルに入れた後、_func2_で何ができると期待するかを言っていません。

事前に引数がわかっている場合は、_std::bind_を使用して簡単にベクトルに入れることができます。

_const std::vector<std::function<void(std::string)>> functions
{
    func1,
    std::bind(func2, std::placeholders::_1, 5, 6)
};
_

これで、functions[1]("foo")func2("foo", 5, 6)を呼び出し、毎回_5_と_6_を_func2_に渡します。

_std::bind_の代わりにラムダを使用した場合も同じです

_const std::vector<std::function<void(std::string)>> functions
{
    func1,
    [=](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};
_

引数がまだわからない場合は、いくつかの変数への参照をバインドできます。

_int func2_arg1 = 5;
int func2_arg2 = 6;
const std::vector<std::function<void(std::string)>> functions
{
    func1,
    std::bind(func2, std::placeholders::_1, std::ref(func2_arg1), std::ref(func2_arg2))
};
_

これで、functions[1]("foo")func2("foo", func2_arg1, func2_arg2)を呼び出し、整数に新しい値を割り当てて、_func2_にさまざまな引数を渡すことができます。

そして、_std::bind_の代わりにラムダ関数を使用します

_const std::vector<std::function<void(std::string)>> functions
{
    func1,
    [&](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};
_

ただし、これを参照する呼び出し可能オブジェクト(クロージャまたはバインド式)が存在する限り、int変数を保持する必要があるため、これはかなり醜いです。

21
Jonathan Wakely

あなたが望むことはpolymorphismを通して可能です。アイデアは、実行時にさまざまなメソッドを呼び出す特定の署名を持つクラスを作成することです。例えば:

#include <iostream>
#include <functional>
#include <memory>
#include <vector>

void foo(int) {
    std::cout << "I'm foo!\n";
}

int bar(char, double) {
    std::cout << "I'm bar!\n";
}

class MyFunction {
    public:
        virtual ~MyFunction(){}

        virtual void operator()() = 0;
};

class MyFunctionA : public MyFunction {
    public:
        virtual void operator()() {
            foo(4);
        }
};

class MyFunctionB : public MyFunction {
    public:
        MyFunctionB(std::function<int(char,double)> f, char arg1, double arg2) : fun_(f), arg1_(arg1), arg2_(arg2) {} 

        virtual void operator()() {
            fun_(arg1_, arg2_);
        }
    private:
        std::function<int(char,double)> fun_;
        char arg1_;
        double arg2_;
};

int main() {
    using MyFunPtr = std::unique_ptr<MyFunction>;
    std::vector<MyFunPtr> v;

    v.emplace_back(new MyFunctionA());
    v.emplace_back(new MyFunctionB(bar, 'c', 3.4));

    for ( auto&& myfun : v ) {
        (*myfun)();
    }
    return 0;
}

派生クラスは必要に応じて複雑にすることができますが、最終的にはすべて同じインターフェイスを持つため、すべてを呼び出すことができます。

6
Svalorzen

あなたの質問に対する直接の答えは「いいえ」です。ランタイムコンテナでは、同じタイプのオブジェクトのみを格納でき、異なる署名でインスタンス化されたstd :: function <>は異なるデータ型になります。

一般に、「異なるシグネチャを持つ関数のベクトル」が必要になる理由は、次のようなものがある場合です(入力インターフェイスが統合される3ステップの処理(_buffer& buf_および出力インターフェイスが統合されるon_event(Event evt))、しかし、真ん中の層は不均一ですprocess_...(...)

_receive_message(buffer& buf)
  switch(msg_type(buf))
    case A: 
    case B:
    ...

process_A(A& a, One x, Two y)
  ...
  dispatch(Event evt);
  ...

process_B(B& b, Three x);
  ...
  dispatch(Event evt);
  ...
_

メタプログラミングを含まないソリューションでは、通常、初期化時にエンドツーエンドでファンクターを事前に調理し、それらをベクターに格納します。

_vector <std::function<void(buffer& buf)>> handlers;
_
1
bobah

std::functionは、関数オブジェクトの正確なタイプを消去しますが、関数呼び出しのシグネチャは保持します。 Jonathan Wakelyが推奨するように、事前に追加の引数をbindできない場合は、ベクターメンバーとしてboost::variant< std::function<...>, std::function<...> >を使用できます。呼び出しサイトで、ベクトルに適切な種類の関数オブジェクトが含まれているかどうかを確認し、それに応じて呼び出すことができます。

0
pmr

Intと文字列がある場合、それらを1つのベクトルに入れることはできませんが、1つの構造体またはstd::Tuple<>に入れることはできます。同じことが2つの関数タイプにも当てはまります。

0
MSalters

JBLが述べたように、彼らの署名がわからない場合、どのように呼びますか?

_min, max_引数をいくつかの基本クラスParameterを持つパラメーター型に変換することを検討してください。nullptrに追加のパラメーターがないことを示す場合は、コールバック署名はvoid(const std::string&, const Parameter&)またはvoid(const std::string&, const Parameter*)になります。 。次に、コールバックに適切なパラメータが指定されているかどうかを確認する必要があります。これは、visitor、typeid、またはenumを使用して実行できます。それらすべてに賛否両論があります。

どのコールバックを呼び出すかをどのように決定しますか? Cスタイルのコールバックをハンドラーオブジェクトに変換する必要があると思います。ハンドラーが提示されたパラメーターに適用できるかどうかをテストする関数bool canHandle(const Parameter&)を実装する場合があります。

Jonathan WakelyとSvalorzenは、パラメーターと関数が1つの同じオブジェクト(1対1の関係)であるアプローチを示しています。この例では、それらは別々です(複数から複数の関係がある場合):

_#include <cassert>
#include <string>
#include <typeinfo>
#include <vector>

class ParameterBase {
public:
    ParameterBase(const std::string& value) : m_value(value) { }
    virtual ~ParameterBase() { }
    const std::string& GetValue() const { return m_value; }
private:
    std::string m_value;
};

class HandlerBase {
public:
    virtual bool CanHandle(const ParameterBase& params) const = 0;
    virtual void Handle(const ParameterBase& params) = 0;
};

class Handler1 : public HandlerBase {
public:
     class Parameter : public ParameterBase {
     public:
          Parameter(const std::string& value) : ParameterBase(value) { }
          ~Parameter() { }
     };

     bool CanHandle(const ParameterBase& params) const { return typeid(Parameter) == typeid(params); }
     void Handle(const ParameterBase& params) {
          assert(CanHandle(params));
          const Parameter& p = static_cast<const Parameter&>(params);
          // implement callback1
     }
};

void foo(const std::vector<HandlerBase*>& handlers) {
     Handler1::Parameter params("value");
     for(auto handler : handlers)
         if(handler->CanHandle(params)) {
             handler->Handle(params);
             // no break: all handlers may handle params
             // with break: only first handler (if any) handles params
         }
}
_
0
BeyelerStudios

これがあなたにとってどれほど役立つかはわかりません。これはboost::anyに基づいており、冗長なパラメータは無視されます。 try...catchboost::bad_any_castを追加して、引数とパラメーターのタイプが一致しない場合のクラッシュを防ぐことができます。通常のstd::bindの方が良い選択だと思いますが。

[〜#〜]デモ[〜#〜]

#include <boost/any.hpp>
#include <functional>
#include <vector>
#include <cstddef>
#include <memory>
#include <Tuple>
#include <utility>
#include <iostream>
#include <string>

struct IGenericFunction
{
    virtual ~IGenericFunction() = default;

    virtual void call(boost::any a1 = boost::any{}
                    , boost::any a2 = boost::any{}
                    , boost::any a3 = boost::any{}
                    , boost::any a4 = boost::any{}) = 0;
};

template <typename... Args>
class GenericFunction : public IGenericFunction
{
public:
    GenericFunction(std::function<void(Args...)> f) : _f{ f } {}

    virtual void call(boost::any a1 = boost::any{}
                    , boost::any a2 = boost::any{}
                    , boost::any a3 = boost::any{}
                    , boost::any a4 = boost::any{}) override
    {
        call_func(std::make_Tuple(a1, a2, a3, a4)
                , std::make_index_sequence<sizeof...(Args)>{});
    }

private:            
    template <typename Tuple, std::size_t... Indices>
    void call_func(Tuple t, std::index_sequence<Indices...> s)
    {
        _f(boost::any_cast<
                typename std::Tuple_element<Indices, Params>::type
           >(std::get<Indices>(t))...);
    }

    std::function<void(Args...)> _f;

    using Params = std::Tuple<Args...>;
};

template <typename... Args>
std::shared_ptr<IGenericFunction> make_generic_function_ptr(void(*f)(Args...))
{
    return std::make_shared<GenericFunction<Args...>>(f);
}

void func1(const std::string& value)
{
    std::cout << "func1 " << value << std::endl;
}

void func2(const std::string& value, int min, int max)
{
    std::cout << "func2 " << value << " " << min << " " << max << std::endl;
}

int main()
{
    std::vector<std::shared_ptr<IGenericFunction>> functions;

    functions.Push_back(make_generic_function_ptr(&func1));    
    functions.Push_back(make_generic_function_ptr(&func2));

    for (auto f : functions)
    {
        f->call(std::string("abc"), 1, 2);
    }
}
0
Piotr Skotnicki