Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 49 additions & 26 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,25 +490,42 @@ rules:

### Registry affinity

A class can name its registry instead of the program naming one for every class: declare
`auto boost_openmethod_registry(Class*) -> Registry;`, preferably as a hidden friend. The class
then *declares* an affinity for that registry, inherited by its derived classes, and
`registry_affinity<T>` reads it back. `virtual_ptr` and the smart pointer aliases default to it,
and a method declared without a registry argument takes the affinity its virtual parameters agree
on (`detail::method_registry`, driven by `detail::param_affinity` / `agreed_affinity`).

Every class has an affinity; one that declares none has the *default* affinity. Only a *declared*
affinity constrains a method, so a method may mix a class that declares one with a class that does
not - the latter yields. The catch-all `boost_openmethod_registry(...)` returns the sentinel
`detail::default_affinity`, not a registry, so an affinity declared for the default registry
itself still counts as declared; `detail::registry_affinity_aux<T>::declared` is the raw answer
(sentinel or registry) and `registry_affinity<T>` the query, which always answers a registry. A
`virtual_ptr` parameter contributes its *class's* affinity, never the registry it spells; a
spelled registry must agree with the class (`validate_method_parameter`, all three shapes).

Two spellings, looked up in this order: a member typedef `boost_openmethod_registry`
(`detail::member_registry_aux` - ordinary member lookup, so inherited, hidden by a derived class's
own, ambiguous between two bases) and the ADL overload. `inplace_vptr_base` provides the typedef.
A class can name its registry instead of the program naming one for every class. Two spellings,
looked up in this order: a member typedef `boost_openmethod_registry`
(`detail::member_affinity` - ordinary member lookup, so inherited, hidden by a derived class's
own, ambiguous between two bases that disagree) and an ADL overload
`auto boost_openmethod_registry(Class*) -> Registry;`, preferably a hidden friend.
`inplace_vptr_base` provides the typedef. The class then *declares* an affinity for that registry,
inherited by its derived classes, and `registry_affinity<T>` reads it back. `virtual_ptr` and the
smart pointer aliases default to it.

**The sentinel is `void`.** The catch-all `boost_openmethod_registry(...)` returns it, so an
affinity declared for the default registry itself still counts as declared.
`detail::registry_affinity_aux<T>::declared` is the raw answer (registry or `void`), and
`registry_affinity<T>` is the query, which maps `void` to `BOOST_OPENMETHOD_DEFAULT_REGISTRY` and
so always answers a registry. `using boost_openmethod_registry = void;` therefore means "declares
nothing". `void` rather than a dedicated struct because the sentinel appears in every
`virtual_<T, ...>` of every `method<...>`, and mangled names are long enough already.

**Every virtual parameter carries a registry, or adopts.** `virtual_` takes a registry parameter
of its own - declared in `preamble.hpp`, defaulted in `core.hpp` where the affinity machinery
exists, since C++ merges default template arguments across declarations:

| | carries |
|---|---|
| `virtual_<T>` | its class's declared affinity; **adopts** if the class declares none |
| `virtual_<T, S>` | `S` |
| `virtual_ptr<C>` | `C`'s affinity, else the macro default |
| `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.
`detail::param_registry` says what a parameter carries - **what it carries, never its class's
affinity**: a spelled `virtual_ptr<B, a_registry>` decides the method's registry even though `B`
declares nothing. `detail::agreed_registry` folds them, adopters abstaining. A method that names a
registry requires every carrier to carry that one (`validate_method_parameter`, all four shapes);
one that names none takes what the carriers agree on, or the macro default.

**The answer is memoized, so every question carries a `Question` tag.** A class mentioned before
it is complete - `virtual_ptr<Node>` as a member of `Node`, or through a forward declaration - is
Expand All @@ -526,13 +543,19 @@ Anchoring goes through `virtual_traits` (`detail::virtual_type<T, macro_default_
a bare `element_type` probe: a polymorphic class may define `element_type` and must keep its own
affinity.

