I have been getting started with ROS and came across the following subscriber code:-
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}
My confusion is specifically regarding the syntax
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
As per my understanding, this is a ROS node to keep subscribing messages of type string from the Topic named "topic" and execute the topic_callback() whenever there is a new message. Coming from a very raw background, I wanted to ask why do we even need to use the bind() function? Can't we simply pass topic_callback function or a pointer to that, as an argument (Probably like Python) ?
And why do we need to specify "this"? When we are referencing the topic_callback, it makes sense only if there exists an MinimalSubscriber object whose member function is topic_callback, so again specifying this seems to be redundant. Same for _1, there's only 1 argument being passed, i.e. String. According to me instead of bind, this->topic_callback could have been passed.
I am probably wrong at several fronts. I did look at this excellent answer:- std::bind(), but couldn't grasp it completely.
This problem is not ROS specific. Consider that you have a function that accepts a callback returning void
and taking 1 std::string
argument:
void foo(std::function<void(std::string)> callback) {
if (callback) {
callback("foo");
}
}
If you have a free function void freeFun(std::string)
, you can pass it directly foo(freeFun);
, however if you have a class or struct:
struct Foo {
void bar(std::string str) {
std::cout << "bar str = " << str << "\n";
}
static void baz(std::string str) {
std::cout << "baz str = " << str << "\n";
}
void registerCallback();
};
then you cannot simply use bar
as argument, because bar
needs an object of type Foo
to be called on. So:
using namespace std::placeholders;
int main() {
// foo(&Foo::bar); // nope
foo(&Foo::baz); // ok, because static method doesn't need associated object
Foo myFoo;
foo(std::bind(&Foo::bar, &myFoo, _1)); // ok, but rarely used for practical reasons
}
void Foo::registerCallback() {
foo(std::bind(&Foo::bar, this, _1)); // ok, typical use-case
}
std::bind
transforms function bar
that (informally) has a signature void bar(Foo*, std::string)
to an object that internally holds "bound" arguments, and that can be called like a function with signature void f(std::string)
.
The same could be achieved using lambda expression
foo([this](std::string str) { bar(str); });
or a generic lambda if you don't want to be bothered with argument type:
foo([this](auto str) { bar(str); });