Search code examples
c++functional-programmingros

How to pass a private member function as an argument


In ROS, there is a function called NodeHanle::subscribe(Args...): NodeHandle::subscribe. Which lets u pass a PRIVATE member function as callback.

However, when I tried it myself (passing private member function using std::bind), my compiler always fails and complaining about Foo::foo() is a private member function. When I change Foo::foo to public function, everything goes to normal.

template<typename T>
void getWrapper1(void(T::*fn)(int), T *t) { 
  return [&](int arg) {
    std::cout << "process before function with wrapper" << std::endl;
    (t->*fn)(arg);
    std::cout << "process after function with wrapper" << std::endl;
  };
}

void getWrapper2(std::function<void(int)> fn) {
  return [=](int arg) {
    std::cout << "process before function with wrapper" << std::endl;
    fn(arg);
    std::cout << "process after function with wrapper" << std::endl;
  }
}

class Foo {
private:
  void foo(int a) {
    std::cout << __FUNCTION__ << a << std::endl;
  }
}

int main(int argc, char** argv) {
  Foo foo_inst;
  auto func1 = getWrapper1(&Foo::foo, &foo_inst); // fail because foo is private
  auto func2 = getWrapper2(std::bind(&Foo::foo, &foo_inst, std::placeholders::_1));  // fail because foo is private
  func1(1);
  func2(2);
  return 0;
}

from this answer, using std::function can also passing private member function. But what I tried it different.

It worths to mention that in getWrapper2 I use [=] instead of [&] because using [&] may cause seg fault. Why it has to be a "value capture"?

platform: GCC 5.4.0, c++14, ubuntu16.04


Solution

  • You must pass it from the inside. You cannot access private function from the outside of the class. Not even pointer to private stuff. Private is private.

    class Foo {
        void foo(int a) {
            std::cout << __FUNCTION__ << a << std::endl;
        }
    
     public:
        auto getWrapper() {
            // using a lambda (recommended)
            return getWrapper2([this](int a) {
                return foo(a);
            });
    
            // using a bind (less recommended)
            return getWrapper2(std::bind(&Foo::foo, this, std::placeholders::_1));
        }
    }
    

    Why it has to be a "value capture"?

    Both wrapper need to value capture. Your Wrapper1 have undefined behaviour.

    Consider this:

    // returns a reference to int
    auto test(int a) -> int& {
        // we return the local variable 'a'
        return a;
        // a dies when returning
    }
    

    The same thing happen with a lambda:

    auto test(int a) {
        // we capture the local variable 'a'
        return [&a]{};
        // a dies when returning
    }
    
    auto l = test(1);
    // l contain a captured reference to 'a', which is dead
    

    Pointers are passed by value. A pointer is itself an object. A pointer has itself a lifetime and can die.

    auto test(int* a) -> int*& {
        // we are still returning a reference to local variable 'a'.
        return a;
    }
    

    And... you guessed it, the same thing for std::function:

    auto test(std::function<void(int)> a) {
        // return a lambda capturing a reference to local variable 'a'.
        return [&a]{};
    }