Skip to content

register classes by reflection in C++26 - #94

Open
jll63 wants to merge 17 commits into
boostorg:developfrom
jll63:feature/reflection-registration
Open

register classes by reflection in C++26#94
jll63 wants to merge 17 commits into
boostorg:developfrom
jll63:feature/reflection-registration

Conversation

@jll63

@jll63 jll63 commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #89.

When the compiler supports C++26 reflection (P2996), the library can find the
classes taking part in dispatch on its own, and BOOST_OPENMETHOD_CLASSES
becomes unnecessary in most cases.

What's new

  • use_classes_in<^^Namespace, Registry> (core.hpp) — a registrar that
    scans a namespace by reflection for the classes involved in dispatch, plus
    their bases, and registers them. Virtual and multiple inheritance are
    supported; unlike use_classes, repeated inheritance is not an error here —
    an ambiguous base cannot take part in dispatch, so it is left out.
  • BOOST_OPENMETHOD_CLASSES_IN(NAMESPACE, ...) (macros.hpp) — the macro
    wrapper. Reflection sees only what precedes it, so it goes at the bottom of
    the file. Without reflection it expands to nothing, so a file that also calls
    BOOST_OPENMETHOD_CLASSES builds under either standard.
  • policies::explicit_class_registration (preamble.hpp) — opts a registry
    out, restoring the C++17 behaviour where every class must be registered by
    hand. The registry exposes has_reflected_class_registration. The policy has
    no effect if the compiler does not support reflection.

Methods are found through the names that denote them — the using declaration
BOOST_OPENMETHOD now emits, a hand-written one, or any registrar object. A
core-interface method whose method<...> type is spelled out in full at every
use, with neither a using declaration nor an overrider, is named by nothing
and is not found; its classes still need use_classes.

Detection is automatic, from __cpp_impl_reflection — nothing about the C++17
build changes.

Tests

test_classes.hpp adds BOOST_OPENMETHOD_TEST_CLASSES, which expands to
BOOST_OPENMETHOD_CLASSES in C++17 and to nothing under reflection. Tests that
are not about class registration use it and add a trailing
BOOST_OPENMETHOD_CLASSES_IN(::), so a C++26 run exercises reflection-based
registration across the whole suite: the classes go unregistered and every test
still has to pass. Tests that check what happens when a class is not
registered keep BOOST_OPENMETHOD_CLASSES and put
explicit_class_registration in their registry.

test_reflection.cpp covers the scan itself.

Build

  • CMake: BOOST_OPENMETHOD_ENABLE_REFLECTION=ON probes for -std=c++26 and
    -std=c++26 -freflection, and applies whichever works per target (not through
    CMAKE_CXX_FLAGS — CMake probes the compiler before CMAKE_CXX_STANDARD
    takes effect, and GCC rejects -freflection under any other standard).
  • b2: <toolset>gcc,<cxxstd>26:<cxxflags>-freflection.
  • CI: a new reflection job builds and runs the suite with GCC 16.

Verified locally in both configurations: C++17/clang 153/153, and C++26
reflection (g++-16 -freflection) 148/148.

@cppalliance-bot

cppalliance-bot commented Aug 25, 2026

Copy link
Copy Markdown

An automated preview of the documentation is available at https://94.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-08-31 23:27:18 UTC

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.17355% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.02%. Comparing base (b3bec3b) to head (77996d8).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
include/boost/openmethod/core.hpp 98.07% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop      #94      +/-   ##
===========================================
- Coverage    94.94%   93.02%   -1.93%     
===========================================
  Files           99       22      -77     
  Lines         4373     1634    -2739     
  Branches      2168      504    -1664     
