Skip to content

Hide noexcept function_traits specializations from Doxygen - #3367

Merged
mjcarroll merged 1 commit into
ros2:rollingfrom
rkent:rosdoc2-fix-rclcpp
Oct 20, 2026
Merged

mjcarroll merged 1 commit into
ros2:rollingfrom
rkent:rosdoc2-fix-rclcpp

Conversation

@rkent

@rkent rkent commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Description

The noexcept specializations added in #3322 make the rosdoc2 documentation build fail: Sphinx's C++ domain hits an internal AssertionError while resolving references in the function_traits specializations. Wrap the noexcept variants in \cond/\endcond so Doxygen skips them. They mirror the documented non-noexcept specializations, and the compiled code is unchanged.

Is this user-facing behavior change?

No, affects documentation only.

Did you use Generative AI?

Yes, Opus 5.5

More details as reported by Opus:

These lines are there to keep Doxygen from documenting the two noexcept specializations. Without them, the rosdoc2 build of rclcpp crashes in Sphinx.

What breaks without it

The unfixed run (runs_rolling/rclcpp/log.txt) gets through Doxygen. Then Sphinx dies while resolving cross-references:

File ".../sphinx/domains/cpp/init.py", line 1115, in _resolve_xref_inner
assert parent_symbol # should be there
AssertionError
The patched run (runs_rolling/fix_rclcpp/log.txt) ends with build succeeded.

Why noexcept is the trigger

In C++17, noexcept became part of a function's type. So R(Args...) and R(Args...) noexcept are different types, and function_traits needs separate partial specializations for each. That's the point of the comment in the code.
Doxygen sees four separate specializations and outputs four separate class pages. The log shows entries like function_traits< ReturnTypeT(Args ...) noexcept >.
Breathe/Exhale turns each page into a Sphinx cpp:struct declaration. Sphinx's C++ domain doesn't handle noexcept inside a function type used as a template argument. In its symbol table, the noexcept specialization ends up colliding with (or failing to attach to) the plain one. When Sphinx later resolves a reference to it, the parent symbol it expects isn't there, and an internal assert fails. Because it's an assert, it doesn't just log a warning like the "Duplicate C++ declaration" messages elsewhere in the log; it aborts the whole build.
To be clear about the evidence: the traceback and the before/after runs are confirmed. The exact way Sphinx mishandles noexcept is my best reading. The log has no warning naming these structs before the assert.

Why \cond / \endcond

/// \cond … /// \endcond is Doxygen's way to skip a block. Everything between the markers is left out of the docs, but the compiler still sees it, so the code behaves exactly the same. The only loss is two doc pages nobody needs: these specializations just inherit from the non-noexcept versions, which are still documented.

It's the same idea as the DOXYGEN_ONLY trick a few lines further down in function_traits.hpp. That one shows Doxygen a simpler declaration; this one hides the problem declarations completely.

The noexcept specializations added in ros2#3322 make the rosdoc2
documentation build fail: Sphinx's C++ domain hits an internal
AssertionError while resolving references in the function_traits
specializations. Wrap the noexcept variants in \cond/\endcond so Doxygen
skips them. They mirror the documented non-noexcept specializations, and
the compiled code is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: R Kent James <kent@caspia.com>
@mjcarroll

Copy link
Copy Markdown
Member

Just a documentation change and passes Rpr, does not need full CI.

@github-actions

Copy link
Copy Markdown

ABI Compliance Check

✅ Verdict: compatible

Library Verdict Summary
libcomponent_manager.so ✅ compatible No ABI changes detected.
librclcpp.so ✅ compatible No ABI changes detected.
librclcpp_action.so ✅ compatible No ABI changes detected.
librclcpp_lifecycle.so ✅ compatible No ABI changes detected.
✅ libcomponent_manager.so — full abidiff report

Compared:

  • Base: lib-base/libcomponent_manager.so
  • Head: lib-pr/libcomponent_manager.so @ 0a1311c
(empty report — no differences printed by abidiff)
✅ librclcpp.so — full abidiff report

Compared:

  • Base: lib-base/librclcpp.so
  • Head: lib-pr/librclcpp.so @ 0a1311c
(empty report — no differences printed by abidiff)
✅ librclcpp_action.so — full abidiff report

Compared:

  • Base: lib-base/librclcpp_action.so
  • Head: lib-pr/librclcpp_action.so @ 0a1311c
(empty report — no differences printed by abidiff)
✅ librclcpp_lifecycle.so — full abidiff report

Compared:

  • Base: lib-base/librclcpp_lifecycle.so
  • Head: lib-pr/librclcpp_lifecycle.so @ 0a1311c
(empty report — no differences printed by abidiff)

Updated for commit 0a1311c · check run · suppressions: /home/runner/work/_temp/ros2-abi-suppressions.txt

@mjcarroll
mjcarroll merged commit e64ad31 into ros2:rolling Oct 20, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants