Skip to content

overriders: support static member functions as overriders (#53) - #120

Open
jll63 wants to merge 5 commits into
boostorg:developfrom
jll63:feature/member-overriders
Open

jll63 wants to merge 5 commits into
boostorg:developfrom
jll63:feature/member-overriders

Conversation

@jll63

@jll63 jll63 commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #53 ("support member functions as overriders"). These are static
member functions — no implicit receiver, no this binding.

  • BOOST_OPENMETHOD_REGISTER now declares its registrar inline, so it can
    be used as a data member inside a class body, not just at namespace scope,
    where it has the same access to the class's private members as any other
    member. That removes the need for friend when the overrider itself is a
    member of the class it needs access to.
  • New BOOST_OPENMETHOD_OVERRIDE_FN(ID, PARAMETERS, RETURN, Fn...) registers
    one or more already-existing functions (free or static member) as
    overriders, via BOOST_OPENMETHOD_TYPE(...)::override<Fn...>. No core.hpp
    changes — it reuses override/override_aux/thunk and
    validate_overrider_parameter unchanged, so a mismatched member overrider
    hits the same diagnostics as a mismatched free-function one.
  • A handful of tests that deliberately use BOOST_OPENMETHOD_REGISTER as a
    function-local static (inline is illegal at block scope) are rewritten to
    spell out its prior expansion by hand.
  • doc/modules/ROOT/pages/friends.adoc gains a member-overriders section,
    backed by a new doc/modules/ROOT/examples/rolex/8 example, presenting this
    as the default alternative to the friend-based idiom it documents for
    classes under the caller's control.

Test plan

  • ctest — 200/200 passing (197 existing + test_member_overrider (2
    cases) + compile_fail_member_overrider_parameter_mismatch)
  • doc/build_antora.sh — full site build, no new warnings; verified no
    stray backticks and no MrDocs artifact leakage in the touched/new pages
    and generated reference pages
  • rolex/8 example run manually, output matches rolex/5/rolex/6
    ($5000 / $10000 / $985000)

🤖 Generated with Claude Code

jll63 and others added 5 commits September 17, 2026 18:00
…ies (boostorg#103)

* policies: add minimal_perfect_hash, two_level_hash and minimal_cover_hash

Three new type_hash policies, from the research for boostorg#57. fast_perfect_hash stays
the default: none of these beats it as a general v-table lookup. What they offer
is a way out of its one real failure mode - its randomized search costs tens to
hundreds of milliseconds on a sparse type id set and gives up outright on a
large one, and the table it finds is sized by where the addresses are rather
than by how many there are.

minimal_perfect_hash (boostorg#56) is hash-and-displace: one slot per type id whatever
the addresses are, a search whose cost depends only on the class count, and no
instruction-set requirement. It pays with a second dependent load on the
dispatch path. It is the one to reach for in a program that dlopens modules
registering classes of their own.

two_level_hash is the same family with the final reduction replaced by a shift
into a power-of-two table: cheaper per dispatch where the compiler hoists the
shift amount, at the cost of a table that is a sawtooth between 1.0 and 2.0
slots per type id rather than a flat figure.

minimal_cover_hash picks the smallest set of bit positions that still separates
the type ids and extracts them with pext. It dispatches as fast as the default
and finds its table deterministically, but BMI2 is not a portable requirement,
so the header always compiles and naming the policy in a registry is what fails
when the instruction is unavailable - with a diagnostic that names both the flag
and the portable alternative.

Four changes from the prototypes, beyond namespace and naming:

- detail::uintptr moves from fast_perfect_hash.hpp to preamble.hpp. Every
  type_hash policy needs it; only one of them had it.
- minimal_perfect_hash and two_level_hash hash `x + 1`. Zero is a fixed point of
  a multiply, so a type id of 0 would be pinned to slot 0 for every seed and
  every pilot and the search could fail spuriously. One increment on the
  dispatch path buys a policy that works for any type id, including the small
  integers a custom rtti policy may hand out.
- two_level_hash drops the M1Candidates knob: scoring first-level multipliers
  for even buckets was measured and does not pay, because the placement cost is
  set by the tail, where every remaining bucket faces an almost full table. Its
  table cap is now relative to the class count rather than an absolute 2^26,
  which could ask for half a gigabyte.
- aux_bytes() is gone from both. It is not part of the TypeHashFn contract; it
  existed so a benchmark could report the pilot array size. finalize() now
  releases that array, which the prototypes leaked until the registry died.

Docs and tests follow.

* policies: rule a zero type id out rather than hashing around it

minimal_perfect_hash and two_level_hash hashed `x + 1` so that a type id of zero
could not be pinned to slot 0. That put an increment on every dispatch to buy
something no caller needs: addresses are never zero, so std_rtti and static_rtti
could never hit it, and a custom rtti policy handing out small integers can
simply not start at zero.

So make it a precondition instead. Both policies hash the type id directly
again, both document zero as outside their domain, and `initialize` asserts that
none of the registered ids is zero when the registry has runtime_checks - one
comparison, since the ids are sorted by then, and compiled out entirely
otherwise.

The dispatch path is back to imul/shr/load/imul/mul/load, with nothing in front
of the first multiply.

* test: cover the three new type_hash policies

Four new files, and the first tests in the suite to drive a type_hash policy
directly rather than through dispatch.

test_hash_policies.cpp feeds each policy a fabricated InitializeContext over
chosen type ids, which is the only way to present a distribution deliberately
instead of taking whatever this program's own classes happen to get. Four
distributions: one packed module, one diluted module (v-tables emitted between
the records, which is what a real one looks like), a program with implicitly
linked libraries, and a program plus dlopened modules tens of terabytes apart.
It asserts injectivity and that hash_range brackets every value on all of them,
that the table size is identical for the packed and the dlopened sets - the
property this family of policies exists for - that minimal_perfect_hash<2, 100>
is exactly minimal and the default is ceil(n / 0.95), that two_level_hash's
table is a power of two between n and 2n, that a type id registered by several
modules is not a collision, and that initialize works again after finalize.

The generators keep a per-module cursor so the ids are distinct by construction,
and a test case asserts that much: a repeated id would exercise the policies'
deduplication rather than their hashing, and would make every injectivity count
come out short for no fault of the policy. Getting that wrong the first time is
what caught it.

test_dispatch_{minimal_perfect,two_level,minimal_cover}_hash.cpp run each policy
end to end - single and multiple dispatch over a five-class hierarchy - with
runtime_checks on unconditionally rather than only in a Debug build, so the
control table that `hash` consults is exercised in both configurations, and with
throw_error_handler so that a call passing an unregistered class is observable as
missing_class instead of aborting.

The minimal_cover_hash test needs BMI2 for the whole translation unit, which the
CMake build adds for that one target on x86, and which b2 gets from a new
config//has_bmi2 probe - the same shape as the existing has_reflection one.
Probing beats naming an architecture: an <architecture>x86 conditional does not
match every toolset spelling. Where the instruction is absent the test still
builds, as one case that records why it did nothing.

test_policies.cpp gains static_asserts that each new policy satisfies the
TypeHashFn blueprint, and that `with` replaces a type_hash policy in place
rather than appending a second - which would leave vptr_vector reading the wrong
state.

* doc: document the three policies, and the dlopen situation they address

The tutorial explained dlopen without ever saying what is different about it for
type ids, which is the thing that decides whether the default hash policy copes.
A new section of shared_libraries.adoc, "Type Ids Across Modules", fills that in:
that a type id is `&typeid(X)`; that the Itanium ABI requires pointer identity
across modules and the linker delivers it for an implicitly linked library with a
copy relocation, so a program and the libraries it links against present one
compact set of ids; that dlopen gets none of that, because nothing names a
plugin's classes, so its records stay in its own mapping wherever the loader put
it; and that RTTI has to keep default visibility for any of it to work, which is
why the library's own tests mark their classes BOOST_SYMBOL_VISIBLE. None of that
was written down anywhere outside a comment in test/dynamic_loading/classes.hpp.

Then what it costs - fast_perfect_hash searching over several far-apart clusters,
and vptr_vector sizing its table from the result - and the four ways out, as a
table: the three new policies and vptr_map, which sidesteps the question by not
hashing at all. Each with the one declaration that selects it, and a note that
`with` replaces by category in place, so the ordering rule elsewhere on the page
is not something a caller has to think about.

Also: three entries in ref_headers.adoc; a paragraph in
registries_and_policies.adoc saying there are four type_hash policies and what
the other three are for; a forward reference in performance.adoc, whose figures
are fast_perfect_hash's specifically; and three tagged snippets in
snippets/policies.cpp, which is compiled and run as a test, so the examples in
the reference cannot rot.

Two fixes the rendered output turned up, neither visible in the source:

- `@ref minimal_perfect_hash:` had its colon absorbed into the reference name,
  so the sentence rendered as "...as in `minimal_perfect_hash` zero is a fixed
  point...". A colon is valid in a qualified name, so the parser takes it.
  Reworded to end the sentence with a period instead. Note that five shipped
  headers have the same construct and lose their colons the same way -
  initialize.hpp's `@li @ref missing_class:` among them - which is left alone
  here.
- BOOST_OPENMETHOD_HAS_PEXT got no reference page, because its doc comment was
  separated from both `#define` directives by the `#if` that chooses between
  them, and a comment on the far side of a directive is not attached. Every
  @ref to it therefore rendered as plain text. The detection now sets an
  internal macro and the documented one is a single unconditional `#define` with
  the comment attached to it.

Verified by rendering, not by reading: every @ref in the three new headers
resolves to a link, the new section's table and both code blocks render, the
three cross-references into its anchor resolve, no page leaks MRDOCS, and no
stray backticks survive on any of the four edited pages.

* fix: two things CI found that a 64-bit build could not

**minimal_cover_hash's static_assert fired at parse time on GCC 11 and 12, and
Clang 13 through 15.** A static_assert whose condition does not depend on the
enclosing template may be diagnosed as soon as the template is *defined* rather
than when it is instantiated - the standard calls such a template ill-formed, no
diagnostic required, and compilers differ on when they report it. The condition
was BOOST_OPENMETHOD_HAS_PEXT, a plain 0 or 1, so on those compilers merely
*including* the header was an error when the instruction was unavailable, which
is the one thing the header promises not to do. It now goes through
detail::has_pext<Registry>, a variable template, so the condition is dependent
and the check happens on use.

Not reproducible here: the oldest local compiler is GCC 13, which defers, and no
container runtime is available. Verified instead that including the header is
clean on GCC 13, 15 and 16 and Clang 22, that naming the policy in a registry
still fails with the same diagnostic, and that it still works under -mbmi2.

**test_hash_policies fabricated 64-bit addresses**, so on a 32-bit target
reinterpret_cast to type_id truncated them. The multi_module bases differ only
in their high bits, so all four collapsed onto one another and the generator
emitted duplicates - 988 distinct ids out of 1000. The bases are now derived
from sizeof(uintptr_t), with a `spread` parameter saying how far apart the
modules sit, so the same layout holds at either width.

The fixture's own generators_produce_distinct_ids case caught this first in CI
and pointed straight at the cause, which is what it is there for. Reproduced
locally with -m32 - the old file fails with exactly CI's [16 != 17] and
[988 != 1000], the new one passes at both widths.

164 tests pass in Release and Debug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqH1U2Ky7ubqKzqSvgxY9y

* fix: the two Windows failures, both verified on the compilers that found them

**_pext_u64 does not exist in 32-bit mode.** The feature test accepted any x86
target - `_M_IX86` on MSVC, or `__BMI2__` on a `-m32` GCC build - so on 32-bit
x86 the header set BOOST_OPENMETHOD_HAS_PEXT to 1 and then reached for an
intrinsic that is not declared there: `_pext_u32` is the widest one 32-bit mode
has. That took out MSVC x86_32, MINGW32 and the 32-bit half of clang-win. The
guard now requires x86-*64* on both compilers, and the documentation, the
CMake matcher and the warning in the policy's docs say 64-bit where they said
x86.

**two_level_hash used std::partial_sum and std::iota without <numeric>.** It
built everywhere I had tried because libstdc++ and libc++ pull the header in
transitively; MSVC's STL at /std:c++latest does not, so the failure appeared
only in that column - on ARM64 as well as x86, which is what made it look like
a second architecture problem rather than a missing include. An audit of all
three new headers for the same class of defect turned up nothing else.

Both reproduced before fixing and re-checked after, on the toolchains that
reported them rather than by inference:

- 32-bit: the old header gives `_pext_u64 was not declared in this scope` under
  `g++ -m32 -mbmi2`; the new one compiles, with HAS_PEXT 0 at 32 bits and 1 at
  64.
- MSVC 18 at /std:c++latest: the old header gives exactly CI's C2039 pair,
  `'partial_sum': is not a member of 'std'` at line 235 and `'iota'` at 246; the
  new one compiles. minimal_cover_hash and two_level_hash now both compile under
  vcvars64 and vcvars32.

164 tests pass in Release and Debug, and the new tests still pass on gcc 11.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqH1U2Ky7ubqKzqSvgxY9y

* fix: MSVC's unreachable-code warning, and clang-cl's pext guard

MSVC C4702 in the three new policy headers. The `abort()` after a call to
the error handler is dead code when the handler is [[noreturn]], as
throw_error_handler is, and /W4 /WX turns that into an error. The library
already answers this with a per-header `#pragma warning(push/disable:
4702/pop)` - preamble.hpp, core.hpp and initialize.hpp all carry one - and
these three were missing it.

Only the cover-hash test failed, which made the warning look specific to
that policy. It is not: fast_perfect_hash.hpp pushes the same disable and
never pops it, so from the first include of <boost/openmethod.hpp> the
warning is off for the rest of the translation unit. The cover-hash test
has to include its policy header *before* that, to read
BOOST_OPENMETHOD_HAS_PEXT before the registry override; the other two
include theirs after, and were covered by the leak. A push/pop pair in
each header makes it self-sufficient whatever the include order.

clang-cl and `_pext_u64`. clang-cl defines _MSC_VER and _M_X64, so the
MSVC arm of the BMI2 guard claimed the intrinsic on a compiler that gates
it on the `bmi2` target feature - "always_inline function '_pext_u64'
requires target feature 'bmi2'", which is exactly the error the guard
exists to prevent. It now excludes clang, so clang-cl falls to the
__BMI2__ arm where it belongs.

test_hash_policies.cpp names the policy too, and was getting no BMI2 flag
from either build file - the reason clang-win was the job that found this.
It now gets the same treatment as test_dispatch_minimal_cover_hash.cpp.
Which also means its cover-hash cases, compiled out everywhere but MSVC
until now, run on gcc and clang as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTCobjuUoBUHHPb1jVShyq

* fix: keep the BMI2 tests off the shared PCH

test_hash_policies.cpp does not override the default registry, so it was
reusing the shared PCH - which is compiled without the BMI2 flag. GCC
warns for every such file ("created and used with differing settings of
'-mbmi2'", -Winvalid-pch) and MSVC rejects a PCH built with a different
/arch outright. test_dispatch_minimal_cover_hash.cpp never hit this: it
overrides the registry, which already excludes it from the PCH.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTCobjuUoBUHHPb1jVShyq

* fix: balance fast_perfect_hash's warning push, and give the vptr policies theirs

fast_perfect_hash.hpp has pushed a `disable: 4702` since it was written and
never popped it, so on MSVC the warning stays off for everything included
after it - which, since default_registry names the policy, is most of a
translation unit. That is what hid the missing pragma in the three new hash
policy headers until a test that had to include one *before*
<boost/openmethod.hpp> found it.

With the pop in place the warning comes back where it always applied, at
vptr_map.hpp's `abort()`; vptr_vector.hpp has the same construct and gets
the same treatment, so that neither depends on who was included first.

Verified with MSVC 14.5: all 63 test_*.cpp compile clean at /W4 /WX, as do
two throwaway sources that instantiate every one of these policies against
throw_error_handler - which is the [[noreturn]] handler that makes the
`abort()` dead code in the first place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTCobjuUoBUHHPb1jVShyq

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…oostorg#110)

* interop: add weak_virtual_ptr for std::weak_ptr

Closes boostorg#73.

Add <boost/openmethod/interop/std_weak_ptr.hpp>, providing weak_virtual_ptr<Class>,
an alias for virtual_ptr<std::weak_ptr<Class>>. It tracks an object with a
std::weak_ptr and remembers its v-table pointer, so that lock() returns a
shared_virtual_ptr without a hash table lookup. Remembering the vptr is safe:
the weak pointer keeps the control block alive, so once the object is destroyed
it stays expired for good.

It is a storage facility only. It is constructed from a shared_virtual_ptr (or
a std::shared_ptr or std::weak_ptr), converts to a weak_virtual_ptr to a base
class, and offers lock(), expired(), use_count(), reset(), pointer() and
vptr(). It cannot be dereferenced, and it cannot be used as a virtual
parameter - neither as virtual_<std::weak_ptr<T>> nor as weak_virtual_ptr<T> -
because the object may no longer exist; validate_method_parameter
specializations reject both with "a weak pointer cannot be a virtual parameter;
call lock() first".

std::weak_ptr does not fit the generic smart-pointer specialization of
virtual_ptr, which needs get(), operator* and a conversion to bool, so the
specialization is written by hand. virtual_traits is deliberately not
specialized for std::weak_ptr: that keeps IsSmartPtr false, which is what lets
the hand-written specialization win.

In core.hpp, the plain virtual_ptr's converting constructor and assignment from
another virtual_ptr now also require the source to have get() (detail::has_get),
so a weak source is a clean substitution failure instead of a hard error in the
body, and is_constructible reports it correctly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: use namespace-scope overriders in the weak_virtual_ptr dispatch test

The dispatch test registered the static member functions of a local class
as override<> template arguments. That is valid C++17, but gcc before 13
rejects it ("has no linkage"), failing the gcc-10/11/12 and Cygwin 32-bit
jobs. Use namespace-scope function templates instead, as test_util.hpp's
poke_bear does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* interop: make weak_virtual_ptr a class of its own

As a partial specialization of virtual_ptr, weak_virtual_ptr matched
everything written for virtual_ptr: is_virtual, so it could not be an
ordinary non-virtual method parameter; the converting constructors of
the plain virtual_ptr, hence the has_get gate in core.hpp; the free
operator==, which then failed inside core.hpp; final_virtual_ptr; and a
user virtual_traits<std::weak_ptr> made it ambiguous. A weak pointer is
not a virtual_ptr - it cannot be dereferenced or dispatched on - so
model it as a class of its own, with the same members.

What it needs from a shared virtual_ptr has no public route: the boxed
v-table pointer, which under indirect_vptr is the address of the cell
that initialize() rewrites and which vptr() unboxes away, and the
constructor that takes a v-table pointer, so that lock() skips the
lookup. core.hpp therefore gains detail::virtual_ptr_access, a generic
door to those two things, befriended by both virtual_ptr
specializations; weak_virtual_ptr uses it to copy the pointer from a
shared virtual_ptr and hand it back in lock(). core names no client
class: the next adaptor - boost::weak_ptr - uses the same door.

Fold in the review's findings on the way. Move constructors and
assignments are noexcept, so containers relocate by moving. Construction
from a std::weak_ptr to another class locks it once, not twice, and an
expired source is copied as is, so that it keeps its control block as
far as the standard library allows - libstdc++ shares ownership with a
source that is expired but not empty, as [util.smartptr.weak.const]
requires; libc++ locks first, so there an expired source of a different
class yields an empty weak pointer. owner_before and swap support the
owner-keyed containers a cache of weak pointers is built on - with a
comparator of one's own, since std::owner_less<void> is generic only in
libstdc++; MSVC's and libc++'s accept std::shared_ptr and std::weak_ptr
alone. The "call lock() first" diagnostic now fires for every form of a
weak virtual parameter - value, &, const& and && - and for
virtual_<weak_virtual_ptr<T>>; a bare weak_virtual_ptr<T> parameter is
valid. A virtual_ptr<std::weak_ptr<T>>, which is what
final_virtual_ptr(std::weak_ptr) would build, is rejected with "use
weak_virtual_ptr", unless virtual_traits is specialized for
std::weak_ptr. The doc comment says what a remembered v-table pointer is
safe against, and what it is not: a second initialize(), unless the
registry uses indirect_vptr.

Like the smart pointer aliases, weak_virtual_ptr defaults its registry
to the affinity its class declares, so that it agrees with the
shared_virtual_ptr it converts to and from.

The compile-fail markers no longer contain a `;`: PASS_REGULAR_EXPRESSION
is a CMake list, so it split each regex into two alternatives, and a
test passed on either half. The CMake loop now refuses such a marker.

Docs: the Weak Pointers section no longer swallows the unique_ptr AST
example, describes a class rather than an alias, and ref_headers.adoc no
longer lists the header under "use in virtual parameters".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018FzFtVWNk6d4Wut5SboCZC

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…g#116)

boostorg#113 gave `virtual_` a registry parameter, so that a method can be
declared without naming a registry and a parameter can name one. The
`validate_method_parameter` specializations in the `any` and
type_erasure interop headers were left spelling `virtual_<T>`, which
after the change matches only the defaulted second argument. A method
declared `virtual_<const std::any&, R>` - valid, and what the registry
affinity machinery produces when a parameter names its registry - fell
through to the primary template instead:

    error: virtual_<> parameter is not a polymorphic class and no
    boost_openmethod_vptr is applicable

Give the registry `virtual_` carries its own template parameter, as the
specializations in core.hpp do. The rejecting one, for an owning
`type_erasure::any` passed by value, is fixed the same way, so that it
keeps reporting "an owning type_erasure::any must be passed by
reference" rather than the vague message above.

The three dispatch tests assert the accepting spellings, in each form
the header covers.


Claude-Session: https://claude.ai/code/session_018FzFtVWNk6d4Wut5SboCZC

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rg#117)

boostorg#103 brought 415 files of b2 output into the tree - 106 `.o`, 8 `.so`,
the test executables, their `.run`/`.output`/`.test` stamps, and the
`bin/config.log` and `bin/project-cache.jam` at the root. 613 MiB
expanded, 94 MiB in the pack.

`bin/` has been ignored since boostorg#108, which landed four days before boostorg#103
was merged and said what would happen without it: "leaving the object
trees permanently untracked-but-addable - `git add test` would commit
tens of thousands of lines of objects". feature/hash-policies was cut
before that commit, so the rule was not in its .gitignore, and a rule
added later does not apply to a path that is already tracked. The merge
carried the files in.

Two of them are written on every b2 invocation, so `git status` came up
dirty after any local build.

`git rm -r --cached` only: the files stay on disk, the ignore rule now
takes effect, and nothing is rewritten. The blobs remain reachable
through `refs/pull/103/head`, which cannot be deleted, so a rewrite of
develop would break the superproject's pinned submodule SHAs without
actually removing them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BOOST_OPENMETHOD_REGISTER now declares its registrar `inline`, so it can be
used as a data member inside a class body - not just at namespace scope -
where it has the same access to the class's private members as any other
member. That is what a member overrider needs: no `friend` declaration, since
the overrider itself is a member.

Add BOOST_OPENMETHOD_OVERRIDE_FN(ID, PARAMETERS, RETURN, Fn...), which
registers one or more already-existing functions (free or `static` member)
as overriders via BOOST_OPENMETHOD_TYPE(...)::override<Fn...>. It reuses the
method-type reconstruction BOOST_OPENMETHOD already does internally, so it
needs no ADL guide lookup and no new validation path: a mismatched overrider
hits the same validate_overrider_parameter diagnostics as a free-function one
(test/compile_fail_member_overrider_parameter_mismatch.cpp).

`inline` is illegal on a block-scope variable, so the handful of tests that
deliberately use BOOST_OPENMETHOD_REGISTER as a function-local static (to
control registration timing relative to initialize()) are rewritten to spell
out its old expansion by hand.

doc/modules/ROOT/pages/friends.adoc gains a member-overriders section
presenting this as the default alternative to the `friend`-based idiom it
documents, backed by a new doc/modules/ROOT/examples/rolex/8 example; the
`friend` sections remain for classes the caller does not control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
@cppalliance-bot

Copy link
Copy Markdown

An automated preview of the documentation is available at https://120.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-18 13:22:52 UTC

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.

support member functions as overriders

2 participants