Skip to content

doc: fix the custom RTTI examples; build: warning flags on the examples - #129

Merged
jll63 merged 3 commits into
boostorg:developfrom
jll63:fix/custom-rtti-example-ids
Oct 11, 2026
Merged

jll63 merged 3 commits into
boostorg:developfrom
jll63:fix/custom-rtti-example-ids

Conversation

@jll63

@jll63 jll63 commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

(Written by Claude Code, on behalf of @jll63.)

Both custom_rtti examples return reinterpret_cast<type_id>(0) for every type that does not derive from Node:

template<typename T>
static auto static_type() {
    if constexpr (is_polymorphic<T>) {
        return reinterpret_cast<type_id>(T::static_type);
    } else {
        return reinterpret_cast<type_id>(0);   // every method shares this
    }
}

That reads as harmless, since such types never take part in dispatch. But static_type is also how a method is identified, and a method<...> specialization is not derived from Node, so it takes that branch. augment_methods keys method_map on rtti::type_index(method_type_id), so every method in the registry lands under the same key: they merge into one logical method, their overriders pool together, no overrider dominates, and the first call reaches fn_ambiguous. Neither registry has an error_handler or output policy, so the program aborts with no message.

The arity assertion in augment_methods does not catch it — it only fires when the merged copies disagree on arity, and then only in a debug build.

Why it is invisible today

Each example declares exactly one method, so nothing merges and both run correctly. The defect bites the reader who follows the page and adds a second method to their own registry, which is the natural next step — and the failure gives them nothing to go on.

Demonstration

Adding a depth method to each example, changing nothing else:

before after
example 1 Aborted (core dumped), exit 134 2 3 + 4 * = 20 depth=3, exit 0
example 2 Aborted (core dumped), exit 134 20 depth=3, exit 0

The fix

Non-polymorphic types get the address of a function-local static — what the library's own static_rtti policy does — and dynamic_type delegates to static_type so the two cannot disagree.

The page gains a paragraph explaining why the shared placeholder is a trap, and the sentence promising a "small, dense range" of type ids is narrowed to the classes that take part in dispatch, which is what vptr_vector indexes.

Scope

Every other custom static_type in the tree already gives non-polymorphic types distinct ids: the static_rtti and named_static_rtti policies, and the five tests that define one (test_custom_rtti_simple, _deferred, _virtual_base, _simple_projection, test_initialize_dropped_class). Only the examples were wrong, which is why no test caught it.

Warning flags on the examples build

The unused std::ostream& parameter that the second commit removes was only noticed by eye, which prompted the question of what does check these files. The answer was nothing: there is no Jamfile under doc/modules/ROOT/examples, so b2 never compiles the examples, and the CMake build — their only check — was passing them no warning flags at all.

doc/modules/ROOT/examples/CMakeLists.txt now mirrors test/CMakeLists.txt: the flags go on under BOOST_OPENMETHOD_WARNINGS_AS_ERRORS, as -Wall -Wextra -Werror or /W4 /WX, set before the first target so they also reach shared_libs and the step-by-step subdirectories. -Wextra is more than the test flags carry.

This is enforced in CI rather than locally only: C++26 reflection is the sole CMake job in ci.yml, it sets WARNINGS_AS_ERRORS=ON, and it configures the library as the root project — which defaults BOOST_OPENMETHOD_BUILD_EXAMPLES to ON.

Three latent defects came out, two of them visible to only one compiler:

  • A dead double s in both matrix examples, left over from a main that exercised the scalar overloads. Plain -Wunused-variable, from -Wall — invisible purely because the examples had no flags. Rather than drop the variable, the examples now call the methods they declare: matrix * matrix (the dense/diagonal catch-all and the diagonal/diagonal override), scalar * matrix and matrix * scalar. They demonstrate the multiple dispatch they set up, which they previously did not.

  • Three expr bindings shadowing one another along the dynamic_cast chain in slides.cpp. MSVC C4456; GCC and clang need -Wshadow, which is in neither -Wall nor -Wextra. Each is now named for the type it casts to, which reads better regardless.

  • GCC 16 reports -Wsfinae-incomplete on the friendship examples. They name virtual_ptr<Cat> in a friend declaration inside Animal, before Cat is defined — which is the point of the example. Defaulting the registry argument runs the affinity probe, a SFINAE context, so GCC reports that the answer may change once Cat is complete. The library asks the question again under its rechecked tag, where the class has to be complete, and refuses an answer that has changed.

    This warning is pre-existing: it is on by default, brought in by neither -Wall nor -Wextra, so it is already in the reflection job's log today and only -Werror makes it fatal. It is kept visible but non-fatal with -Wno-error=sfinae-incomplete, behind a check_cxx_compiler_flag probe — GCC 13, GCC 15 and clang 22 all reject that option outright, so a version guess would have broken them.

/we4265, which the test flags also carry, is deliberately left off: it fires 29 times in preamble.hpp on detail::deferred_overrider_info, not in example code. That is a separate question, worth its own look since the tests do enable it.

Verification

configuration result
GCC 13 Release, BUILD_EXAMPLES=ON, WARNINGS_AS_ERRORS=ON clean, 197/197
GCC 16 the reflection job's own: -std=c++26 -freflection, Debug, BUILD_SHARED_LIBS=ON clean, 203/203
clang 22 -Wall -Wextra -Werror over all 31 examples clean
MSVC v18 /W4 /WX over all 31 examples clean

Plus the Antora render for the doc commits — the two new cpp: cross-references resolve to real reference pages.

🤖 Generated with Claude Code

https://claude.ai/code/session_011Eo4q4cYTx457W7oaunqsQ

