【问题标题】:C++ std::function variable with varying arguments具有不同参数的 C++ std::function 变量
【发布时间】:2015-12-25 16:45:42
【问题描述】:

在我的回调系统中,我想用不同的参数存储 std::function(或其他东西)。

例子:

  1. 我想打电话给void()
  2. 我想打电话给void(int, int)

我希望 1) 和 2) 存储在同一个变量中,并选择在实际调用中调用什么

FunctionPointer f0;
FunctionPointer f2;

f0();
f2(4, 5);

有可能做这样的事情吗?或者我必须根据输入参数计数创建几个“FuntionPointer”模板。

编辑

是否有可能以某种方式利用 std::bind 来完成这项任务?使用 std::bind 我可以拥有std::function<void()> f = std::bind(test, 2, 5);

编辑 2

实际用例:我有一个触发系统,我想将函数指针分配给动作,所以当动作发生时,函数被调用。 伪代码示例:

structure Trigger 
{
   Function f;
}

Init:
  Trigger0.f = pointer to some function ()
  Trigger1.f = pointer to some function (a, b)

Input: 
  Find Trigger by input
  if (in == A) Trigger.f();
  else Trigger.f(10, 20)

如果可能的话

Input:
  Find Trigger by input
  if (in == A) f = bind(Trigger.f);
  else f = bind(Trigger.f, 10, 20);
  f()

【问题讨论】:

  • @ShafikYaghmour 这只是将问题转移到模板,变量类型不能相同并且同时拥有两个函数。例如,我无法制作不同功能的 std::vector
  • 能否提供一个实际的实际用例?如果没有实际的用例,答案只是把语法扔到墙上。 “类似的东西”是非常模糊的,这与你得到的一样具体。然后您继续讨论bind,这与“类似”示例完全不同。同样,解决所有这些噪音的实际用例/问题。这意味着您在编写软件时遇到的实际具体问题。我可以想出 10 个答案来回答你的问题,但哪一个对你有帮助取决于你真正想要做什么
  • 请看我最新的回答。如果它不能满足您的需求,您能解释一下原因吗(在我的最后一个答案下评论)?

标签: c++ c++11


【解决方案1】:

std::function<void()>std::function<void(int, int)> 是两种完全不同的类型。您需要某种联合功能(或多态性)来存储未知类型的对象。

如果您可以使用 Boost,您可以使用 boost::variant 轻松做到这一点:

// Declaration:
boost::variant<std::function<void()>, std::function<void(int, int)> > f;

// Calling, explicit:
if (fContainsNullary()) {
  boost::get<std::function<void()>>(f)();
} else {
  boost::get<std::function<void(int, int)>>(f)(4, 5);
}

由您提供fContainsNullary() 的逻辑。或者,您可以通过访问者使用变体自己存储的值类型知识:

struct MyVisitor : boost::static_visitor<void>
{
  result_type operator() (const std::function<void()> &a) {
    a();
  }

  result_type operator() (const std::function<void(int, int)> &a) {
    a(4, 5);
  }
};

// Calling, via visitor:
boost::apply_visitor(MyVisitor(), f);

如果 Boost 不是一个选项,您可以手工制作一个合适的 union 以实现几乎相同的目的。

