Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add const reference service callback signatures - #3269

Open
KR-Ravindra wants to merge 1 commit into
ros2:rollingfrom
KR-Ravindra:feat/service-callback-const-ref
Open

Add const reference service callback signatures#3269
KR-Ravindra wants to merge 1 commit into
ros2:rollingfrom
KR-Ravindra:feat/service-callback-const-ref

Conversation

@KR-Ravindra

@KR-Ravindra KR-Ravindra commented Sep 13, 2026

Copy link
Copy Markdown

Problem

AnySubscriptionCallback accepts const MessageT & callbacks, but AnyServiceCallback only accepts std::shared_ptr signatures. Every service callback therefore has to take shared_ptrs (the response one is copied per call) even when the handler answers immediately and never needs to extend the request/response lifetime. This is inconsistent with subscriptions.

Design

Follows the approach suggested in the issue: the request and response keep arriving as shared_ptr internally; dispatch() dereferences them for the user callback, exactly like AnySubscriptionCallback does for const MessageT &. The existing shared_ptr overloads are unchanged.

Fix

rclcpp/include/rclcpp/any_service_callback.hpp

  • New variant alternatives ConstRefCallback = void (const Request &, Response &) and ConstRefWithRequestHeaderCallback = void (const rmw_request_id_t &, const Request &, Response &), appended after the existing alternatives.
  • Both set() overloads get matching function_traits::same_arguments branches so std::bind results resolve like the shared_ptr shapes do.
  • dispatch() calls cb(*request, *response) / cb(*request_header, *request, *response) and returns the response as before.

The new alternatives cannot be ambiguous with the existing ones in the std::variant converting assignment: a callable taking const Request & is not invocable with a shared_ptr<Request> and vice versa, so at most one std::function alternative is viable for any callback.

How tested

rclcpp/test/rclcpp/test_any_service_callback.cpp: four new cases (const-ref without/with request header, response mutation propagated to the returned shared_ptr, std::bind for both shapes). rclcpp/test/rclcpp/test_service.cpp: create_service<> with a const-ref callback, exercised end to end through a client.

Before the change, the new callback shapes fail to compile:

any_service_callback.hpp:102:17: error: no match for 'operator=' (operand types are
'std::variant<std::monostate, std::function<void(std::shared_ptr<...Request>, std::shared_ptr<...Response>)>, ...>'
and '<lambda(const ...Request&, ...Response&)>')

After the change the header compiles and all cases pass. I could not run colcon test locally (no ROS 2 workspace), so the new gtest cases were mirrored in a standalone gtest harness that compiles the unmodified any_service_callback.hpp / function_traits.hpp against stub rmw/tracetools headers: 10/10 pass with -Wall -Wextra on GCC 13, including the pre-existing callback shapes. cpplint with the ament filters reports no issues. Please rely on CI for the full build and for ament_uncrustify.

Links

This change was prepared with an AI agent operated by KR-Ravindra, who reviewed and tested it.

AnyServiceCallback now also accepts
  void (const Request &, Response &)
  void (const rmw_request_id_t &, const Request &, Response &)
in addition to the existing shared_ptr signatures. The request and
response are still received as shared_ptr internally and are
dereferenced before calling the user callback, mirroring what
AnySubscriptionCallback does for const MessageT & callbacks.

Both set() overloads gain matching function_traits::same_arguments
branches so std::bind results resolve to the right alternative, and
dispatch() handles the two new alternatives. Existing overloads are
unchanged.

Signed-off-by: KR Ravindra <[email protected]>
@KR-Ravindra

Copy link
Copy Markdown
Author

Round 1 self-review.

Verified against rolling (e47c084):

  1. The two new alternatives are appended after the four existing ones in AnyServiceCallback's variant; the existing shared_ptr shapes and their set() branches are untouched.
  2. Ambiguity: a callable taking const Request & is not invocable with shared_ptr<Request> and vice versa, so the std::variant converting assignment sees at most one viable alternative; generic [](auto, auto) lambdas were already ambiguous before this change.
  3. dispatch() dereferences request_header only in the with-header branch; Executor::execute_service always passes a non-null rmw_request_id_t and Service::handle_request already dereferences it.
  4. GenericService keeps its own callback variant and is intentionally not changed here.
  5. Corrected one sentence in the description: upstream dispatch() moves the request and copies only the response shared_ptr.

No duplicate: #3078 cross-references only this PR, and the approach is the one jmachowinski suggested there. Open #3168 touches the same set() overloads, so one of the two will need a rebase. DCO passes; the abi workflow needs a maintainer to approve the run. Marking ready.

@KR-Ravindra
KR-Ravindra marked this pull request as ready for review September 13, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant