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
28 changes: 28 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,34 @@ rules:
`missing template arguments` - but a name that *does* resolve would bind to the wrong type
silently. `::registry` works.

### 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. `detail::declared_affinity` maps the default registry to
`detail::default_affinity` to express that; `registry_affinity` itself is a query and always
answers.

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.

**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
force-included PCH parses it first. An affinity has no such ordering relation to the library
headers, so those tests can share the PCH - do not add a fourth marker for them.

`test/CMakeLists.txt` withholds the shared PCH from any `test_*.cpp` that overrides the
registry - a force-included PCH would still precede the `#define`. It detects them by scanning
for the token `BOOST_OPENMETHOD_DEFAULT_REGISTRY` **or** for an include of a header that
Expand Down
71 changes: 71 additions & 0 deletions doc/modules/ROOT/examples/adl_registry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) 2017-2026 Jean-Louis Leroy
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <string>

#include <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>

#define BOOST_TEST_MODULE adl_registry
#include <boost/test/unit_test.hpp>

// tag::registry[]
struct zoo_registry :
boost::openmethod::default_registry::with<
boost::openmethod::policies::runtime_checks> {};
// end::registry[]

using namespace boost::openmethod;

// tag::affinity[]
namespace zoo {

class Animal {
public:
virtual ~Animal() = default;

private:
// Animal - and every class derived from it - belongs to zoo_registry
friend auto boost_openmethod_registry(Animal*) -> zoo_registry;
};

class Dog : public Animal {};
class Cat : public Animal {};

} // namespace zoo
// end::affinity[]

// tag::methods[]
BOOST_OPENMETHOD_CLASSES(zoo::Animal, zoo::Dog, zoo::Cat, zoo_registry);

// no registry argument: speak follows Animal
BOOST_OPENMETHOD(speak, (virtual_<const zoo::Animal&>), std::string);

BOOST_OPENMETHOD_OVERRIDE(speak, (const zoo::Dog&), std::string) {
return "bark";
}

BOOST_OPENMETHOD_OVERRIDE(speak, (const zoo::Cat&), std::string) {
return "meow";
}
// end::methods[]

// tag::virtual_ptr[]
// ...and so does virtual_ptr
static_assert(
std::is_same_v<virtual_ptr<zoo::Dog>, virtual_ptr<zoo::Dog, zoo_registry>>);
// end::virtual_ptr[]

BOOST_AUTO_TEST_CASE(adl_registry) {
// tag::call[]
initialize<zoo_registry>();

zoo::Dog spot;
zoo::Cat felix;
// end::call[]

BOOST_TEST(speak(spot) == "bark");
BOOST_TEST(speak(felix) == "meow");
}
56 changes: 56 additions & 0 deletions doc/modules/ROOT/pages/registries_and_policies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,62 @@ declaration must use the same class-key as the definition. Qualify the name if
it could also be found in namespace `boost::openmethod` - `registry` in
particular.

### Registry affinity

`BOOST_OPENMETHOD_DEFAULT_REGISTRY` is a whole-program answer: one registry, for
every class. A class can instead name its own, by declaring a
cpp:boost_openmethod_registry[] function that takes a pointer to it and returns
the registry. The function is never called - only its return type is used - so
it needs no definition:

[source,c++]
----
include::example$adl_registry.cpp[tag=affinity]
----

The class then _declares_ an affinity for that registry, and everything that
mentions the class finds it. A method declared without a registry argument takes the
affinity of its virtual parameters:

[source,c++]
----
include::example$adl_registry.cpp[tag=methods]
----

...and so does cpp:virtual_ptr[], along with the smart pointer aliases and the
`make_*_virtual` factories:

[source,c++]
----
include::example$adl_registry.cpp[tag=virtual_ptr]
----

An affinity is inherited: declaring it for the root of a hierarchy covers every
class derived from it, because the derived-to-base pointer conversion makes the
root's declaration viable. A declaration for a derived class is a better match,
and wins.

Every class has a registry affinity; a class that declares none has the
_default_ affinity, `BOOST_OPENMETHOD_DEFAULT_REGISTRY`. A _declared_ affinity
wins over a default one, so a method may mix a class that declares one with a
class that does not, and lands in the declared registry. Two virtual parameters
with *different* declared affinities are an error, as is a registry named on the
method that contradicts one of its parameters.

A hidden friend, as above, is the spelling to prefer. The declaration must
precede every use of the class in a method, a `virtual_ptr` or a class
registration - declaring it later makes the program ill-formed with no
diagnostic required, and compilers disagree silently about which registry the
earlier use got. Being part of the class, a hidden friend cannot be late.

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 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
control how type information is obtained, how vptrs are acquired, how errors are
Expand Down
6 changes: 6 additions & 0 deletions doc/modules/ROOT/pages/virtual_ptr_alt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ v-table for the bases, just like what C++ does for its native vptrs.
`inplace_vptr_base` and `inplace_vptr_derived` are aliased in `namespace
boost::openmethod::aliases`.

`inplace_vptr_base` also declares the class's
xref:ROOT:registries_and_policies.adoc#registries_and_policies[registry affinity],
as a hidden friend - it is told the registry, and every other construct can then
find it. A method over such a hierarchy needs no registry argument, and the
program needs no `BOOST_OPENMETHOD_DEFAULT_REGISTRY` override.

An object that embeds its v-table pointer does not need to be wrapped in a
`virtual_ptr` - the two fill the same goal, fast access to the v-table
pointer - and wrapping one is rejected at compile time.
1 change: 0 additions & 1 deletion doc/mrdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ exclude-symbols:
- 'boost::openmethod::registry::initialize'
- 'boost::openmethod::registry::finalize'
- 'boost::openmethod::boost_openmethod_bases'
- 'boost::openmethod::boost_openmethod_registry'
- 'boost::openmethod::registry_state::st'

# Macros. Only the public macros carry a doc comment, and with
Expand Down
Loading
Loading