register classes by reflection in C++26 - #94
Open
jll63 wants to merge 9 commits into
Open
Conversation
|
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-29 19:36:06 UTC |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ 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
... and 77 files with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
jll63
force-pushed
the
feature/reflection-registration
branch
from
August 29, 2026 16:47
6539363 to
095002e
Compare
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
force-pushed
the
feature/reflection-registration
branch
from
August 29, 2026 19:30
c63ca00 to
77996d8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_CLASSESbecomes unnecessary in most cases.
What's new
use_classes_in<^^Namespace, Registry>(core.hpp) — a registrar thatscans 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 macrowrapper. 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_CLASSESbuilds under either standard.policies::explicit_class_registration(preamble.hpp) — opts a registryout, restoring the C++17 behaviour where every class must be registered by
hand. The registry exposes
has_reflected_class_registration. The policy hasno effect if the compiler does not support reflection.
Methods are found through the names that denote them — the
usingdeclarationBOOST_OPENMETHODnow emits, a hand-written one, or any registrar object. Acore-interface method whose
method<...>type is spelled out in full at everyuse, with neither a
usingdeclaration nor an overrider, is named by nothingand is not found; its classes still need
use_classes.Detection is automatic, from
__cpp_impl_reflection— nothing about the C++17build changes.
Tests
test_classes.hppaddsBOOST_OPENMETHOD_TEST_CLASSES, which expands toBOOST_OPENMETHOD_CLASSESin C++17 and to nothing under reflection. Tests thatare not about class registration use it and add a trailing
BOOST_OPENMETHOD_CLASSES_IN(::), so a C++26 run exercises reflection-basedregistration 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_CLASSESand putexplicit_class_registrationin their registry.test_reflection.cppcovers the scan itself.Build
BOOST_OPENMETHOD_ENABLE_REFLECTION=ONprobes for-std=c++26and-std=c++26 -freflection, and applies whichever works per target (not throughCMAKE_CXX_FLAGS— CMake probes the compiler beforeCMAKE_CXX_STANDARDtakes effect, and GCC rejects
-freflectionunder any other standard).<toolset>gcc,<cxxstd>26:<cxxflags>-freflection.reflectionjob 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.