C-Python-like-Decorators icon indicating copy to clipboard operation
C-Python-like-Decorators copied to clipboard

Help with decorated function return type

Open RicardoM17 opened this issue 2 years ago • 1 comments

Hello @TheMaverickProgrammer !

First of all thank you for this tutorial. I don't have it working but I've already learned a bunch of stuff.

I'm working with ROS and I wanted to decorate our callback functions so that I can log both the request that was made and the returned response. It seems like I am almost there as I have managed to do this with some dummy functions but I'm having some trouble with the final step.

Basically I'm trying to use a decorate function as the callback function to this method https://docs.ros.org/en/melodic/api/roscpp/html/classros_1_1NodeHandle.html#a0262cf4028324bbcd896b6909ab867ae (it has a few overloads).

I'm not expecting you to know ROS but for a static method this is how it usually goes:

ros::ServiceServer test_service_ = node_.advertiseService("/test_service_topic", callbackFunction);

where the callback function takes the form

bool staticCallbackFunction(std_srvs::Empty& request, std_srvs::Empty& response) {

However, and taking your stars example for now, if I do

auto decorated_callback = static_stars(&callbackFunction);

ros::ServiceServer test_service_ = node_.advertiseService("/test_service_topic", decorated_callback);

with

template <typename F>
auto stars(const F& func) {
	return [func](auto&&... args) {
		std::cout << "*******" << std::endl;
		func(std::forward<decltype(args)>(args)...);
		std::cout << std::flush << "\n*******" << std::endl;
	};
}

then I get the following error when compiling, that the types don't match:

In instantiation of ‘struct stars(const F&) [with F = bool(std_srvs::Empty_<std::allocator<void> >&, std_srvs::Empty_<std::allocator<void> >&)]::<lambda(auto:1&& ...)>’:
/filename/:95:2:   required from ‘auto stars(const F&) [with F = bool(std_srvs::Empty_<std::allocator<void> >&, std_srvs::Empty_<std::allocator<void> >&)]’
/filename/:   required from here
/filename/: error: field ‘stars(const F&) [with F = bool(hive_msgs::std_srvs::Empty_<std::allocator<void> >&, std_srvs::Empty_<std::allocator<void> >&)]::<lambda(auto:1&& ...)>::<func capture>’ invalidly declared function type
  return [func](auto&&... args) {
          ^~~~
/filename/: In member function ‘void MainClass::init()’:
/filename/: error: no matching function for call to ‘ros::NodeHandle::advertiseService(const char [16], stars(const F&) [with F = bool(std_srvs::Empty_<std::allocator<void> >&, std_srvs::Empty_<std::allocator<void> >&)]::<lambda(auto:1&& ...)>&)’
  [test_service_2_ = node_.advertiseService("/test_service_2", decorated_callback);](ros::ServiceServer test_service_ = node_.advertiseService("/test_service_topic", decorated_callback);)
                                                                                ^
/opt/ros/noetic/include/ros/node_handle.h:879:17: note: candidate: template<class T, class MReq, class MRes> ros::ServiceServer ros::NodeHandle::advertiseService(const string&, bool (T::*)(MReq&, MRes&), T*)
   ServiceServer advertiseService(const std::string& service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
                 ^~~~~~~~~~~~~~~~
/opt/ros/noetic/include/ros/node_handle.h:879:17: note:   template argument deduction/substitution failed:
/filename/: note:   mismatched types ‘bool (T::*)(MReq&, MRes&)’ and ‘stars(const F&) [with F = bool(hive_msgs::std_srvs::Empty_<std::allocator<void> >&, hive_msgs::std_srvs::Empty_<std::allocator<void> >&)]::<lambda(auto:1&& ...)>’
  [test_service_2_ = node_.advertiseService("/test_service_2", decorated_callback);](ros::ServiceServer test_service_ = node_.advertiseService("/test_service_topic", decorated_callback);)

I changed some of the names and types like the message above to make it more generic.

In a nutshell the advertiseService requires a function callback of a matching type, which is not what we get from the decorated function. I guess I see two ways to solve this but I am not as experienced as you:

  1. Can we somehow define the type of the decorated function so that it matches? (Preferred solution).
  2. Can we somehow overload the advertiseService method to be able to receive a decorated function? (Feasible but more cumbersome to implement across many classes.

A couple of more notes that might simplify things.

  • The decorated function doesn't need to receive any arguments. For now I would be happy to just print both the req and then the res variables.
  • The callback function always has these two variables but they are of different types. I don't know however if that makes a big difference.

I think that is it.

Thank you very much in advance for you help :)

RicardoM17 avatar Sep 06 '23 17:09 RicardoM17

I am going off of very limited information here as I don't have the full code but after reading and looking at the documentation, you want to use the variable decorated_callback as a function pointer - or at least that is what the function .advertiseService(...) is expecting in argument position 2. The lambda function returned from stars(...) is not a function pointer and since it uses a lambda capture, it cannot be implicitly cast as one.

It might be better to wrap the advertiseService function instead of supplying a wrapped function for the function pointer. If that's a direction you are approaching, I recommend reading my follow-up article about making member functions "decoratable". It may be overkill if you are just trying to decorate this one function but it may shed some light on how the compiler is deducing some of these types.

TheMaverickProgrammer avatar Sep 11 '23 14:09 TheMaverickProgrammer