Binding a std::function to the same function of a

2019-05-06 16:24发布

问题:

Is it possible to rebind a std::function to point to the same function but with a different object instance?

Say if I have an object that has a std::function that is bound to another function, but if that object was copied to another instance, I'd like to rebind the std::function to that new instance instead of the old instance.

#include "stdafx.h"
#include <iostream>
#include <functional>

class EventHandler
{
public:
    int Num;
    std::function<int()> OnEvent;

    EventHandler (int inNum)
    {
        Num = inNum;
    }

    EventHandler (const EventHandler& other)
    {
        Num = other.Num;
        OnEvent = other.OnEvent; //TODO:  Need some way to redirect the std::function to the new instance rather than having the delegate point to the original object's handler.
    }

    int HandleEvent ()
    {
        return Num;
    }
};

int main()
{
    EventHandler a(4);
    a.OnEvent = std::bind(&EventHandler::HandleEvent, a);
    EventHandler b(a);
    b.Num = 5;
    //Uncommenting the line below is a manual way of redirecting event handler to the new instance.
    //b.OnEvent = std::bind(&EventHandler::HandleEvent, b);

    int aResult = a.OnEvent();
    int bResult = b.OnEvent();

    //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
    std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

    return 0;
}

I'm open to having a wrapper of the std::function to store additional information.

回答1:

The following code introduced a binding_function<R(Args...)>, which is called like function<R()>, and arguments can be rebind anytime after it constructed (assuming it was not nullptr).

#include <functional>
#include <tuple>
#include <utility>
#include <memory>
#include <iostream>

template <typename T>
class binding_function;

template <typename R, typename... Args>
class binding_function<R(Args...)> : std::function<R()>
{
  using base_function = std::function<R(Args...)>;
  using binded_function = std::function<R()>;
  base_function base;

public:
  binding_function() = default;

  template <typename BaseF, typename... TArgs>
  binding_function(BaseF&& f, TArgs&&... args)
    : base(std::forward<BaseF>(f)) {
    rebind(std::forward<TArgs>(args)...);
  }

  template <typename... TArgs>
  void rebind(TArgs&&... args)
  {
    static_cast<binded_function&>(*this) = 
      std::bind(base, std::forward<TArgs>(args)...);
  }

  using binded_function::operator();
};

class EventHandler
{
public:
    // change type of OnEvent to binding_function
    binding_function<int(EventHandler)> OnEvent;

    // others remain the same
};

int main()
{
    EventHandler a(4);

                // first binding
    a.OnEvent = {&EventHandler::HandleEvent, a};
    EventHandler b(a);
    b.Num = 5;
    b.OnEvent.rebind(b);  // rebinding

    int aResult = a.OnEvent();
    int bResult = b.OnEvent();

    //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
    std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

    return 0;
}


回答2:

What your event handler does should depend on which instance it is called on. Hence, logically, the correct way of solving the problem is providing the instance as a parameter to the handler function, e.g.

#include <iostream>
#include <functional>

class EventHandler
{
private:
  std::function<int(EventHandler &)> handlingFunction;

public:
  int Num;

  EventHandler (int inNum)
  : handlingFunction ([] (EventHandler &) -> int { throw 0; })
  , Num (inNum)
  { }

  void SetHandlingFunction (std::function<int(EventHandler &)> f) {
     handlingFunction = f;
  }

  // for convenience, if the handling function is a member
  void SetHandlingFunction (int EventHandler::*mf ()) {
      handlingFunction = 
         [mf] (EventHandler & myself) -> int { return myself.*mf (); }
      ;
  }

  int OnEvent () {
     return handlingFunction (*this);
  }

  int HandleEvent ()
  {
     return Num;
  }
};

int main()
{
   EventHandler a(4);
   a.SetHandlingFunction ( [] (EventHandler & h) -> int { return h.HandleEvent (); } );

   // or
   a.SetHandlingFunction (&EventHandler::HandleEvent);

   EventHandler b(a);
   b.Num = 5;

   int aResult = a.OnEvent();
   int bResult = b.OnEvent();

   std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

   return 0;
}

Of course, if your handling function always is a member function, you can simply replace the std::function by a pointer-to-member-function.

Note that you should properly initialize the handlingFunction member in the constructor of your EventHandler class, e.g. by setting it to a dummy function.



回答3:

I extended user1887915's answer to allow functions with parameters:

#include <functional>
#include <tuple>
#include <utility>
#include <memory>
#include <iostream>

template <typename T>
class binding_function;

template <typename R, typename... Args, typename SelfType>
class binding_function<R(SelfType, Args...)> : std::function<R(Args...)>
{
  using base_function = std::function<R(SelfType, Args...)>;
  using binded_function = std::function<R(Args...)>;
  base_function base;

public:
  binding_function() = default;

  template <typename BaseF, typename... TArgs>
  binding_function(BaseF&& f, SelfType t, TArgs&&... args)
    : base(std::forward<BaseF>(f)) {
    rebind(std::forward<SelfType>(t), std::forward<TArgs>(args)...);
  }

  template <typename T, typename... TArgs>
  void rebind(T&& t, TArgs&&... args)
  {
    static_cast<binded_function&>(*this) =
      std::bind(base, std::forward<SelfType>(t), std::forward<TArgs>(args)...);
  }

  using binded_function::operator();
};



class EventHandler
{
public:
    int Num;
    binding_function<int(EventHandler, int)> OnEvent;


    EventHandler (int inNum)
    {
        Num = inNum;
    }

    EventHandler (const EventHandler& other)
    {
        Num = other.Num;
        OnEvent = other.OnEvent; //TODO:  Need some way to redirect the std::function to the new instance rather than having the delegate point to the original object's handler.
    }
    int HandleEvent (int value)
    {
        return Num + value;
    }
};
int main()
{
    EventHandler a(4);

                // first binding
    a.OnEvent = {&EventHandler::HandleEvent, a, std::placeholders::_1};
    EventHandler b(a);
    b.Num = 5;
    b.OnEvent.rebind(b, std::placeholders::_1);  // rebinding

    int aResult = a.OnEvent(1);
    int bResult = b.OnEvent(1);

    //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
    std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

    return 0;
}


回答4:

AFAIK what you are asking is not possible, but I think there is a workaround that you can do:

class EventHandler
{
public:
    int Num;
    std::function<int()> OnEvent;

    template <typename Func>
    EventHandler (int inNum, Func on_event)
    {
        Num = inNum;
        OnEvent = [=]() { return (this->*on_event)(); };
    }

    EventHandler (const EventHandler& other): EventHandler(other.Num, &EventHandler::HandleEvent) {}

    int HandleEvent ()
    {
        return Num;
    }
};

int main()
{
    EventHandler a(4, &EventHandler::HandleEvent);
    EventHandler b(a);
    b.Num = 5;

    int aResult = a.OnEvent();
    int bResult = b.OnEvent();

    //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
    std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

    return 0;
}

This prints "aResult=4 bResult=5" as you want. Also, I think by employing a bit more metaprogramming magic, we can try to prettify the syntax.

Let me know if this works for you.