classes declare the registry they belong to - #96
Merged
Merged
Conversation
|
An automated preview of the documentation is available at https://96.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-15 01:51:55 UTC |
jll63
force-pushed
the
feature/adl-default-registry
branch
from
September 2, 2026 13:50
60a423f to
0ef01ba
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #96 +/- ##
===========================================
- 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/adl-default-registry
branch
from
September 2, 2026 22:58
0ef01ba to
55b4446
Compare
jll63
force-pushed
the
feature/adl-default-registry
branch
2 times, most recently
from
September 10, 2026 23:52
55b4446 to
ef84783
Compare
A class can now name the registry it belongs to, once, next to itself:
class Animal {
friend auto boost_openmethod_registry(Animal*) -> zoo_registry;
};
The class then has an *affinity* for that registry, inherited by its derived
classes, and everything that mentions it finds it: `virtual_ptr`, its deduction
guides, `final_virtual_ptr`, the smart pointer aliases and factories, and any
method that takes the class as a virtual parameter. A method declared without a
registry argument takes the affinity its virtual parameters agree on.
`inplace_vptr.hpp` already had this hook, privately, returning `void` to mean
"no customization". Making the catch-all return BOOST_OPENMETHOD_DEFAULT_REGISTRY
instead lets it serve as a default template argument directly, and makes
backward compatibility structural: with no overload anywhere, every construct
resolves to what it resolved to before. `.text` for test_virtual_ptr_dispatch.cpp
is byte-identical, and the 530 defined symbols are unchanged.
Having *no* affinity is not the same as an affinity for the default registry -
only the former yields. That is what lets a method mix a class that has one with
a class that has none, so a first affinity does not cascade errors through a
codebase. Two conflicting affinities are diagnosed, as is a registry named on a
method that contradicts one of its parameters.
Deliberately out of scope, and documented as such: `virtual_` keeps its single
template parameter; `use_classes` still registers into the macro default unless
a registry is listed last; the `any` and `type_erasure` interop headers are
untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1
compile_fail_adl_registry_declared_mismatch only declared the method. The "registry mismatch" static_assert is a fold in `method`'s class body, so it fires when the class is instantiated - and declaring the method is not enough. gcc and clang instantiate it anyway through the static registrar; MSVC does not, so the file compiled and the compile-fail test failed on every Windows job. Call the method in main(), the way compile_fail_virtual_ptr_different_registries.cpp already does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1
"Affinity" is the term the documentation uses for the relation, so the query that reads it back should carry it too. `default_registry_of` also read badly where it mattered most: "Registry defaults to the default registry of Class". `affine_registry` was the other candidate and is worse - it predicates "affine" of the registry, when it is the class that has the affinity, and "affine" reads as affine geometry in a library whose flagship example dispatches on matrix types. The concept is adjusted to match the name. Every class now *has* a registry affinity: a declared one if it declares `boost_openmethod_registry`, the default affinity otherwise. A declared affinity wins over a default one, which is the same rule as before - a method may mix a class that declares an affinity with one that does not - but stated without the awkward "no affinity, which is not the same as an affinity for the default registry". It also removes a corner that framing had: a class declared explicitly to the default registry is no longer a special case, it simply has the default affinity like any other. detail::affinity_of becomes declared_affinity, and no_affinity becomes default_affinity, so the internals read the same way as the prose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1
develop adopted clang-format 22 in 6b02978; reflow the files this branch touches to match, so the diff carries no formatting noise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1
jll63
force-pushed
the
feature/adl-default-registry
branch
from
September 15, 2026 01:36
ef84783 to
cb84d19
Compare
…ered Review of this branch turned up five ways `registry_affinity` gives a wrong or unchecked answer, each reproduced on gcc 13, clang 18 and MSVC. **Memoization.** `registry_affinity<Class>` is a class template specialization: asked once, then remembered. A class mentioned before it is complete - a `virtual_ptr<Cat>` named after `class Cat;`, or `virtual_ptr<Node>` as a member of `Node` - is asked before its base classes, or a declaration further down its body, can be seen. The answer, the default registry, then stuck for the whole translation unit: a method over the class landed in one registry while `BOOST_OPENMETHOD_CLASSES(..., zoo_registry)` registered it in another, and two translation units that completed the class in different orders saw two `virtual_ptr<Cat>` types - `nm` showed `U feed(virtual_ptr<Dog, default_registry>)` in one object against `T feed(virtual_ptr<Dog, zoo_registry>)` in the other. The doc said the opposite: "Being part of the class, a hidden friend cannot be late." Refusing to answer an incomplete class is not open to us: `virtual_ptr<Node> next;` in a plain linked structure declares no affinity, needs none, and has worked since boostorg#109. So every question now carries a tag. The `asked` one builds types, as before; `check_affinity<Class>` puts the same question again under `rechecked` where the class must be complete anyway - a virtual parameter, in `validate_method_parameter`, and a registration, in `use_class_aux` - and refuses an answer that has changed. Declaring nothing stays silent; declaring too late is an error, reported where the wrong registry would do its damage rather than at the mention, where nothing is wrong yet. **The anchor.** `registry_anchor` unwrapped anything with a nested `element_type`, so a polymorphic class that happens to define one - a matrix, a buffer - lost its own declared affinity; an `inplace_vptr` class with one registered into the wrong registry and stored that registry's null `static_vptr` in the object. A smart pointer is now a type `virtual_traits` is specialized for: `detail::virtual_type<T, macro_default_registry>`, no `virtual_type` in the tree depending on the registry. **The return type** was taken verbatim: `-> const zoo_registry` yielded a distinct registry, with its own `registry_state`, disjoint from the one the classes were registered in; `-> int` failed at the first `sizeof(virtual_ptr<Animal>)`, far from the declaration. It is stripped of cv-qualifiers and checked with `is_registry`. **The sentinel.** The catch-all returned `BOOST_OPENMETHOD_DEFAULT_REGISTRY` itself, so "no opinion" had to be recovered by comparing with the macro. An affinity explicitly declared for the default registry was therefore treated as none and yielded in a mixed method, and a registry spelled on a `virtual_ptr` parameter counted as a declared affinity - so a method over `virtual_ptr<Cat, other_registry>` naming no registry, formerly the `registry mismatch` error, silently landed in `other_registry`. The catch-all now returns `detail::default_affinity`. `declared` is the raw answer and constrains a method; `registry_affinity` maps the sentinel to the macro default and stays a query that always answers a registry. A `virtual_ptr` parameter contributes its class's affinity, never the registry it spells. **The mismatch check** covered only a by-value `virtual_ptr`: `virtual_<const Animal&>` and `const virtual_ptr<Animal>&` contradictions compiled clean. All four shapes are checked now. Along the way, a class can declare its affinity with `using boost_openmethod_registry = R;`. Member lookup finds it: inherited, hidden by a derived class's own, ambiguous between two bases that disagree. It takes precedence over the overload, and being visible from the point it is declared, it is the spelling for a class that mentions `virtual_ptr` of itself in its own body - which has already decided to be openmethod-aware, so the typedef intrudes no further. `inplace_vptr_base` provides it. It also sidesteps the drawbacks of a free function found only by ADL: a wrong namespace, a translation unit that does not see it, a function template that outranks it. Nine compile-fail tests, one per diagnosis. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQG6CbE4o2agseE7bDVzHS
jll63
force-pushed
the
feature/adl-default-registry
branch
from
September 15, 2026 01:47
cb84d19 to
ab876ed
Compare
This was referenced Sep 15, 2026
jll63
added a commit
that referenced
this pull request
Sep 15, 2026
…thrice (#111) Follow-ups from the review of #96, all mechanical. The reference still described the pre-affinity default in five places: the `BOOST_OPENMETHOD_DEFAULT_REGISTRY` page ("the default value for the Registry template parameter of method, use_classes, virtual_ptr, and all the constructs that take a registry" - only `use_classes` is still true), the `virtual_ptr` class, the one-argument `final_virtual_ptr`, and the `@note` on each of `BOOST_OPENMETHOD` and `BOOST_OPENMETHOD_CLASSES`. Each now says what the construct actually defaults to, and the `BOOST_OPENMETHOD_CLASSES` note says plainly that it does *not* consult affinities - the trap the page warns about. `registries_and_policies.adoc` gets the same correction in its opening paragraph, and `class method`'s own description still carried a paragraph contradicting its `@tparam Registry`. That description was also missing from the reference entirely: MrDocs does not attach a `//!` comment to a declaration when a namespace definition comes between them, and the `namespace detail { ... }` holding the affinity scan had been inserted there. The block moves above the comment, which now touches `class method` as it must. Rendered `method` page keeps its call semantics and overrider-selection steps. `BOOST_OPENMETHOD_TYPE` expanded `va_args<__VA_ARGS__>` three times and the parameter list twice, to compute a registry and hand it back to `method`: 30-36% more preprocessor output per declaration, measured with `g++ -E -P`. A `method_type` alias on the two `va_args` specializations names the method directly, so the macro spells `va_args` twice and the parameter list once, and the affinity scan still never runs for a declaration that names a registry. `va_args::registry` had no consumer left in the tree and goes. Also: three explanatory comments inside the tagged regions of `adl_registry.cpp` moved to the page that includes them, per the rule in CLAUDE.md, and two tests stop naming `BOOST_OPENMETHOD_DEFAULT_REGISTRY` in a comment - `test/CMakeLists.txt` scans for that token to decide which tests must not get the shared PCH, and a mention in prose was enough to withhold it. Claude-Session: https://claude.ai/code/session_01RQG6CbE4o2agseE7bDVzHS Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
jll63
added a commit
that referenced
this pull request
Sep 16, 2026
…#112) Since #96 a class can declare a registry of its own through `boost_openmethod_registry`, so an overrider whose class resolves to a different registry than the method's has parameters that simply do not convert - `virtual_ptr<Poodle, kennel_registry>` against `virtual_ptr<Animal, zoo_registry>`. The guide declared by BOOST_OPENMETHOD then fails to match, and BOOST_OPENMETHOD_OVERRIDE reported only that it "cannot find 'poke' method that accepts the same arguments as the overrider", which points nowhere near the cause. BOOST_OPENMETHOD now declares a second, relaxed guide beside the real one, `<id>_guide_any_registry`, driven by `enable_guide_ignoring_registry`: it rewrites every `virtual_ptr` parameter into the method's own registry, through `rebind_parameter_registry` and its reference forms, before trying the call. An overrider that matches that guide and not the strict one differs from the method in nothing but a registry. The guide never finds a method to call; it exists only to be asked. BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD grows a rung under its existing fallback. The old generic static_assert moves into `..._explain_method_aux`, whose specialization fires when the relaxed guide succeeds and inherits `explain_overrider_mismatch`. That pairs the method's parameters with the overrider's through `validate_overrider_parameter` - the same instantiation a call through the thunk would perform - so the diagnosis is "registry mismatch", with both registries in the trace. The chain is now: strict guide, then the relaxed guide for the mismatch, then "cannot find". Diagnostics only: nothing that compiled before stops compiling. test/compile_fail_overrider_registry_mismatch.cpp covers it. The compile_fail glob has no CONFIGURE_DEPENDS, so a new file needs a manual cmake re-run before ctest sees it. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
jll63
added a commit
to jll63/openmethod
that referenced
this pull request
Sep 16, 2026
… 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
added a commit
to jll63/openmethod
that referenced
this pull request
Sep 16, 2026
… 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
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.)
Closes #82.
A class can name the registry it belongs to, once, next to itself:
or, equivalently, with a member typedef:
The class then has an affinity for that registry, and everything that mentions
it finds it:
virtual_ptr, its two deduction guides,final_virtual_ptr, thesmart pointer aliases and their
make_*_virtualfactories, and any method thattakes the class as a virtual parameter. A method declared without a registry
argument takes the affinity its virtual parameters agree on.
registry_affinity<T>reads it back.Notes on the shape
Two spellings, typedef first. The function is found by argument-dependent
lookup, which is what lets one declaration cover a hierarchy and lets a class
declare an affinity for a class it does not own. It is also ADL's weaknesses: an
overload outside the class's namespace is silently ignored, a translation unit
that does not include it compiles the same method into another registry, and a
namespace-wide function template outranks an inherited affinity. The member
typedef has none of those - it is ordinary member lookup, so it is inherited,
hidden by a derived class's own, and ambiguous between two bases that disagree -
and it is visible from the point it is declared, which is what a class that
mentions
virtual_ptrof itself in its own body needs. Such a class has alreadydecided to be openmethod-aware; the typedef intrudes no further.
Class*, notClass&as sketched in the issue. It matches the hookinplace_vptr.hppalready had, so those classes get the behaviour for free;pointers pass through the
...fallback safely, where a class lvalue is onlyconditionally supported; and an overload on
Base*ranks below an exactDerived*one, which is what lets one declaration cover a hierarchy while aderived class can still override it.
The catch-all returns a sentinel,
detail::default_affinity, not aregistry. "Declares nothing" and "declares an affinity for the default registry"
are then distinct: the second constrains a method like any other declaration,
where recovering "no opinion" by comparing with the macro would have made it
yield.
registry_affinitymaps the sentinel toBOOST_OPENMETHOD_DEFAULT_REGISTRYand remains a query that always answers aregistry.
No affinity is not an affinity for the default registry. Only a declared one
constrains, so a method may mix a class that declares one with a class that does
not - a first affinity does not cascade errors through a codebase. Two
conflicting affinities are diagnosed, as is a registry named on a method that
contradicts one of its parameters, in any of the four parameter shapes. A
virtual_ptr<Class, R>parameter contributes its class's affinity, never theRit spells; the two must agree.A class whose base's overload cannot be used is diagnosed, not defaulted. An
ambiguous or inaccessible base conversion is a substitution failure, so a class
with two differently-hooked bases, a private hooked base, or a repeated
non-virtual hooked base gets a message telling it to declare its own.
The answer is memoized, and asked again where it matters.
registry_affinityis a class template specialization: asked once per class, then remembered. A
class mentioned before it is complete -
virtual_ptr<Node>as a member ofNode, or through a forward declaration - is asked before a declaration furtherdown its body, or a base class, can be seen. Refusing to answer is not an
option:
virtual_ptr<Node> next;in a plain linked structure declares noaffinity, needs none, and has worked since #109. So the question is tagged. The
answer that builds types is the one above;
check_affinityputs the samequestion again where the class must be complete anyway - a virtual parameter, a
class registration - and refuses an answer that has changed. Declaring nothing
stays silent; declaring too late is an error, reported where a wrong registry
would do its damage rather than at the mention, where nothing is wrong yet.
Compatibility
With nothing declared anywhere the catch-all answers for every class, so
registry_affinity<T>isBOOST_OPENMETHOD_DEFAULT_REGISTRYand nothingchanges. Measured rather than argued: for
test_virtual_ptr_dispatch.cppat-O2 -g0,.textis byte-identical and all 530 defined symbols are unchanged.No existing spelling changes meaning or stops compiling, including
BOOST_OPENMETHOD(..., R)over an unhookedvirtual_<T>.Deliberately out of scope
virtual_keeps its single template parameter. Giving it aRegistrywouldcost a mangled-name change for every
method<...>mentioning it, ninepattern-match edits in
core.hpp, and twelve specializations in theanyandtype_erasureheaders. Scanning the parameter list reaches the same place.use_classes/BOOST_OPENMETHOD_CLASSESstill register into the macrodefault unless a registry is listed last. Registering a class that has an
affinity without naming its registry is a run-time
missing_class, not acompile error - documented with a warning, and the obvious follow-up. The
C++26 reflection registrar has the same fallback.
anyandtype_erasureinterop headers are untouched.Tests and docs
Eight tests and eleven compile-fail tests, one per diagnosis. 185/185 pass under
gcc 13.3 Release with examples, and 191/191 under gcc 16 with C++26 reflection
and
BUILD_SHARED_LIBS=ON, both withBOOST_OPENMETHOD_WARNINGS_AS_ERRORS=ON;every compile-fail diagnostic was also matched under clang 18.1. Documentation
introduces "registry affinity" as the term, in
registries_and_policies.adoc,with a new example;
mrdocs.ymlstops excluding the hook, so it andregistry_affinitynow have reference pages.Since the first review round
Rebased onto current
develop. A multi-angle review of the branch found fiveways the affinity was answered wrongly or unchecked, each reproduced on gcc 13,
clang 18 and MSVC, and all five are fixed in the last commit: the memoization
above; an anchor that unwrapped anything with a nested
element_type, so apolymorphic class defining one lost its affinity (it now goes through
virtual_traits); a return type taken verbatim, so-> const zoo_registrywasa distinct registry with its own state and
-> intfailed far from thedeclaration; the sentinel above; and a mismatch check that covered only one of
the four parameter shapes. The
virtual_ptrpart of that work - a constraintthat demanded a complete class too early - was split out and merged separately
as #109.
🤖 Generated with Claude Code
https://claude.ai/code/session_01RQG6CbE4o2agseE7bDVzHS