Search code examples
c++c++11variadic-templatesstdbind

Function to bind member functions to object instances in C++


Recently, I've frequently been binding member functions to instances of objects. Rather than using a combination of std::bind, std::mem_fn and std::ref, I'd like to combine all of these into one function to handle it all automatically.

For example, consider the following code:

#include <functional>
#include <iostream>
#include <string>

// This is the function I'd like to have working:
template <class T, typename RETURN_TYPE, typename... Arguments>
std::function<RETURN_TYPE(Arguments...)> obj_bind(RETURN_TYPE (T::*in_fun)(), T & obj, Arguments... params)
{
  return std::bind( std::mem_fn(in_fun), std::ref(obj), params...);
}

int main()
{
  // Standard use of push_back member function (our test case):
  std::string test_obj = "Test 1: ";
  test_obj.push_back('A');
  std::cout << test_obj << std::endl;

  // This WORKS:
  auto test_fun = std::bind( std::mem_fn(&std::string::push_back), std::ref(test_obj), 'B');

  // But I'd like to use this instead:
  // auto test_fun = obj_bind( &std::string::push_back, test_obj, 'C');

  test_obj = "Test 2: ";
  test_fun();
  std::cout << test_obj << std::endl;
}

My obj_bind function actually works fine on member functions with no arguments, so I'm pretty sure my problem lies in how I'm handling those, but after several failed attempts to fix it, I thought I'd try here for advice.


Solution

  • Before diving into fixing your binder, a few notes:

    1. You don't really need to use std::mem_fn() for the first argument of std::bind() as std::bind() already knows how to deal with member function pointers.
    2. The type of taking an address of a member function is undefined. That is, you can't really on std::string::push_back being a member function pointer taking just one argument.
    3. Instead of using std::ref(testobj) you could jut juse &testobj.

    You can't deduce a member function like this if it needs to have argument: you'll need to specify the argument:

    template <class T, typename RETURN_TYPE, typename...Args, typename... Arguments>
    std::function<RETURN_TYPE(Arguments...)>
    obj_bind(RETURN_TYPE (T::*in_fun)(Args...), T & obj, Arguments... params) {
        ...
    }
    

    This gets you over the immediate error. The next problem is that you somehow related the bound arguments to the type of function being called. Of course, that's not how it works. In your case, the resulting argument actually doesn't take any arguments, i.e., you'd have a declaration like this:

    template <class T, typename RETURN_TYPE, typename... Args, typename... Arguments>
    std::function<RETURN_TYPE()>
    obj_bind(RETURN_TYPE (T::*in_fun)(Args...), T & obj, Arguments... params) {
        ...
    }
    

    In case you actually use a placeholder with your function, the returned std::function<RC(...)> would actually take some argument. Figuring these arguments out is somewhat non-trivial. When restricting the argument type to a pointer to function or a pointer to member function it should be doable to return an appropriate function object, though.

    Just for the fun of it, here is an implementation which seems to deal with placeholders (it isn't thoroughly tested, though):

    #include <functional>
    #include <iostream>
    #include <string>
    
    using namespace std::placeholders;
    
    struct foo
    {
        void f(int i, double d, char c) {
            std::cout << "f(int=" << i << ", double=" << d << ", char=" << c << ")\n";
        }
    };
    
    template <typename...> struct type_list;
    
    template <typename, typename, typename, typename> struct objbind_result_type;
    template <typename RC, typename... RA>
    struct objbind_result_type<RC, type_list<RA...>, type_list<>, type_list<> > {
        typedef std::function<RC(RA...)> type;
    };
    
    template <typename RC,
              typename... RA,
              typename A0, typename... A,
              typename B0, typename... B>
    struct objbind_result_type<RC, type_list<RA...>,
                               type_list<A0, A...>,
                               type_list<B0, B...> >;
    
    template <bool, typename, typename, typename, typename, typename>
    struct objbind_result_type_helper;
    template <typename A0, typename RC, typename... RA, typename... A, typename...B>
    struct objbind_result_type_helper<true, A0, RC, type_list<RA...>, type_list<A...>, type_list<B...> > {
        typedef typename objbind_result_type<RC, type_list<RA..., A0>, type_list<A...>, type_list<B...> >::type type;
    };
    
    template <typename A0, typename RC, typename... RA, typename... A, typename...B>
    struct objbind_result_type_helper<false, A0, RC, type_list<RA...>, type_list<A...>, type_list<B...> > {
        typedef typename objbind_result_type<RC, type_list<RA...>, type_list<A...>, type_list<B...> >::type type;
    };
    
    template <typename RC,
              typename... RA,
              typename A0, typename... A,
              typename B0, typename... B>
    struct objbind_result_type<RC, type_list<RA...>,
                               type_list<A0, A...>,
                               type_list<B0, B...> > {
        typedef typename objbind_result_type_helper<bool(std::is_placeholder<B0>::value), A0,
                                                    RC, type_list<RA...>,
                                                    type_list<A...>,
                                                    type_list<B...> >::type type;
    };
    
    
    // This is the function I'd like to have working:
    template <class T, typename RETURN_TYPE, typename...Args, typename... Arguments>
    typename objbind_result_type<RETURN_TYPE, type_list<>, type_list<Args...>, type_list<Arguments...> >::type
    obj_bind(RETURN_TYPE (T::*in_fun)(Args...), T & obj, Arguments... params)
    {
      return std::bind(in_fun, &obj, params...);
    }
    
    int main()
    {
      foo fo;
      auto fun = obj_bind(&foo::f, fo, _1, 2.34, _2);
      fun(17, 'b');
    }