===========================================
- Hits          4152     1520    -2632     
+ Misses         162       66      -96     
+ Partials        59       48      -11     
Files with missing lines Coverage Δ
include/boost/openmethod/default_registry.hpp 100.00% <ø> (ø)
include/boost/openmethod/detail/ostdstream.hpp 96.77% <100.00%> (+0.10%) ⬆️
include/boost/openmethod/detail/static_list.hpp 96.22% <ø> (ø)
include/boost/openmethod/initialize.hpp 92.02% <100.00%> (+0.03%) ⬆️
include/boost/openmethod/inplace_vptr.hpp 100.00% <100.00%> (ø)
include/boost/openmethod/interop/boost_any.hpp 100.00% <ø> (ø)
...e/boost/openmethod/interop/boost_intrusive_ptr.hpp 94.44% <ø> (ø)
...de/boost/openmethod/interop/boost_type_erasure.hpp 100.00% <100.00%> (ø)
include/boost/openmethod/interop/std_any.hpp 100.00% <ø> (ø)
...nclude/boost/openmethod/interop/std_shared_ptr.hpp 94.73% <ø> (ø)
... and 6 more

... and 77 files with indirect coverage changes


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 dd2c86f...77996d8. 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 force-pushed the feature/reflection-registration branch from 6539363 to 095002e Compare August 29, 2026 16:47
jll63 and others added 9 commits August 29, 2026 14:53
Two things the formatter was getting wrong, and the version bump that
one of them needs.

`TemplateNames` - added in clang-format 20 - tells the formatter that
`register_classes<` opens a template argument list rather than a
comparison, so a registration inside a function-like macro call breaks
after the open parenthesis instead of aligning at it.

`PenaltyReturnTypeOnItsOwnLine` goes from 60 to 200, so a declaration
too long for one line breaks its parameter list rather than putting
`auto` on a line of its own:

    static auto initialize(
        const Context& ctx, const std::tuple<Options...>& options) -> void;

rather than

    static auto
    initialize(const Context& ctx, const std::tuple<Options...>& options)
        -> void;

200 is well past the threshold - the layout stops changing above 120 -
and gives what 1000 gives, so the choice is not on a knife edge.

The rest of the diff is the drift from clang-format 18 to 22: trailing
return types and `if constexpr` continuations, mostly. `.clang-format`
now requires clang-format 20 or later, which rejects no key in this
file; 22 reproduces the tree byte for byte.