【讨论】:

    【解决方案2】:

    以下解决方案可能对您有用(我不确定这里的代码是否绝对正确):

    使用虚拟析构函数为 std::function 创建一个包装器以启用动态转换

    class function_wrapper_base
    {
        virtual ~function_wrapper_base();
    }
    
    template <class... Args>
    class function_wrapper
        : public function_wrapper_base
    {
    public:
       std::function<void, Args...> f;
       ...
    };
    

    然后创建一个类variant_function_holder

    class variant_function_holder
    {
        std::unique_ptr<function_wrapper_base> f;
        ...
        template <class... Args>
        void operator()(Args&&... args)
        {
            function_wrapper<std::decay<Args>::type...> * g = dynamic_cast<function_wrapper<std::decay<Args>::type...>>(f.get());
    
            if (g == nullptr)
            {
                // ToDo
            }
    
            g->f(std::forward<Args>(args)...);
        }
    };
    

    【讨论】:

    • 注意:对于function_wrapper&lt;std::string&gt;,使用 c 字符串文字 (const char*) 的调用将失败(对于 dynamic_cast)...
    【解决方案3】:

    好吧,如果你可以使用RTTI,你可以像这样定义一个MultiFuncObject,你可以轻松绑定其他函数。此外,您可以轻松地调用它们。但不幸的是,这种方法只适用于有限数量的论点。但实际上boost::bind 也支持有限数量的参数(默认为 9)。所以你可以扩展这个类来满足你的需要。

    在给你MultiFuncObject的来源之前,我想告诉你如何使用它。它需要一个模板参数作为返回类型。您可以使用+= 运算符绑定新功能。借助一些模板魔法,该类可以区分具有相同数量参数和至少一种不同参数类型的绑定函数之间的差异。

    您需要 C++11,因为 MultiFuncObject 使用 std::unordered_mapstd::type_index

    这里是用法:

    #include <iostream>
    using namespace std;
    
    void _1() {
      cout << "_1" << endl;
    }
    
    void _2(char x) {
      cout << "_2" << " " << x << endl;
    }
    
    void _3(int x) {
      cout << "_3" << " " << x << endl;
    }
    
    void _4(double x) {
      cout << "_4" << " " << x << endl;
    }
    
    void _5(int a, int b) {
      cout << "_5" << " " << a << " " << b << endl;
    }
    
    void _6(char a, int b) {
      cout << "_6" << " " << a << " " << b << endl;
    }
    
    void _7(int a, int b, int c) {
      cout << "_7" << " " << a << " " << b << " " << c << endl;
    }
    
    int main() {
      MultiFuncObject<void> funcs;
      funcs += &_1;
      funcs += &_2;
      funcs += &_3;
      funcs += &_4;
      funcs += &_5;
      funcs += &_6;
      funcs += &_7;
      funcs();
      funcs('a');
      funcs(56);
      funcs(5.5);
      funcs(2, 5);
      funcs('q', 6);
      funcs(1, 2, 3);
      return 0;
    }
    

    我希望这接近你想要的。这里是MultiFuncObject的来源:

    #include <typeinfo>
    #include <typeindex>
    #include <unordered_map>
    
    using namespace std;
    
    template <typename R>
    class MultiFuncObject {
      unordered_map<type_index, void (*)()> m_funcs;
    public:
    
      MultiFuncObject<R> operator +=( R (* f)() ) {
        m_funcs[typeid( R() )] = (void (*)()) f;
        return *this;
      }
    
      template <typename A1>
      MultiFuncObject<R> operator +=( R (* f)(A1) ) {
        m_funcs[typeid( R(A1) )] = (void (*)()) f;
        return *this;
      }
    
      template <typename A1, typename A2>
      MultiFuncObject<R> operator +=( R (* f)(A1, A2) ) {
        m_funcs[typeid( R(A1, A2) )] = (void (*)()) f;
        return *this;
      }
    
      template <typename A1, typename A2, typename A3>
      MultiFuncObject<R> operator +=( R (* f)(A1, A2, A3) ) {
        m_funcs[typeid( R(A1, A2, A3) )] = (void (*)()) f;
        return *this;
      }
    
      R operator()() const
      {
        unordered_map<type_index, void (*)()>::const_iterator it = m_funcs.find(typeid( R() ));
        if (it != m_funcs.end()) {
          R (*f)() = ( R (*)() )(it->second);
          (*f)();
        }
      }
    
      template <typename A1>
      R operator()(A1 a1) const
      {
        unordered_map<type_index, void (*)()>::const_iterator it = m_funcs.find(typeid( R(A1) ));
        if (it != m_funcs.end()) {
          R (*f)(A1) = ( R (*)(A1) )(it->second);
          (*f)(a1);
        }
      }
    
      template <typename A1, typename A2>
      R operator()(A1 a1, A2 a2) const
      {
        unordered_map<type_index, void (*)()>::const_iterator it = m_funcs.find(typeid( R(A1, A2) ));
        if (it != m_funcs.end()) {
          R (*f)(A1, A2) = ( R (*)(A1, A2) )(it->second);
          (*f)(a1, a2);
        }
      }
    
      template <typename A1, typename A2, typename A3>
      R operator()(A1 a1, A2 a2, A3 a3) const
      {
        unordered_map<type_index, void (*)()>::const_iterator it = m_funcs.find(typeid( R(A1, A2, A3) ));
        if (it != m_funcs.end()) {
          R (*f)(A1, A2, A3) = ( R (*)(A1, A2, A3) )(it->second);
          (*f)(a1, a2, a3);
        }
      }
    
    };
    

    它使用std::unordered_map 存储不同的函数原型,键为std::type_index,值为void (*)()。需要时,使用该映射检索正确的函数。

    Here is the working example

    【讨论】:

    • 我已经使用可变参数模板改进了您的解决方案,现在它几乎是我想要的(现在唯一关心的是 RTTI,但是嘿,我可以忍受,因为我没有实时应用程序)
    • @MartinPerry 太好了!我对可变参数模板不熟悉,所以如果您可以发送改进代码的链接;那很好啊! :)
    • 我也做了一些性能测量:perry.cz/clanky/functions.html
    【解决方案4】:

    C++11 来救援!

    如果您可以将您的函数泛化为不带参数的函子对象,那么您可以使用任何 lambda 调用它。

    #include <iostream>
    using namespace std;
    
    template <class F>
    void call_it(F&& f)
    {
        f();
    }
    
    int main()
    {
        int x = 50, y = 75;
    
        call_it([] () { cout << "Hello!\n"; });
        call_it([x,y] () { cout << x << " + " << y << " = " << x + y << '\n';});
    
    
        return 0;
    }
    

    【讨论】:

    • 这与仅使用 std::function&lt;void()&gt; 有何不同?
    • std::function 有一点存储开销,可以通过直接绑定到闭包类型来消除。实际差异可以忽略不计,但根据用例可能很重要。
    【解决方案5】:

    如果您不需要std::function,您可以创建一个代理类。

    class fn_t {
    public:
      typedef void (*fn_1_t)();
      typedef void (*fn_2_t)(int, int);
      fn_1_t fn_1;
      fn_2_t fn_2;
      fn_t operator=(fn_1_t func_1) { fn_1 = func_1; return *this; }
      fn_t operator=(fn_2_t func_2) { fn_2 = func_2; return *this; }
      void operator()() { (*fn_1)(); }
      void operator()(int a, int b) { (*fn_2)(a, b); }
    };
    
    #include <iostream>
    using namespace std;
    
    void first() {
      cout << "first" << endl;
    }
    
    void second(int a, int b) {
      cout << "second " << a << " : " << b << endl;
    }
    
    int main() {
      fn_t f;
      f = &first;
      f = &second;
      f();
      f(5, 4);
      return 0;
    }
    

    fn_t 类自动使用您想要的两个原型,自动分配需要的一个,并且它可以通过使用适当的参数覆盖 () 运算符来使用这两个原型调用函数。

    您可能想要检查函数指针 fn_1fn_2 的有效性,但我没有包括此检查以确保最小化。

    这样做的好处是你只需要 C++,甚至不需要 STL 和 Boost。

    【讨论】:

    • 仅适用于这两个示例。如果他突然需要 3 个参数怎么办?还是不同类型的两个等?那他就得写一大堆烂课了。
    • @pfannkuchen_gesicht 我将这个问题解释为“我有两个定义明确的函数原型,我想将指向具有这些原型的函数的指针存储在一个对象中并轻松调用它们使用一个对象。”
    【解决方案6】:

    其他答案都很好,但我也想展示我的解决方案。

    这是一个small header,您可以使用它“延长”函数签名。 这允许您执行此操作(从github example 中提取):

    int foo_1p(int a);
    int foo_2p(int a, int b);
    int foo_3p(int a, int b, int c);
    int foo_4p(int a, int b, int c, int d);
    int foo_5p(int a, int b, int c, int d, int e);
    int foo_6p(int a, int b, int c, int d, int e, int f);
    int foo_7p(int a, int b, int c, int d, int e, int f, std::string g);
    ...
    
    int main()
    {
       std::unordered_map<std::string, std::function<int(int, int, int, int, int, int, std::string)>> map;
       map["foo_1p"] = ex::bind(foo_1p, ph, ph, ph, ph, ph, ph);
       map["foo_2p"] = ex::bind(foo_2p, ph, ph, ph, ph, ph);
       map["foo_3p"] = ex::bind(foo_3p, ph, ph, ph, ph);
       map["foo_4p"] = ex::bind(foo_4p, ph, ph, ph);
       map["foo_5p"] = ex::bind(foo_5p, ph, ph);
       map["foo_6p"] = ex::bind(foo_6p, ph);
       map["foo_7p"] = foo_7p;
    
       for (const auto& f : map)
       {
           std::cout << f.first << " = " << f.second(1, 1, 1, 1, 1, 1, "101") << std::endl;
       }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-11-27
      • 2012-03-03
      • 2018-08-04
      • 1970-01-01
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多