Both examples returned `reinterpret_cast<type_id>(0)` for every type that does
not derive from `Node`. That looks harmless - such types never take part in
dispatch - but `static_type` is also how a *method* is identified, and a
`method<...>` specialization is not derived from `Node`, so it takes that
branch too. `augment_methods` keys `method_map` on the method's type id, so
every method in the registry lands under the same key: they merge into one,
their overriders pool together, nothing dominates, and the first call reaches
`fn_ambiguous`. Neither example's registry has an `error_handler` or an
`output` policy, so it aborts with no message at all.

Each example declares exactly one method, so nothing merges today and the
examples work. It bites the reader who follows the page and adds a second
method, which is the obvious next step.

Demonstrated by adding a `depth` method to each example: before, abort with
exit 134; after, `2 3 + 4 * = 20 depth=3`.

Non-polymorphic types now get the address of a function-local static, which is
what the library's own `static_rtti` policy does, and `dynamic_type` delegates
to `static_type` so the two cannot disagree. The page gains a paragraph on why
the placeholder is a trap, and the sentence promising a "small, dense range"
of ids is narrowed to the classes that take part in dispatch - which is what
`vptr_vector` indexes.

Every other custom `static_type` in the tree - the `static_rtti` and
`named_static_rtti` policies, and the five tests that define one - already
gives non-polymorphic types distinct ids. Only the examples were wrong, which
is why no test caught it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Eo4q4cYTx457W7oaunqsQ
@cppalliance-bot

cppalliance-bot commented Oct 10, 2026 •

Copy link
Copy Markdown

An automated preview of the documentation is available at https://129.openmethod.prtest3.cppalliance.org/libs/openmethod/doc/html/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-10-10 17:14:10 UTC

jll63 and others added 2 commits October 10, 2026 12:42
`call_via_ref` was copied from the first example, where `postfix` writes to
the stream. The second example evaluates instead - the body is
`return value(node)` - so `os` has been unused ever since, and the example
does not compile under -Wall -Wextra -Werror:

    custom_rtti.cpp:118:51: error: unused parameter 'os'
    [-Werror=unused-parameter]

The examples are not built with -Wextra, which is why it went unnoticed.

The function sits outside every doc tag, so nothing renders it;
`performance.adoc` discusses a `call_via_ref` of its own, written inline and
taking the stream, and the first example keeps its two-parameter version.
Both examples now compile clean at -Wall -Wextra -Werror.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Eo4q4cYTx457W7oaunqsQ
There is no Jamfile under doc/modules/ROOT/examples, so b2 never compiles
the examples, and the CMake build - the only check they get - was passing
them no warning flags at all. Mirror test/CMakeLists.txt: the flags go on
under BOOST_OPENMETHOD_WARNINGS_AS_ERRORS, which the C++26 reflection job
sets, and that job builds the examples because it configures the library
as the root project. Add -Wextra, which the test flags lack.

The block sits before the first target, so the options reach shared_libs
and the step-by-step subdirectories too.

Three latent defects came out, two of them only under one compiler:

  - A dead `double s` in both matrix examples (-Wunused-variable, in
    -Wall, invisible only because the examples had no flags), left over
    from a main() that exercised the scalar overloads. Rather than drop
    the variable, call the methods the examples declare: matrix * matrix
    (the dense/diagonal catch-all and the diagonal/diagonal override),
    scalar * matrix, and matrix * scalar. The examples now demonstrate
    the multiple dispatch they set up.

  - Three `expr` bindings shadowing one another along the dynamic_cast
    chain in slides.cpp (MSVC C4456; GCC and clang want -Wshadow, which
    is in neither -Wall nor -Wextra). Named for the type each one casts
    to, which reads better anyway.

  - GCC 16 reports -Wsfinae-incomplete on the friendship examples, which
    name virtual_ptr<Cat> in a friend declaration inside Animal, before
    Cat is defined - the point of the example. Defaulting the registry
    argument runs the affinity probe, a SFINAE context. The library asks
    the question again under its `rechecked` tag, where the class has to
    be complete, and refuses an answer that has changed, so the warning
    is kept visible but non-fatal. It is on by default - neither -Wall
    nor -Wextra brings it in - so this is pre-existing, and already in
    the reflection job's log; only -Werror makes it fatal. Probed rather
    than tied to a version: only GCC 16 knows the option, and naming it
    on GCC 13, GCC 15 or clang is a hard error.

Verified on GCC 13 (Release), GCC 16 with -freflection (the reflection
job's configuration, Debug + shared libs, 203/203 tests), clang 22, and
MSVC v18 /W4 /WX over all 31 examples. /we4265, which the test flags also
carry, is left off: it fires in preamble.hpp, not in the examples.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Eo4q4cYTx457W7oaunqsQ
@jll63
jll63 force-pushed the fix/custom-rtti-example-ids branch from 2fcee3b to 997e6aa Compare October 10, 2026 17:06
@jll63 jll63 changed the title doc: custom RTTI examples conflate methods under one type id doc: fix the custom RTTI examples; build: warning flags on the examples Oct 10, 2026
@codecov

codecov Bot commented Oct 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.34%. Comparing base (f093df7) to head (997e6aa).
⚠️ Report is 2 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           develop     #129   +/-   ##
========================================
  Coverage    92.34%   92.34%           
========================================
  Files           27       27           
  Lines         2234     2234           
  Branches       679      677    -2     
========================================
  Hits          2063     2063           
  Misses         112      112           
  Partials        59       59           

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c846bca...997e6aa. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jll63
jll63 merged commit a426f03 into boostorg:develop Oct 11, 2026
55 of 56 checks passed
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.

2 participants