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 includerclcpp::init_in/rclcpp::shutdown— session lifetimerclcpp::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>::SharedPtrisrclcpp::ServiceHandle<S>, a two-word keep-alive with nothing to dereference (phase-456 W5);Client<S>::SharedPtrisrclcpp::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 anoperator->.rclcpp::PollService<S>— L1 polling service server. The caller owns the server and drains it withtake_request/send_responsefrom 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_requestreturns arclcpp::Future<T>,callblocks while driving the executor,call_pollingblocks without driving it, andwait_for_service/service_is_readyprobe 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 drivestake_*/accept_goal/complete_goalfrom a spin loop.rclcpp::Future<T>— async result handlerclcpp::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 thercl_interfacesservices read.rclcpp::Seq<T, N>is the array VALUE a freestanding node declares with (std::vector<T>underNROS_CPP_STD). The standalonerclcpp::ParameterServer<Cap>is GONE (phase-426 W4): it was a second store, and what it held was invisible toros2 param get. See Differences from ROS 2 §9 for what is intentionally smaller thanrclcpp.
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 vianros_cpp_action_server_set_callbacks, dispatched by the executor). The newrclcpp::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>andPollingActionClient<A>expose a nestedWakeStatePOD and typedset_{goal,cancel,get_result,goal_response, cancel_response,result,feedback}_wake_callback(state, cb, ctx)methods. Pair annros_cpp_wake_state_twith 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… | Flag | Why |
|---|---|---|
writing a new node against rclcpp::Node / rclcpp::Publisher<M> / rclcpp::Executor | no | The 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 SharedPtr | yes | Those 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 file | yes | Those are the std_compat.hpp overloads. |
building for Zephyr, FreeRTOS, NuttX, ThreadX, or any -ffreestanding toolchain | no | And 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) | 0 | TRUE | hard #error |
Zephyr -nostdinc++, minimal libcpp | 1 | FALSE | absent |
| hosted g++ 12.3 | 1 | TRUE | works |
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