nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
Public Types | Public Member Functions | Friends | List of all members
rclcpp::Client< S > Class Template Reference

#include <client.hpp>

Public Types

using ConstSharedPtr = ::rclcpp::ClientHandle< S >
 
using RequestType = typename S::Request
 
using ResponseType = typename S::Response
 
using SharedPtr = ::rclcpp::ClientHandle< S >
 
using TypedResponseFn = void(*)(const ResponseType &response)
 
using UniquePtr = ::rclcpp::ClientHandle< S >
 rclcpp::Client<S>::UniquePtr — see SharedPtr.
 

Public Member Functions

Result async_send_request (const RequestType &req)
 
 Client ()
 
 Client (Client &&other)
 
::rclcpp::QoS get_request_publisher_actual_qos () const
 
::rclcpp::QoS get_response_subscription_actual_qos () const
 
const char * get_service_name () const
 
size_t handle_id () const
 Executor arena slot for the registration; SIZE_MAX until registered.
 
bool is_valid () const
 Check if the client was registered.
 
Client & operator= (Client &&other)
 
::rclcpp::ResultOf< bool > service_is_ready () const
 
template<typename T = void>
Result wait_for_service ()
 
Result wait_for_service (uint32_t timeout_ms)
 
 ~Client ()
 

Friends

class ::rclcpp::Node
 

Detailed Description

template<typename S>
class rclcpp::Client< S >

Dispatch service client for a ROS 2 service — the rclcpp model.

A response handler is registered into the executor arena, which owns the RmwServiceClient and runs the handler during spin_once. The service type S must provide nested Request and Response types with TYPE_NAME, TYPE_HASH, SERIALIZED_SIZE_MAX, ffi_serialize(), and ffi_deserialize().

THIS OBJECT IS BOOKKEEPING PLUS ITS VERBS — phase-456 W9, amended 2026-09-28. The client, the reply buffer, the handler and its context are all the arena's; W3 made the arena's trampoline context the user's HANDLER rather than &out, so after registration nothing of the caller's is referenced and this object is freely movable.

What it does hold is what a REGISTRATION can be asked about and the arena cannot be asked for by an index alone: {initialized_, handle_id_, executor_, service_name_}. executor_ and handle_id_ are the pair that names the arena entry (issue 1437) — and they are also the argument list of nros_cpp_service_client_send_on_handle, which is why async_send_request needs nothing more. service_name_ is the phase-444 C++-side copy, because the runtime takes the name at create and drops it.

THE SAME PAIR IS NOW THE WHOLE VERB SET. 2026-09-28 added service_is_ready and wait_for_service, which W9 had to leave off — and it added no member to do it: both FFI entry points took caller storage and now take (storage, executor, handle_id) beside it, so {executor_, handle_id_} answers three verbs rather than one. The object is still bookkeeping over an arena entry; what changed is how much of the arena an index can be asked about.

Measured (phase-456 W3, re-measured W9), across examples/, tests/, book/ and packages/: a dispatch client has exactly ONE verb INVOKED on it, async_send_request, at one example site plus the move probe W3 added. That is why Client<S>::SharedPtr is a two-word rclcpp::ClientHandle<S> — a handle with verbs, not the empty keep-alive ServiceHandle<S> is, and not an Owned<Client<S>>, because the arena owns the entity and the caller does not. The census says what ported source REACHES FOR today; it is not the argument for which verbs a registration can answer, which is why the two discovery reads landed on the handle as well.

Usage:

