Skip to content

registry affinity: a registry parameter for virtual_, and class lists that follow it - #113

Open
jll63 wants to merge 1 commit into
boostorg:developfrom
jll63:feature/virtual-registry-param
Open

jll63 wants to merge 1 commit into
boostorg:developfrom
jll63:feature/virtual-registry-param

Conversation

@jll63

@jll63 jll63 commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

(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's
registry read the class's declared affinity instead of what the parameter
carried, so BOOST_OPENMETHOD(f, (virtual_ptr<B, a_registry>), void) was 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.

Every virtual parameter carries a registry, or adopts

virtual_ takes a registry parameter — declared in preamble.hpp, defaulted in
core.hpp where the affinity is known, 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 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 its 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, 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>)> is
method<foo, void(virtual_<A&, default_registry>), default_registry>.

With A declaring a_registry and B declaring nothing:

parameter no registry , a_registry , default_registry
virtual_<A&> a_registry ok error
virtual_<B&> default_registry ok ok
virtual_<B&, a_registry> a_registry ok error
virtual_ptr<A> a_registry ok error
virtual_ptr<B> default_registry error ok
virtual_ptr<B, a_registry> a_registry (was an error) ok error
virtual_ptr<A, default_registry> default_registry (was an error) error ok

Nothing that compiles today changes meaning; the two marked rows gain one.

The sentinel for "carries nothing" is void, replacing the default_affinity
struct — as inplace_vptr's private catch-all spelled it before #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".

Class lists follow the affinities too

use_classes took 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. Not a compile error, a crash:

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 → Aborted (core dumped), exit 134
after:   bark, exit 0

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:

outcome
use_classes<A, A1> a_registryA1 inherits the declaration
use_classes<B> default_registry
use_classes<A, A1, B, a_registry> a_registryB declares nothing, so it goes along
use_classes<A, B> error — some declare, some do not
use_classes<A> a_registry (was default_registry, silently)
use_classes<A, default_registry> error (was allowed, silently)

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.cpp is 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 diagnostic
was matched under clang 18.1 as well.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RQG6CbE4o2agseE7bDVzHS

@cppalliance-bot

cppalliance-bot commented Sep 16, 2026

Copy link
Copy Markdown

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 14:21:59 UTC

@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.25%. Comparing base (d44aa59) to head (ae240ec).
⚠️ Report is 4 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             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              
Files with missing lines Coverage Δ
include/boost/openmethod/core.hpp 91.60% <ø> (-1.49%) ⬇️
include/boost/openmethod/preamble.hpp 78.57% <ø> (ø)

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 261ebed...ae240ec. Read the comment docs.

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

@jll63
jll63 force-pushed the feature/virtual-registry-param branch from ae240ec to 80fce95 Compare September 16, 2026 12:18
… 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
jll63 force-pushed the feature/virtual-registry-param branch from 80fce95 to 80823f5 Compare September 16, 2026 14:18
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.

2 participants