Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C++ API

The C++ user-facing surface lives in packages/api/nros-cpp/include/, at upstream’s paths: rclcpp/, rclcpp_action/ and rclcpp_lifecycle/ (rclcpp/detail/ is internal). Generated by Doxygen; always reflects the current main branch.

API reference

C++ API Doxygen — start here.

The umbrella header rclcpp/rclcpp.hpp pulls in every public surface a user application needs.

Where to start

  • rclcpp/rclcpp.hpp — convenience umbrella include
  • rclcpp::init_in / rclcpp::shutdown — session lifetime
  • rclcpp::Node — node + create_publisher/subscription/service/client/action_*
  • rclcpp::Publisher<M> / Subscription<M>
  • rclcpp::Service<S> / Client<S> — the DISPATCH service server and client. A handler is registered into the executor arena, which owns the entity. Service<S>::SharedPtr is rclcpp::ServiceHandle<S>, a two-word keep-alive with nothing to dereference (phase-456 W5); Client<S>::SharedPtr is rclcpp::ClientHandle<S>, the same two words WITH one verb — async_send_request, which is all a census of the corpus found anyone calling on a dispatch client (phase-456 W9). Neither has an operator->.
  • rclcpp::PollService<S> — L1 polling service server. The caller owns the server and drains it with take_request / send_response from its own spin loop. node.create_service<S>(name) with no handler hands one back.
  • rclcpp::PollClient<S> — L1 future-style service client, the client half of the same split (phase-456 W9). The caller owns the client and drains the reply itself: send_request returns a rclcpp::Future<T>, call blocks while driving the executor, call_polling blocks without driving it, and wait_for_service / service_is_ready probe the server. node.create_client<S>(name) with no handler hands one back.
  • rclcpp_action::Server<A> / Client<A> — L2 callback model (executor-arena registered). rclcpp::ActionServer<A> / rclcpp::ActionClient<A> are the same two types.
  • rclcpp::PollingActionServer<A> / PollingActionClient<A> — L1 polling model. Caller drives take_* / accept_goal / complete_goal from a spin loop.
  • rclcpp::Future<T> — async result handle
  • rclcpp::Executor, Timer, GuardCondition
  • Parameters are methods ON the node — declare_parameter<T> / get_parameter<T> / set_parameter<T> / has_parameter, upstream’s names, forwarding to the one store the rcl_interfaces services read. rclcpp::Seq<T, N> is the array VALUE a freestanding node declares with (std::vector<T> under NROS_CPP_STD). The standalone rclcpp::ParameterServer<Cap> is GONE (phase-426 W4): it was a second store, and what it held was invisible to ros2 param get. See Differences from ROS 2 §9 for what is intentionally smaller than rclcpp.

The library is freestanding C++14 — no STL, no exceptions. A second, std-flavoured surface exists for porting upstream rclcpp code and is reached by defining NROS_CPP_STD; see Two surfaces below for whether you need it.

Two-layer API. The rclcpp::ActionServer<A> / ActionClient<A> templates are L2 callback handles (set callbacks via nros_cpp_action_server_set_callbacks, dispatched by the executor). The new rclcpp::PollingActionServer<A> / PollingActionClient<A> are L1 polling templates with the same typed serdes glue but caller-driven control flow. Subscriptions / services / service clients are already L1-shaped in nros-cpp. See Two-Layer API for the per-layer rationale.

Event-driven wake callbacks. PollingActionServer<A> and PollingActionClient<A> expose a nested WakeState POD and typed set_{goal,cancel,get_result,goal_response, cancel_response,result,feedback}_wake_callback(state, cb, ctx) methods. Pair an nros_cpp_wake_state_t with each entity / channel to wake on rx instead of polling.

Two surfaces: freestanding, and NROS_CPP_STD

One set of headers ships two API shapes, and a build picks one.

The freestanding surface is the default and it is not a fallback. It is plain C++14 with no standard library, no exceptions and no RTTI: rclcpp::Node, create_* writing through an out-reference and returning an rclcpp::Result, const char* for names, uint64_t period_ms for durations. Every nano-ros program in this repository is on it — the native/host examples included.

The std surface is an opt-in and it is reached only by defining NROS_CPP_STD. Defining it turns on the six NROS_CPP_HAS_* capability macros in rclcpp/detail/std_detect.hpp, and with them: the std::string / std::function / std::chrono / std::vector convenience overloads in nros/std_compat.hpp, the RCLCPP_*_STREAM logging macros, and — the piece that actually motivates the flag — rclcpp::Node, whose factories return std::shared_ptr and whose name parameters are const std::string&.

Do I need this flag?

The split is ported rclcpp code versus code written for nano-ros. It is not host versus embedded. A native Linux build does not want the flag any more than a Cortex-M3 build does; a ported node wants it on both.

You are…FlagWhy
writing a new node against rclcpp::Node / rclcpp::Publisher<M> / rclcpp::ExecutornoThe freestanding surface is the whole API you are using. This is the common case on every platform, native included.
compiling an upstream ROS 2 .cpp unmodified — it says class MyNode : public rclcpp::Node, std::make_shared<MyNode>(), create_publisher<M>(...) returning a SharedPtryesThose signatures are spelled in std::shared_ptr and std::string. Without the flag they do not exist and you get a compile error naming a missing overload or an unknown rclcpp::Node.
passing a std::string topic name or a std::chrono period into an otherwise nano-ros-native fileyesThose are the std_compat.hpp overloads.
building for Zephyr, FreeRTOS, NuttX, ThreadX, or any -ffreestanding toolchainnoAnd here it is not merely unnecessary: asking for the std surface asks the headers to #include <string>, which on those toolchains is either absent or a hard #error.

If you are unsure, build without it. The failure direction is loud: a missing overload at compile time, never a silent behaviour change.

Turning it on

Define it on the target, not per file:

target_compile_definitions(my_ported_node PRIVATE NROS_CPP_STD=1)

Per-target rather than per-#define-before-include, because the macro changes the layout of rclcpp::Node (it carries hosted-only members and an enable_shared_from_this base), so two translation units of one image disagreeing about it is an ODR/layout break rather than a missing function. That disagreement is reachable in practice — examples/px4/cpp/bridge/ sets the flag on one module of a larger image deliberately — so keep the definition at target granularity and give every TU of a target the same answer.

A ported project usually needs none of this. cmake/NanoRosAmentSurface.cmake sets NROS_CPP_STD=1 on every target its ament_* verbs create, and find_package(rclcpp) sets it on whatever links rclcpp::rclcpp, so a ported package already has the surface it needs; see Porting a ROS 2 C++ node.

Why it is not detected for you

It used to be. Until phase-438 the capability macros were discovered from the include path with __has_include, so a hosted compiler handed you the std-flavoured API whether you asked for it or not. That was removed because no probe answers the question correctly on both embedded lanes:

__STDC_HOSTED____has_include(<string>)#include <string>
arm-none-eabi 13.2, -ffreestanding (FreeRTOS lane)0TRUEhard #error
Zephyr -nostdinc++, minimal libcpp1FALSEabsent
hosted g++ 12.31TRUEworks

Each probe is right on exactly one embedded lane and wrong on the other: libstdc++ 13 added bits/requires_hosted.h, so header presence stopped implying usability. Only the request is right on both, because it is not a probe — it is you saying which surface you want. The full measurement lives in packages/api/nros-cpp/include/rclcpp/detail/std_detect.hpp.

Generating locally

doxygen packages/api/nros-cpp/Doxyfile
xdg-open target/doc/cpp-api/html/index.html