void on_response(const AddTwoInts::Response& resp) { /* ... *&zwj;/ }
rclcpp::Client<AddTwoInts> client;
RCLCPP_TRY(node.create_client(client, "/add_two_ints", &on_response));
RCLCPP_TRY(client.async_send_request(req));
// ... or, in ported shape:
auto handle = node.create_client<AddTwoInts>("/add_two_ints", &on_response);
RCLCPP_TRY(handle.async_send_request(req));
Definition client.hpp:129
Client()
Definition client.hpp:365

Member Typedef Documentation

◆ ConstSharedPtr

template<typename S >
using rclcpp::Client< S >::ConstSharedPtr = ::rclcpp::ClientHandle<S>

rclcpp::Client<S>::ConstSharedPtr — see SharedPtr. The same handle: async_send_request is const on it (the handle is two words the caller owns; the mutation is the arena's), so there is no mutable/const distinction to draw.

◆ RequestType

template<typename S >
using rclcpp::Client< S >::RequestType = typename S::Request

◆ ResponseType

template<typename S >
using rclcpp::Client< S >::ResponseType = typename S::Response

◆ SharedPtr

template<typename S >
using rclcpp::Client< S >::SharedPtr = ::rclcpp::ClientHandle<S>

rclcpp::Client<S>::SharedPtr — phase-456 W9.

rclcpp::Client<S>::SharedPtr member_; is how ported source declares a client member, so this alias must exist on every target — which std::shared_ptr does not.

IT IS NOT A POINTER TO A Client<S>. A registered client is the arena's; what this names is rclcpp::ClientHandle<S> — two words, copyable, carrying exactly the one verb the census says a dispatch client is asked for. See client_handle.hpp.

◆ TypedResponseFn

template<typename S >
using rclcpp::Client< S >::TypedResponseFn = void (*)(const ResponseType& response)

Phase 189.M3.3.f — typed response-handler signature for the callback-style client (rclcpp async dispatch). The handler runs during spin_once when a reply arrives for a request sent via async_send_request.

phase-456 W3 deleted the TypedResponseFnWithCtx sibling, for the reason stated on Service<S>::TypedServiceFn: the SFINAE guard on Node::create_client admits only a void(*)(const Response&), so no overload could set it and the branch reading it was unreachable.

◆ UniquePtr

template<typename S >
using rclcpp::Client< S >::UniquePtr = ::rclcpp::ClientHandle<S>

rclcpp::Client<S>::UniquePtr — see SharedPtr.

Constructor & Destructor Documentation

◆ ~Client()

template<typename S >
rclcpp::Client< S >::~Client ( )
inline

Destructor — there is nothing to release.

The executor arena owns the client, the reply buffer and the handler. This destructor deliberately does not release them: it clears this object's own bookkeeping, and the registration lives until its node is destroyed or a ClientHandle releases it (cli_.reset(), issue 1667). Until phase-456 W9 the same destructor also freed a FUTURE-style client, behind an if (initialized_ && !callback_mode_); that half moved to rclcpp::PollClient<S>, where the condition is unconditional.

◆ Client() [1/2]

template<typename S >
rclcpp::Client< S >::Client ( Client< S > &&  other)
inline

◆ Client() [2/2]

template<typename S >
rclcpp::Client< S >::Client ( )
inline

Default constructor – creates an unregistered service client. Use Node::create_client() to register one.

Member Function Documentation

◆ async_send_request()

template<typename S >
Result rclcpp::Client< S >::async_send_request ( const RequestType &  req)
inline

Phase 189.M3.3.f — THE ONE VERB. Send a request; the reply is delivered to the registered response handler during spin_once (no Future). Returns immediately after sending.

UPSTREAM PARITY: upstream's async_send_request(req) returns a std::shared_future<Response::SharedPtr> and its two-argument form takes a callback per REQUEST. Ours returns a Result and the handler is bound once, at registration — ledgered at cpp:Client::async_send_request, divergence / adopt. What changed in phase-456 W9 is only that the callback_mode_ check is gone with the flag: every rclcpp::Client<S> is a dispatch client now, and the future-style road is rclcpp::PollClient<S>.

◆ get_request_publisher_actual_qos()

template<typename S >
::rclcpp::QoS rclcpp::Client< S >::get_request_publisher_actual_qos ( ) const
inline

The QoS the backend GRANTED this client's REQUEST endpoint — the publisher that sends calls. Issue 1437.

rclcpp::Client::get_request_publisher_actual_qos. ONE create_client builds TWO endpoints that negotiate against DIFFERENT peers, so this and get_response_subscription_actual_qos are two answers and neither stands for the other.

A policy the backend cannot report is an ABSENCE (ReliabilityPolicy::Unknown and friends), never the request echoed back — see Publisher::get_actual_qos.

phase-456 W9 — BOTH HALVES ANSWER, and this accessor is the WORKED EXAMPLE the two later closures copied. Issue 1437 gave a dispatch client executor_ beside handle_id_, and nros_cpp_service_client_get_actual_qos serves both roads by construction — storage for the owner, (executor, handle_id) for the arena — so the upstream no-argument spelling was reachable here with no weakening and no ledger row was ever owed.

W9 had to leave get_actual_qos off the dispatch Subscription<M> for want of exactly that: the type held only sched_handle_id_. Both ends of that gap are closed now, each by taking this shape rather than by changing a signature — the subscription carries its executor since phase-467 (2026-09-25, its ledger row deleted), and this class's own service_is_ready / wait_for_service since 2026-09-28.

◆ get_response_subscription_actual_qos()

template<typename S >
::rclcpp::QoS rclcpp::Client< S >::get_response_subscription_actual_qos ( ) const
inline

The QoS the backend GRANTED this client's RESPONSE endpoint — the subscription that receives replies. Issue 1437; see get_request_publisher_actual_qos.

◆ get_service_name()

template<typename S >
const char * rclcpp::Client< S >::get_service_name ( ) const
inline

Read back the service name this client was created on — phase-444.

rclcpp's ClientBase::get_service_name, and the client half of "four entity families, one accessor" (ledger rows cpp:Client::get_service_name and cpp:Service::get_service_name): Publisher and Subscription have had get_topic_name() all along, both action tiers gained get_action_name() in phase-417 W4.b, and the service pair was the one family with no accessor in any C++ tier.

The name lives C++-side in service_name_, for the reason ActionServer::get_action_name states: the runtime does NOT own it. nros_cpp_service_client_register takes service_name and drops it, so there is nothing FFI-side to hand back, and a borrowed const char* was rejected because create_client takes a pointer a hosted caller may well have obtained from a temporary.

Returns "" (never NULL) on an unregistered client, matching Publisher::get_topic_name and both action tiers.

phase-456 W9 split this class in two and the method answers on BOTH halves, unchanged — the same shape Service::get_service_name took under W5. Each half keeps its own service_name_, because the copy is made at create from the argument the caller passed and neither owner can recover it afterwards.

◆ handle_id()

template<typename S >
size_t rclcpp::Client< S >::handle_id ( ) const
inline

Executor arena slot for the registration; SIZE_MAX until registered.

◆ is_valid()

template<typename S >
bool rclcpp::Client< S >::is_valid ( ) const
inline

Check if the client was registered.

◆ operator=()

template<typename S >
Client & rclcpp::Client< S >::operator= ( Client< S > &&  other)
inline

◆ service_is_ready()

template<typename S >
::rclcpp::ResultOf< bool > rclcpp::Client< S >::service_is_ready ( ) const
inline

Is a matching service server visible? — rclcpp::ClientBase:: service_is_ready, no arguments, as upstream's is.

  • ok(true) — at least one matching server is currently visible.
  • ok(false) — none discovered yet.
  • error(Unsupported) — the backend cannot answer (XRCE has no participant enumeration); fall back to a timed wait_for_service or assume reachability.
  • error(<code>) — the probe itself failed.

Never spins the executor, so it is safe inside a callback. The tri-state is the divergence from upstream's bare bool and is ledgered at cpp:Client::service_is_ready: RFC-0018 forbids exceptions, so a failed probe and a successful "no" must be distinguishable by return value.

This is the gap phase-456 W9 RECORDED, closed 2026-09-28. The split left this verb on the future-style half alone, because nros_cpp_service_client_server_available read the RmwServiceClient in CALLER storage and a dispatch client has none — so a porter holding a Client<S>::SharedPtr could not ask at all. The fix is the FFI's, not the signature's: that entry point now takes (storage, executor, handle_id), the shape nros_cpp_service_client_get_actual_qos has had since issue 1437, and this class already holds {executor_, handle_id_}. Inventing an Executor& parameter here was refused — a method that exists to adopt the upstream spelling cannot close a gap by leaving it.

◆ wait_for_service() [1/2]

template<typename S >
template<typename T = void>
Result rclcpp::Client< S >::wait_for_service ( )
inline

REFUSED — wait_for_service() with no budget. phase-417 stage 3.

A member template rather than = delete for the C++14 reason given on Executor::spin_once(): a deleted function carries no message.

◆ wait_for_service() [2/2]

template<typename S >
Result rclcpp::Client< S >::wait_for_service ( uint32_t  timeout_ms)
inline

Block until a matching service server is discoverable — rclcpp::ClientBase::wait_for_service.

Spins the executor cooperatively while probing, so do NOT call it from inside a callback; use service_is_ready there. Returns ok when the server is visible, Timeout when the budget elapses, Unsupported at once when the backend cannot know (XRCE — issue 1686; a caller that may send blind proceeds).

The budget is REQUIRED — phase-417 stage 3, and the reason is RFC-0021 rather than convenience: this call drives a single-threaded executor, so a wait that never returns starves every other entity, and uint32_t has no value to port upstream's -1 to. The no-argument form is a compile error carrying NROS_RCLCPP_REFUSE_UNBOUNDED_WAIT, exactly as it is on rclcpp::PollClient<S> and rclcpp_action::Client<A> — one concept, now three sites, each with its own expected-failure TU.

Closed 2026-09-28 with service_is_ready; that method's note carries why the remedy was the FFI's argument list and not this one.

Friends And Related Symbol Documentation

◆ ::rclcpp::Node

template<typename S >
friend class ::rclcpp::Node
friend

The documentation for this class was generated from the following files: