C++ std::function variable with varying arguments

后端 未结 6 1432
天涯浪人
天涯浪人 2020-12-28 09:12

In my callback system I want to store std::function (or something else) with varying arguments.

Example:

  1. I want to call void()
相关标签:
6条回答
  • 2020-12-28 09:55

    The following solution might work for you (I'm not sure that the code is absolutely correct here):

    Create a wrapper for std::function with virtual destructor to enable using dynamic cast

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

    Then create a class 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)...);
        }
    };
    
    0 讨论(0)
  • 2020-12-28 10:01

    C++11 to the rescue!

    If you can generalize your function to a functor object taking no arguments, then you can call it with any 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;
    }
    
    0 讨论(0)
  • 2020-12-28 10:06

    The other answers are fine but I want to show my solution as well.

    It's a small header with which you can "elongate" function signatures. This allows you to do this (extract from the 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;
       }
    }
    
    0 讨论(0)
  • 2020-12-28 10:07

    If std::function is not necessary for you, you can create a proxy class.

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

    Class fn_t automatically works with two prototypes you want, assigns automatically needed one, and it can call functions with both prototypes by overlading () operator with appropriate parameters.

    You may want to check for validity of function pointers fn_1 and fn_2 but I didn't include this checking for minimality.

    The advantage of this is that you only need C++ and not even STL and Boost.

    0 讨论(0)
  • 2020-12-28 10:09

    std::function<void()> and std::function<void(int, int)> are two absolutely distinct types. You need some sort of union functionality (or polymorphism) to store an object of an unknown type.

    If you can use Boost, you could easily do this with 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);
    }
    

    It is up to you to provide the logic of fContainsNullary(). Alternatively, you can use the variant's own stored knowledge of value type by using a visitor:

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

    If Boost is not an option, you can hand-craft a suitable union for much the same purpose.

    0 讨论(0)
  • 2020-12-28 10:11

    Well, if you can use RTTI, you can define a MultiFuncObject like this, and you can easily bind other functions. Also, you can easily call them. But unfortunately, this approach only works for a limited number of arguments. But actually boost::bind also supports limited number of arguments (by default 9). So you can extend this class to satisfy your needs.

    Before giving you the source of MultiFuncObject, I want to show you how you can use it. It takes an template argument to be used as return type. You can bind new functions with += operator. With some template magic, the class distinguishes differences between bound functions with same count of arguments with at least one different argument type.

    You need C++11, because MultiFuncObject uses std::unordered_map and std::type_index.

    Here is usage:

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

    I hope this is close to what you want. Here is the source of 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);
        }
      }
    
    };
    

    It stores different function prototypes using std::unordered_map with keys of std::type_index and values of void (*)(). When needed, the correct function is retrieved using that map.

    Here is the working example

    0 讨论(0)
提交回复
热议问题