Skip to content

virtual parameters obtain their registry via ADL - #96

Open
jll63 wants to merge 4 commits into
boostorg:developfrom
jll63:feature/adl-default-registry
Open

virtual parameters obtain their registry via ADL#96
jll63 wants to merge 4 commits into
boostorg:developfrom
jll63:feature/adl-default-registry

Conversation

@jll63

@jll63 jll63 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

(Written by Claude Code, on behalf of @jll63.)

Closes #82.

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, and everything that mentions
it finds it: virtual_ptr, its two deduction guides, final_virtual_ptr, the
smart pointer aliases and their make_*_virtual 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.

BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, zoo_registry);

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

static_assert(std::is_same_v<virtual_ptr<Dog>, virtual_ptr<Dog, zoo_registry>>);

Notes on the shape

Class*, not Class& as sketched in the issue. It matches the hook
inplace_vptr.hpp already had, so those classes get the behaviour for free;
pointers pass through the ... fallback safely, where a class lvalue is only
conditionally supported; and an overload on Base* ranks below an exact
Derived* one, which is what lets one declaration cover a hierarchy while a
derived class can still override it.

The catch-all returns BOOST_OPENMETHOD_DEFAULT_REGISTRY, not void.
inplace_vptr's private catch-all returned void to mean "no customization";
returning the default registry instead makes the result usable as a default
template argument directly, and makes backward compatibility structural rather
than special-cased. inplace_vptr's copy is removed in favour of the shared one.

No affinity is not an affinity for the default registry. Only the first
yields, so a method may mix a class that has an affinity with one that has none
— 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.

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.

Compatibility

With no overload anywhere the catch-all answers for every class, so
default_registry_of<T> is BOOST_OPENMETHOD_DEFAULT_REGISTRY and nothing
changes. Measured rather than argued: for test_virtual_ptr_dispatch.cpp at
-O2 -g0, .text is byte-identical and all 530 defined symbols are unchanged.
No existing spelling changes meaning or stops compiling, including
BOOST_OPENMETHOD(..., R) over an unhooked virtual_<T>.

Deliberately out of scope

  • virtual_ keeps its single template parameter. Giving it a Registry would
    cost a mangled-name change for every method<...> mentioning it, nine
    pattern-match edits in core.hpp, and twelve specializations in the any and
    type_erasure headers. Scanning the parameter list reaches the same place.
  • use_classes / BOOST_OPENMETHOD_CLASSES still register into the macro
    default 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 — documented with a warning, and the obvious follow-up.
  • The any and type_erasure interop headers are untouched.

Tests and docs

Eight new tests and four compile-fail tests. 160/160 pass under gcc 13.3
Release, clang 18.1 Release, and gcc Debug (runtime checks), all with
BOOST_OPENMETHOD_WARNINGS_AS_ERRORS=ON. Documentation introduces "registry
affinity" as the term, in registries_and_policies.adoc, with a new example;
mrdocs.yml stops excluding the hook, so it and default_registry_of now have
reference pages.

Not covered here: the dynamic_loading and implicit_shared_libraries suites,
which the CMake configuration used skips with BUILD_SHARED_LIBS=OFF.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1

@jll63 jll63 changed the title Virtual parameters obtain their registry by ADL virtual parameters obtain their registry by ADL Sep 1, 2026
@cppalliance-bot

cppalliance-bot commented Sep 1, 2026

Copy link
Copy Markdown

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-02 23:03:42 UTC

@jll63 jll63 changed the title virtual parameters obtain their registry by ADL virtual parameters obtain their registry via ADL Sep 1, 2026
@jll63 jll63 closed this Sep 2, 2026
@jll63 jll63 reopened this Sep 2, 2026
@jll63
jll63 force-pushed the feature/adl-default-registry branch from 60a423f to 0ef01ba Compare September 2, 2026 13:50
@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.75%. Comparing base (b3bec3b) to head (0ef01ba).
⚠️ Report is 7 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop      #96      +/-   ##
===========================================
- Coverage    94.94%   92.75%   -2.19%     
===========================================
  Files           99       22      -77     
  Lines         4373     1643    -2730     
  Branches      2168      505    -1663     
===========================================
- Hits          4152     1524    -2628     
+ Misses         162       71      -91     
+ Partials        59       48      -11     
Files with missing lines Coverage Δ
include/boost/openmethod/core.hpp 91.63% <100.00%> (-1.43%) ⬇️
include/boost/openmethod/inplace_vptr.hpp 100.00% <ø> (ø)
...e/boost/openmethod/interop/boost_intrusive_ptr.hpp 94.44% <ø> (ø)
...nclude/boost/openmethod/interop/std_shared_ptr.hpp 94.73% <ø> (ø)
...nclude/boost/openmethod/interop/std_unique_ptr.hpp 90.00% <ø> (ø)

... and 77 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 60c9ff8...0ef01ba. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

jll63 and others added 4 commits September 2, 2026 18:56
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
jll63 force-pushed the feature/adl-default-registry branch from 0ef01ba to 55b4446 Compare September 2, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADL customization point for default value of virtual_ptr's registry parameter

2 participants