Two things deliberately do **not** participate, and both are documented as such:

- `use_classes` / `BOOST_OPENMETHOD_CLASSES` still register into
`BOOST_OPENMETHOD_DEFAULT_REGISTRY` unless a registry is listed last. Registering a class that
has an affinity without naming its registry is a run-time `missing_class`, not a compile error.
- The `any` and `type_erasure` interop headers are untouched. `virtual_any<A, R>&` contributes no
affinity, so a method over one behaves exactly as before.
**`use_classes` follows the affinities too, and is stricter than a method**, because a class list
has no parameter to adopt from and no spelling of its own to disambiguate with. A registry listed
last wins - a class declaring another is an error, one declaring none goes along. Without one, the
classes must be unanimous: all declaring the same registry, or none declaring one (then the macro
default). Mixing a declaring class with a non-declaring one is an error, where the same mixture
among a method's parameters is fine. `detail::class_list_registry` decides; `unanimous_registry`
is the strict fold, deliberately *not* `agreed_registry`.

One thing deliberately does **not** participate, and is documented as such: the `any` and
`type_erasure` interop headers are untouched, and `virtual_any<A, R>&` contributes no affinity, so
a method over one behaves exactly as before. The C++26 `register_classes` also still defaults to
the macro - its groups may name a namespace, whose classes are only known during the scan that the
choice of registry feeds.

**A test that selects a registry through an affinity needs no PCH marker.** The scan below exists
because `BOOST_OPENMETHOD_DEFAULT_REGISTRY` must be defined before `core.hpp` is parsed, and a
Expand Down
38 changes: 31 additions & 7 deletions doc/modules/ROOT/pages/registries_and_policies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,37 @@ registry, as it always has. Declaring one too late is an error - not at the
mention, where nothing is wrong yet, but where the class is complete and its
affinity matters: a virtual parameter of a method, or a class registration.

WARNING: A declared affinity does not reach
xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], which
still registers into `BOOST_OPENMETHOD_DEFAULT_REGISTRY` unless a registry is
listed last. Registering a class that declares an affinity, without naming its registry,
puts the class in one registry and its methods in another - and that shows up as
a `missing_class` error at run time, not as a compile error. List the registry:
`BOOST_OPENMETHOD_CLASSES(Animal, Dog, zoo_registry)`.
A virtual parameter either _carries_ a registry or _adopts_ the method's. A
`virtual_ptr` always carries one: it is a type in its own right, and names a
registry whether or not its class declares an affinity. A `virtual_` carries
the registry its class declares, and adopts when the class declares none -
which is
what lets a method mix a class that has an affinity with one that has not.
Either can be spelled: `virtual_<const Animal&, zoo_registry>` and
`virtual_ptr<Animal, zoo_registry>` carry `zoo_registry` whatever `Animal`
declares.

A method that names a registry requires every parameter that carries one to
carry that one; the parameters that adopt go along. A method that names none
requires the carriers to agree, and takes their registry - or
`BOOST_OPENMETHOD_DEFAULT_REGISTRY` if no parameter carries one.

xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] follows
the affinities too, and is stricter, because a class list has no parameter to
adopt from and no spelling of its own to disambiguate with. Listing a registry
settles it, and then a class that declares nothing goes along while one that
declares another registry is an error. 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
class that declares an affinity with one that does not is an error, where the
same mixture in a method's parameter list is fine.

NOTE: The C++26 registrar
xref:reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc[BOOST_OPENMETHOD_REGISTER_CLASSES]
does not follow affinities: it registers into
`BOOST_OPENMETHOD_DEFAULT_REGISTRY` unless its groups name a registry. Its
groups may name a namespace, whose classes are only known during the scan that
the choice of registry feeds.

A registry has a collection of _policies_. Each policy belongs to a policy
category. A registry may contain at most one policy of each category. Policies
Expand Down
Loading
Loading