`register_classes` itself arrives with the reflection branch; naming
it here only teaches the formatter, and costs nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
`BreakConstructorInitializers` and `BreakInheritanceList` go from
BeforeColon to AfterColon, so a member initializer list and a base
clause trail their colon rather than lead it, and their continuations
sit on a flat +4 ladder instead of aligning two columns past the
colon:

    explicit virtual_ptr(std::nullptr_t) :
        vp(detail::box_vptr<use_indirect_vptrs>(detail::null_vptr)),
        obj(nullptr) {

rather than

    explicit virtual_ptr(std::nullptr_t)
        : vp(detail::box_vptr<use_indirect_vptrs>(detail::null_vptr)),
          obj(nullptr) {

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GCC 16 since r16-8246 - the fix for PR124575, "ICE with lifetime extension of
consteval-only" - marks a lifetime-extended temporary of consteval-only type
DECL_EXTERNAL. `vector<meta::info>` is such a type, and the range-for's
__for_range is such a temporary, so the constant evaluator hands every frame of
a recursive consteval call the same object: the inner call destroys the vector
the outer call is still walking.

Both recursive scanners tripped on it, and the whole C++26 leg of the b2 matrix
failed to compile - 37 of 58 test/test_*.cpp, 148 targets:

  bits/stl_vector.h:792:29: error: accessing '<anonymous>' outside its lifetime
  note: declared here
      std::meta::bases_of(type, std::meta::access_context::unchecked())) {

A plain automatic variable is not an extended-ref temporary, so each frame gets
its own. Verified against the exact CI compiler (Ubuntu 16-20260322,
r16-8246): 58/58 compile, where HEAD gave 21/58.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz
boost-clone defaults modules-exclude-paths to `test tests`, so it never scans
our own test/ and cloned neither library. The job then died at CMake generate,
before compiling anything:

  CMake Warning: Library 'test' given in BOOST_INCLUDE_LIBRARIES has not been found.
  CMake Error at test/CMakeLists.txt:96: links to Boost::unit_test_framework
  but the target was not found.

The action unions an explicit `modules` with the scan results and resolves
their own dependencies afterwards, so naming the two is enough. The Antora jobs
use the same action and are unaffected: they never configure a test target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz
r16-8246 is only the snapshot Boost.CI happens to install; the regression
starts at d51a78f7a8f3. Isolated by building cc1plus at that commit and at its
parent, 95d2eb6fa073, which differ by exactly the four-line DECL_EXTERNAL hunk
in set_up_extended_ref_temp: the parent accepts the scan, the commit rejects
it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz
The bug is GCC PR124645/PR124646, reported 2026-03-26 and fixed on 2026-04-02
by r16-8430: r16-8235 set DECL_EXTERNAL on the lifetime-extended temporary for
both the at_function_scope_p and !at_function_scope_p cases, where only the
latter was intended.

Naming the PRs and the fixing revision is more use to a future reader than the
introducing SHA alone - it says when the workaround can go. Not yet: Ubuntu
26.04, which Boost.CI uses for the C++26 leg, ships 16-20260322 (r16-8246),
between the two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz
`use_classes_in<^^ns, Registry>` took one namespace and one registry.
`register_classes<...>` takes four groups of non-type arguments, each
optional, in enforced order - namespaces to scan, classes to register,
one `register_classes_opts` value, registries (as reflections now,
since the pack is `auto...`) - and registers in every listed registry.

Listed classes are dispatch roots: registered whether a method
dispatches on them or not, along with the scanned classes deriving
from them. Classes with no namespace disable the scan - exactly the
listed classes are registered, their inheritance lattice read from
reflection and flattened over unlisted intermediates. Scans now skip
`boost` as well as `std` - worth ~0.2s per TU on a scan of `^^::` in a
Boost.Test TU, and reversible with `scan_boost`/`scan_std`;
`no_recurse` keeps a scan out of nested namespaces. The options live
in a namespace rather than an enum class so a using-directive can make
the terse spellings available.

With no namespace and no class at all, the enclosing namespace is
scanned. Three routes lead there, all resting on P2996's call-site
evaluation of `access_context::current()`: the default template
argument covers `register_classes<>`; a `detail::scope_marker` that
`BOOST_OPENMETHOD_REGISTER_CLASSES` always prepends covers every macro
form, registry-only and options-only included; and the public
`current_namespace()` helper covers bare-template argument lists,
which the template itself cannot capture - a static_assert points
there. The macro no longer pastes `^^`; the caller writes it.

The two new compile-fail tests produce the expected diagnostic via
`#error` when reflection is off, so they pass under every
configuration with no build-file changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
@jll63
jll63 force-pushed the feature/reflection-registration branch from c63ca00 to 77996d8 Compare August 29, 2026 19:30
jll63 and others added 8 commits August 30, 2026 02:03
MrDocs' front-end does not implement P2996, so the reference build runs at
`-D CMAKE_CXX_STANDARD=20` and sees `BOOST_OPENMETHOD_HAS_REFLECTION` as 0:
every `#if BOOST_OPENMETHOD_HAS_REFLECTION` block is invisible to it, and the
whole C++26 reflection API was missing from the reference.

`register_classes` and `current_namespace` are now restated as documentation
stubs in an `#elif defined(__MRDOCS__)` branch beside the real declarations,
following `inplace_vptr_derived`: each doc comment exists exactly once, on the
stub, and the real declaration carries only a `@see`. `detail/reflection.hpp`
forward-declares `std::meta::info` and `std::meta::access_context` under
`#ifdef __MRDOCS__` so `current_namespace` can render its exact signature;
`include-symbols` keeps them out of the pages. The `register_classes` stub is
`template<auto... Args>` where the real one splits `First`/`Rest` - the split
exists only because a pack cannot carry a default argument, and MrDocs prints a
default argument as raw source text, so the real spelling would leak `detail::`.
`register_classes_opts` needs no stub at all: it is plain C++17, so its guard is
widened to `|| defined(__MRDOCS__)` and MrDocs reads the real definition.

`BOOST_OPENMETHOD_REGISTER_CLASSES` was not extracted either, though for a
different reason: its doc comment sat above the `#if`, separated by preprocessor
directives from the `#else` `#define` that MrDocs compiles, so nothing attached
it. `ref_macros.adoc` has linked to that page all along, and the link rendered as
a literal `href="#reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc"`. Moving the
comment down to the fallback definition produces the page.

Rendering those comments for the first time exposed three markup traps, none of
which asciidoctor or MrDocs reports:

* a line starting with `- ` becomes a list item, so the em-dash before "pass it
  explicitly" split the paragraph and opened a stray bullet;
* `@ref` inside `**bold**` breaks the span, leaving literal asterisks - the
  options bullet now bolds plain text, like its three siblings;
* an inline `` `^^::` `` loses both carets and renders as `::`. Escaping the
  first, `` `\^^::` ``, comes through intact. Found by building the page with
  eight candidate spellings; only that one survives.

`registries_and_policies.adoc` pointed at `xref:reference:use_classes.adoc`,
which resolves only for macros - MrDocs puts those at the top level and
namespace-scoped symbols under `reference/boost/openmethod/`. It is now
`cpp:use_classes[]`, and the prose that names the new symbols links to them the
same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
`register_classes` took a flat list of reflections, sorted by kind and required
to appear in canonical order. The arguments are now groups:

    register_classes<{^^zoo, ^^pets}, {^^Animal}, {^^r1, ^^r2}>

A group holds one kind of reflection - the namespaces to scan, the classes to
register, the registries to register them in, in that order - and a group of
one needs no braces, so `register_classes<^^zoo, ^^r1>` still means what it
did. Groups are still identified by what they hold rather than by position, so
any of them may be left out: `register_classes<{^^r1}>` names only registries.

The argument type, `detail::reflection_group`, is a class-type non-type
template parameter with a deduced-class-type placeholder, deducing its length
from the braced template argument through CTAD (P2308). A pack of them replaces
the old `template<auto...>`; `auto` cannot be deduced from a braced-init-list
("unable to deduce 'auto' from '{1, 2}'"). Its converting constructor is what
lets a lone reflection stand for a group of one, `consteval` because
`std::meta::info` is a consteval-only type, and constrained so that it cannot
be picked over the copy constructor. None of this is P2996: with `int` in place
of `std::meta::info` the same shape compiles clean on gcc 13, 15 and 16 and on
clang 18 and 22 at `-std=c++20` with `-pedantic-errors`, and on MSVC v18 at
`/std:c++20` with `/W4 /permissive-`.

The options are gone with `register_classes_opts`, `has_opt` and the `opts`
parameter threaded through the scan. A scan now always recurses, and never
descends into `std` or `boost`; a namespace *listed* explicitly is scanned
whatever it is, which is how a class in either gets registered, and was already
true before. `no_recurse` bought little for a mechanism whose cost is dominated
by the namespaces it is pointed at.

With no namespace group the scan covers `^^::`, where it used to cover the
namespace enclosing the registrar. Listing classes no longer turns the scan
off: they are extra roots, and the classes deriving from them are registered
too. `current_namespace()` survives as the way to narrow a scan to one
namespace, which is the only thing that still needs it.

That default is what removes `detail::scope_marker` and the `scope` member each
group used to carry - a default member initializer calling
`access_context::current()`, evaluated at the initialization site. Without it
`reflection_group` needs no aggregate-ness, which is what made the converting
constructor above possible, and `register_classes` needs no `First`/`Rest`
split: an empty pack already covers `register_classes<>`. Nothing separates
`BOOST_OPENMETHOD_REGISTER_CLASSES` from the bare template now except its
expanding to nothing without reflection.

Three static_asserts diagnose an argument list, ordered so that one mistake
yields one message: an unknown reflection, then a group mixing two kinds, then
groups out of order. `compile_fail_reflection_mixed_group.cpp` covers the
second; `compile_fail_reflection_no_scan_source.cpp` goes, as a registry on its
own is now a scan of `^^::` rather than an error.

`.clang-format` loses `TemplateNames: register_classes`. It was added so that
the flat spelling parsed as a template-id, and it is what turns a braced
argument into `{^^a }`; without it both spellings come out right, and
clang-format 18 can read the file again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
Three defects in what `register_classes` gathers, each of them silent, and one
piece of dead code.

**Classes nested in classes were never found.** `scan_namespace` recursed into
nested namespaces only, so `struct Outer { struct Inner : Animal {}; };` was
skipped - and the reference promises "every class in the scanned namespaces
that derives from one of them". An `Inner` dispatched as `Animal&` got
`unknown_class` under `runtime_checks`, and an out-of-range vptr lookup
without them. It is now `scan_scope`, and walks a class as it walks a
namespace. Recursion enters only a class the scope actually *declares*, which
`parent_of` answers: following an alias instead would walk whatever it points
at, and a member `using` for `std::string` would drag the whole of
`basic_string` in behind it.

**cv-qualification was stripped asymmetrically.** `virtual_classes` entries got
`remove_cv`, but the types the scan collected were only `dealias`'d, so a
namespace-scope `using CDog = const Dog` put `const Dog` in the list as an
entry distinct from `Dog` - a second lattice node with its own `static_vptr`,
perfect-hash slot and dispatch table row, for a spelling nothing else uses.
The scan now applies `remove_cv` too.

**Ambiguous bases were documented as left out, but nothing left them out.**
`collect_reflected_bases` only de-duplicated, so with `Left : Animal`,
`Right : Animal`, `Repeated : Left, Right`, `Repeated` was registered under
`Animal` - consuming a class node, a hash slot and dispatch table cells for a
class that can never be passed, as the conversion is ill-formed.
`collect_dispatchable_bases` now drops such a base. Ambiguity is decided by
`is_convertible_type`, which is exact - a virtual base is one subobject however
many paths reach it - but instantiates a template, so it is asked only about
the bases the walk arrived at more than once. A hierarchy without repeated
inheritance instantiates nothing, which is what the cost note in
`reflected_registered_classes_info` asks for.

`reflected_bases_info` and `reflected_bases` were used by nothing in
`include/`, `test/` or `doc/`, and go, along with the mp11 include that only
they needed.

The tests cover the two behaviours that had none: a nested-class fixture
(public and private nesting, plain and cv aliases) and an assertion that the
repeated-inheritance class is absent from the registry. Both were checked
against the unfixed code - the first aborts, the second fails its `BOOST_TEST`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
`test/Jamfile` applied `-freflection` on `<toolset>gcc,<cxxstd>26`, with no
condition on the compiler version. Every GCC older than 16 accepts
`-std=c++26` but rejects the flag - `error: unrecognized command line option` -
so `b2 toolset=gcc-14 cxxstd=26`, or any super-project job that adds
`cxxstd=26` to a gcc-14 or gcc-15 leg, failed to build the whole test
directory, where it had worked before.

Pinning the condition to `<toolset>gcc-16` would fix that (b2 registers the
major version, so it does match 16.0.1), but it fails in the other direction:
a later GCC that no longer needs the flag, or needs it under a different
spelling, would silently lose reflection coverage. So this does what the CMake
side already does and asks the compiler. `config//has_reflection` compiles a
`<meta>` snippet with `-freflection` under whatever standard the build request
carries, and `check-target-builds` adds the flag only when that succeeds.

The probe is its own project rather than a target under `test/` because a b2
subproject inherits its parent's requirements: declared there, the check
target would carry the very conditional it is being consulted for, and asking
for it would ask for itself - b2 reports it as an unresolvable reference to
`config//has_reflection` from `test/config`.

Verified against the three cases: gcc 16 at `cxxstd=26` answers yes and puts
the flag on the command line, gcc 16 at `cxxstd=17` answers no, and clang at
`cxxstd=26` answers no. gcc 13 does not accept `-std=c++26` at all, so the
`[ requires cxx17_... ]` checks skip the directory before this is reached.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
Three files traded `BOOST_OPENMETHOD_CLASSES` for the scan on the wrong
reasoning, in both directions.

`test_virtual_ptr_value_semantics.cpp` registers an extra class between two
`initialize` calls, "to make sure dispatch data is not re-constructed in the
same place with the same values". That `Cat` is declared inside the test case
body, and the file has no `BOOST_OPENMETHOD_REGISTER_CLASSES()`, so under
C++26 the macro expanded to nothing and no scan could see a function-local
class: nothing was added between the two calls, and the following
`p.vptr() != static_vptr<Dog>` was left waiting for an identical rebuild to
land at a different address. It goes back to registering by hand, as the
sibling registration in the header already does.

`test_dispatch_multi.cpp` and `test_namespaces.cpp` went the other way. Both
kept a hand registration for `dense_matrix` and `delphinus::Dolphin` on the
grounds that "reflection has no way of finding it". Both are namespace-scope
classes deriving publicly from a class the methods in the file dispatch on, so
the scan finds them exactly like the others; the registration was redundant and
the reason given would mislead anyone copying the pattern. They are folded back
into the scanned list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
…cipe

The snippet header included <boost/openmethod/default_registry.hpp> to get at
`default_registry` before defining the override macro - the pre-core-header
idiom the guidelines drop, and the one thing the documented hand-check looks
for:

    git grep -n "openmethod/preamble.hpp\|openmethod/default_registry.hpp" -- doc test

It now has the shape test_capture_errors.hpp uses: forward-declare the
registry, define BOOST_OPENMETHOD_DEFAULT_REGISTRY, include
<boost/openmethod.hpp>, then define the registry. The four error snippets that
include it are unchanged - they include the library themselves afterwards, and
the include guard makes that a no-op - and the check now returns prose only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
`register_classes` documents two ways a namespace member can name a method
specialization: a type alias, and a registrar variable whose type is nested in
the specialization. Only the first was tested in isolation (`method_only`);
every fixture with a registrar also had an alias standing next to it, so a
regression in the variable branch of `specialization_named_by` would have been
masked by the alias channel in every existing test.

`registrar_only` spells the method type out in full everywhere and registers a
hand-written overrider with `method<...>::override<...>`: that registrar is the
only namespace member whose type involves the specialization, so the test
passes only through the variable branch. The alias the test case itself uses to
call the method is function-local, which the scan - namespace members only -
never sees.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
`BOOST_OPENMETHOD_REGISTER_CLASSES()` expands to `register_classes<>`, which
scans the global namespace and names nothing. That is the documented common
case, and the shape some 35 converted tests use - but the empty pack broke two
things in `detail`, both of them invisible under the flags this suite happens
to build with.

`register_classes_groups_are_ordered` formed
`register_classes_kind kinds[] = {register_classes_group_kind<Groups>()...}`,
which is a zero-size array when the pack is empty. That is not standard C++:
GCC rejects it outright under plain `-Wpedantic`, clang under
`-pedantic-errors`. It compiled here only because the suite builds with
`-Wall -Wextra -Werror` and no `-pedantic`, so any consumer that adds it got a
hard error on the library's primary entry point. The empty case is now taken
before the array is formed.

`register_classes_items` reads its `kind` parameter only inside the fold over
`Groups`, which expands to nothing when the pack is empty, so the parameter is
never read and GCC 16 reports it under `-Wunused-but-set-parameter`. That one
was not theoretical: it broke the b2 C++26 leg outright - every test using
`BOOST_OPENMETHOD_REGISTER_CLASSES()` failed to compile under
`toolset=gcc cxxstd=26`, which is `-Werror` here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt
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.

auto-register classes in C++26

2 participants