registry affinity: a registry parameter for virtual_, and class lists that follow it - #113
Merged
Merged
Conversation
|
An automated preview of the documentation is available at https://113.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-09-16 22:12:13 UTC |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #113 +/- ##
===========================================
- Coverage 93.51% 93.25% -0.26%
===========================================
Files 22 22
Lines 1695 1706 +11
Branches 504 509 +5
===========================================
+ Hits 1585 1591 +6
- Misses 66 71 +5
Partials 44 44
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
jll63
force-pushed
the
feature/virtual-registry-param
branch
2 times, most recently
from
September 16, 2026 14:18
80fce95 to
80823f5
Compare
… that follow it Two loose ends from boostorg#96, and one rule for both. `virtual_ptr<C, S>` carries a registry, but the scan that picks a method's registry read the *class's* declared affinity instead of what the parameter carried. `BOOST_OPENMETHOD(f, (virtual_ptr<B, a_registry>), void)` was therefore an error: the scan took `default_registry` from `B`, then rejected the parameter for carrying `a_registry`. And `virtual_<T>` could not name a registry at all. `virtual_` now takes one - `virtual_<T, S>` - declared in `preamble.hpp` and defaulted in `core.hpp`, where the affinity is known; C++ merges default template arguments across declarations. Every virtual parameter then either *carries* a registry or *adopts* the method's: virtual_<T> the class's declared affinity; adopts if it declares none virtual_<T, S> S virtual_ptr<C> C's affinity, else default_registry virtual_ptr<C, S> S `virtual_ptr` never adopts - it is a type in its own right and names a registry whether or not the class declares an affinity. `virtual_` can, because it appears only in a method signature, and that is what lets a method mix a class that has an affinity with one that has not. A method that names a registry requires every carrier to carry it; the adopters go along. A method that names none requires the carriers to agree, and takes their registry. So `method<foo, void(virtual_<A&, default_registry>)>` is `method<foo, void(virtual_<A&, default_registry>), default_registry>`. Nothing that compiles today changes meaning. Two spellings gain one: `virtual_ptr<B, a_registry>` and `virtual_ptr<A, default_registry>` as the sole virtual parameter now decide the method's registry. The sentinel for "carries nothing" is `void`, replacing the `default_affinity` struct - as `inplace_vptr`'s private catch-all spelled it before boostorg#96 unified the hook. It keeps the mangled names short, now that it appears in every `virtual_<T, ...>` of every `method<...>`. `registry_affinity` still maps it to BOOST_OPENMETHOD_DEFAULT_REGISTRY; only `::declared` sees it raw. A member typedef `using boost_openmethod_registry = void;` therefore means "declares nothing". `use_classes` and `BOOST_OPENMETHOD_CLASSES` follow the affinities too, where they used to take the registry from the last element or else the macro default, whatever the classes said - so registering a class that declares an affinity, without naming its registry, put the class in one registry and its methods in another. That was a run-time failure, not a compile error: BOOST_OPENMETHOD_CLASSES(Animal, Dog); // Animal declares zoo_registry BOOST_OPENMETHOD(speak, (virtual_<const Animal&>), std::string); initialize<zoo_registry>(); speak(dog); // before: unknown class Animal, abort; after: "bark" A class list is stricter than a parameter list, because it has no parameter to adopt from and no spelling of its own to disambiguate with. A registry listed last wins, and then a class declaring another one is an error while one declaring nothing goes along. Listing none, the classes must be unanimous - all declaring the same registry, or none declaring one, in which case they are registered into BOOST_OPENMETHOD_DEFAULT_REGISTRY. Mixing a declaring class with a non-declaring one is an error, where the same mixture among a method's parameters is fine. The C++26 reflection registrar keeps the macro default: its groups may name a namespace, whose classes are only known during the scan that the choice of registry feeds. compile_fail_adl_registry_parameter_registry.cpp goes: its premise - that a registry spelled on a parameter contradicts the method - is what this reverses. Two markers move to the fold's new wording. Four compile-fail tests are added, one per new diagnosis, and the carries/adopts table and the six class-list outcomes are pinned with static_asserts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQG6CbE4o2agseE7bDVzHS
jll63
force-pushed
the
feature/virtual-registry-param
branch
from
September 16, 2026 22:05
80823f5 to
219e921
Compare
jll63
added a commit
that referenced
this pull request
Sep 17, 2026
#113 gave `virtual_` a registry parameter, so that a method can be declared without naming a registry and a parameter can name one. The `validate_method_parameter` specializations in the `any` and type_erasure interop headers were left spelling `virtual_<T>`, which after the change matches only the defaulted second argument. A method declared `virtual_<const std::any&, R>` - valid, and what the registry affinity machinery produces when a parameter names its registry - fell through to the primary template instead: error: virtual_<> parameter is not a polymorphic class and no boost_openmethod_vptr is applicable Give the registry `virtual_` carries its own template parameter, as the specializations in core.hpp do. The rejecting one, for an owning `type_erasure::any` passed by value, is fixed the same way, so that it keeps reporting "an owning type_erasure::any must be passed by reference" rather than the vague message above. The three dispatch tests assert the accepting spellings, in each form the header covers. Claude-Session: https://claude.ai/code/session_018FzFtVWNk6d4Wut5SboCZC Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
jll63
added a commit
to jll63/openmethod
that referenced
this pull request
Sep 18, 2026
…g#116) boostorg#113 gave `virtual_` a registry parameter, so that a method can be declared without naming a registry and a parameter can name one. The `validate_method_parameter` specializations in the `any` and type_erasure interop headers were left spelling `virtual_<T>`, which after the change matches only the defaulted second argument. A method declared `virtual_<const std::any&, R>` - valid, and what the registry affinity machinery produces when a parameter names its registry - fell through to the primary template instead: error: virtual_<> parameter is not a polymorphic class and no boost_openmethod_vptr is applicable Give the registry `virtual_` carries its own template parameter, as the specializations in core.hpp do. The rejecting one, for an owning `type_erasure::any` passed by value, is fixed the same way, so that it keeps reporting "an owning type_erasure::any must be passed by reference" rather than the vague message above. The three dispatch tests assert the accepting spellings, in each form the header covers. Claude-Session: https://claude.ai/code/session_018FzFtVWNk6d4Wut5SboCZC Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
jll63
added a commit
to jll63/openmethod
that referenced
this pull request
Sep 19, 2026
Both were falsified by changes that did not update the prose around them. boostorg#113 collapsed the "Two things deliberately do not participate" bullets into one sentence, because `use_classes` had just started participating - but then named two things under "One thing": the interop headers and the C++26 `register_classes`. Restore the bullet form for the two that are left. The same sentence says the `any` and `type_erasure` interop headers "are untouched". boostorg#116 touched all three of them, and had to: boostorg#113 gave `virtual_` a registry parameter, and their `validate_method_parameter` specializations still spelled `virtual_<T>`, which after the change matches only the defaulted argument. What survives is the affinity claim, which is the point of the paragraph - a `virtual_any` contributes none. Say that, and record why a specialization there cannot go back to the bare spelling. The PCH paragraph names `test_capture_errors.hpp` as the only header that carries the override on a test's behalf. `test/CMakeLists.txt` has scanned for `test_checked_registry.hpp` as well since boostorg#93; the sentence three lines above, "do not add a fourth marker", already counts three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jll63
added a commit
to jll63/openmethod
that referenced
this pull request
Sep 19, 2026
Both were falsified by changes that did not update the prose around them. boostorg#113 collapsed the "Two things deliberately do not participate" bullets into one sentence, because `use_classes` had just started participating - but then named two things under "One thing": the interop headers and the C++26 `register_classes`. Restore the bullet form for the two that are left. The same sentence says the `any` and `type_erasure` interop headers "are untouched". boostorg#116 touched all three of them, and had to: boostorg#113 gave `virtual_` a registry parameter, and their `validate_method_parameter` specializations still spelled `virtual_<T>`, which after the change matches only the defaulted argument. What survives is the affinity claim, which is the point of the paragraph - a `virtual_any` contributes none. Say that, and record why a specialization there cannot go back to the bare spelling. The PCH paragraph names `test_capture_errors.hpp` as the only header that carries the override on a test's behalf. `test/CMakeLists.txt` has scanned for `test_checked_registry.hpp` as well since boostorg#93; the sentence three lines above, "do not add a fourth marker", already counts three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jll63
added a commit
that referenced
this pull request
Sep 19, 2026
…e notes (#118) * doc: correct two stale notes in CLAUDE.md's registry-affinity section Both were falsified by changes that did not update the prose around them. #113 collapsed the "Two things deliberately do not participate" bullets into one sentence, because `use_classes` had just started participating - but then named two things under "One thing": the interop headers and the C++26 `register_classes`. Restore the bullet form for the two that are left. The same sentence says the `any` and `type_erasure` interop headers "are untouched". #116 touched all three of them, and had to: #113 gave `virtual_` a registry parameter, and their `validate_method_parameter` specializations still spelled `virtual_<T>`, which after the change matches only the defaulted argument. What survives is the affinity claim, which is the point of the paragraph - a `virtual_any` contributes none. Say that, and record why a specialization there cannot go back to the bare spelling. The PCH paragraph names `test_capture_errors.hpp` as the only header that carries the override on a test's behalf. `test/CMakeLists.txt` has scanned for `test_checked_registry.hpp` as well since #93; the sentence three lines above, "do not add a fourth marker", already counts three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * doc: state the rule against committing build output #103 merged 415 files of b2 output - 613 MiB expanded, 94 MiB in the pack, on a repository of about 3 MB - days after #108 added the `bin/` ignore rule intended to prevent it. The branch was cut before that rule landed, and an ignore rule does not apply to a path that is already tracked, so the merge carried them in. Nothing in the build or the review catches this, and #117 could only untrack them: the superproject pins this library by SHA and its bot bumps the pin within minutes of every merge, so rewriting `develop` orphans commits `boostorg/boost` already points at - and would not even remove the blobs, which stay reachable through `refs/pull/<n>/head`. Write the rule down where the workflow is: stage named paths, never `git add <dir>`, and ask before committing a build artefact or any file over 1MB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
(Written by Claude Code, on behalf of @jll63.)
Two loose ends from #96, settled by one rule.
virtual_ptr<C, S>carries a registry, but the scan that picks a method'sregistry read the class's declared affinity instead of what the parameter
carried, so
BOOST_OPENMETHOD(f, (virtual_ptr<B, a_registry>), void)was anerror: the scan took
default_registryfromB, then rejected the parameterfor carrying
a_registry. Andvirtual_<T>could not name a registry at all.Every virtual parameter carries a registry, or adopts
virtual_takes a registry parameter — declared inpreamble.hpp, defaulted incore.hppwhere the affinity is known, since C++ merges default templatearguments across declarations.
virtual_<T>virtual_<T, S>Svirtual_ptr<C>C's affinity, elsedefault_registryvirtual_ptr<C, S>Svirtual_ptrnever adopts — it is a type in its own right and names a registrywhether or not its class declares an affinity.
virtual_can, because itappears only in a method signature, and that is what lets a method mix a class
that has an affinity with one that has not.
A method that names a registry requires every carrier to carry it, and the
adopters go along. A method that names none requires the carriers to agree, and
takes their registry. So
method<foo, void(virtual_<A&, default_registry>)>ismethod<foo, void(virtual_<A&, default_registry>), default_registry>.With
Adeclaringa_registryandBdeclaring nothing:, a_registry, default_registryvirtual_<A&>a_registryvirtual_<B&>default_registryvirtual_<B&, a_registry>a_registryvirtual_ptr<A>a_registryvirtual_ptr<B>default_registryvirtual_ptr<B, a_registry>a_registry(was an error)virtual_ptr<A, default_registry>default_registry(was an error)Nothing that compiles today changes meaning; the two marked rows gain one.
The sentinel for "carries nothing" is
void, replacing thedefault_affinitystruct — as
inplace_vptr's private catch-all spelled it before #96 unified thehook. It keeps the mangled names short, now that it appears in every
virtual_<T, …>of everymethod<…>.registry_affinitystill maps it toBOOST_OPENMETHOD_DEFAULT_REGISTRY; only::declaredsees it raw. A membertypedef
using boost_openmethod_registry = void;therefore means "declaresnothing".
Class lists follow the affinities too
use_classestook the registry from the last element, or else the macrodefault, whatever the classes said — so registering a class that declares an
affinity, without naming its registry, put the class in one registry and its
methods in another. Not a compile error, a crash:
A class list is stricter than a parameter list, because it has no parameter to
adopt from and no spelling of its own to disambiguate with:
use_classes<A, A1>a_registry—A1inherits the declarationuse_classes<B>default_registryuse_classes<A, A1, B, a_registry>a_registry—Bdeclares nothing, so it goes alonguse_classes<A, B>use_classes<A>a_registry(wasdefault_registry, silently)use_classes<A, default_registry>Mixing a declaring class with a non-declaring one is an error, where the same
mixture among a method's parameters is fine. The C++26 reflection registrar
keeps the macro default: its groups may name a namespace, whose classes are
only known during the scan that the choice of registry feeds.
Tests
compile_fail_adl_registry_parameter_registry.cppis removed — its premise,that a registry spelled on a parameter contradicts the method, is what this
reverses. Two markers move to the fold's new wording. Four compile-fail tests
are added, one per new diagnosis, and the carries/adopts table and the six
class-list outcomes above are pinned with
static_asserts.189/189 under gcc 13.3 Debug with warnings as errors, 195/195 under gcc 16 with
C++26 reflection and
BUILD_SHARED_LIBS=ON, and every compile-fail diagnosticwas matched under clang 18.1 as well.
🤖 Generated with Claude Code
https://claude.ai/code/session_01RQG6CbE4o2agseE7bDVzHS