Conversation
|
An automated preview of the documentation is available at https://122.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-21 18:52:13 UTC |
3 tasks
…tic members (boostorg#53) Alternative design to boostorg#120: six macros mirroring BOOST_OPENMETHOD and its overrider macros, but declaring static member functions instead of free ones. No implicit `this`, no receiver binding - dispatch is still entirely by the method's own virtual parameters. - BOOST_OPENMETHOD_MEM declares a method as a static member function of the class it's used in, overloadable exactly like a free method. Its tag is generated per-expansion rather than ID-derived, which is what makes the overload possible. - BOOST_OPENMETHOD_TYPE_MEM names a member method's core `method<>` type, since BOOST_OPENMETHOD_TYPE can't (it reconstructs the tag from ID alone). - BOOST_OPENMETHOD_OVERRIDE_MEM / _DECLARE_OVERRIDER_MEM / _DEFINE_OVERRIDER_MEM add an overrider, as a static member function, to either a free method or a member method (qualified as Class::method). Being a member gives it the same access to its class's private state as any other member, with no `friend` declaration. - BOOST_OPENMETHOD_OVERRIDER_MEM finds a member overrider's key (`fn`, `method_type`) from outside, for explicit calls and for `next`/`has_next` via the core API - not available by name inside a _MEM body the way they are in a free one's. No core.hpp changes: each overrider is registered through a per-overrider "key" struct holding a BOOST_FORCEINLINE trampoline, so override_aux/thunk/ validate_overrider_parameter all see an ordinary function pointer, unchanged. Verified on gcc, clang and MSVC, including private access, overloaded member methods, and next<> from a DECLARE/DEFINE-split body (self-referencing, easy to get backwards - documented prominently). Known limitation: a _MEM overrider's body and key-accessor are each named once, overloaded purely on (return type, parameters) - never on which method they override, since a qualified ID can't be pasted into a new declaration. At most one overrider of a given exact signature per class, regardless of method. doc/modules/ROOT/pages/friends.adoc is retitled "Members and Friends" in the nav and gains a _MEM tutorial before the existing `friend` content, which it supersedes for classes under the caller's control. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
…rence The page named the macros in plain code spans while every other page links them, so a reader had no way from the tutorial to the reference page that describes the arguments. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
The `pay` example declared its first parameter `Employee&` and made Payroll derive from Employee so that each overrider could cast it back - which reads as if a payroll were a kind of employee, and put a `static_cast<Payroll&>` in front of every call to the private member the example exists to demonstrate. Declare the parameter `Payroll&` instead, over a forward declaration, exactly as the `friend` example further down the same page already does: only a reference appears in the parameter list, and Payroll is not dispatched on. The inheritance and all three casts go away, and the page's claim that the overriders "call it as an ordinary same-class private call" becomes literally true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
The page now covers both ways an overrider reaches a class's private state - being a member of it, and `friend` - so the file name names the subject rather than one of the two answers. The nav label and the `@see` link text are unchanged; only the path moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
The tutorial referred to `ID`, and to `(Class, ID, PARAMETERS, RETURN)`, which are the reference pages' formal parameter names and are never introduced on the tutorial page itself. Describe the arguments by what they are instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
…acros BOOST_OPENMETHOD and BOOST_OPENMETHOD_MEM take an optional registry after the return type. The macros that name an existing member method, or that declare an overrider, cannot use one: a member method's registry is fixed by the declaration that created it, and an overrider takes the registry of the method it overrides, which LOCATE_METHOD finds through a guide lookup that enable_guide_ignoring_registry makes registry-agnostic. Passing one anyway produced two different failures, neither legible. BOOST_OPENMETHOD_TYPE_MEM accepted it and silently ignored it, because va_args<...>::return_type takes the first argument and drops the rest. The four overrider macros pasted __VA_ARGS__ raw into a function pointer type, which gave three cascading parse errors on the user's own line with nothing to suggest the cause. **This makes a previously tolerated input an error**: TYPE_MEM with a trailing registry no longer compiles. There are no call sites. Route all five through a new detail::va_args_no_registry, whose variadic specialization is a static_assert naming the reason. Its return_type lives in a base class: a failed static_assert marks the record invalid on clang, so a member declared alongside it is not found and the cascade returns. With the base, gcc reports exactly one error and clang leads with the message. A return type containing a top-level comma still arrives as several macro arguments and reassembles inside the template argument list, which is what tells the two cases apart - the same discrimination va_args already relies on, and the reason DEFINE_OVERRIDER_MEM can stop using mp_back for it. DETAIL_OVERRIDE_MEM forwards the return type to a new _AUX helper, which is variadic rather than taking a named RET: a comma-bearing return type would otherwise be split into several arguments before _AUX could receive it, and the expansion would fail on arity. Tests: a comma-bearing return type through all three _MEM overrider shapes plus TYPE_MEM and OVERRIDER_MEM, which nothing covered before and which va_args_no_registry is now solely responsible for; and a compile-fail test for the rejected registry. 204/204. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
Every MSVC release job has failed since this branch's first push (run 35455224030, 6207dd3), and it is not a flake: test_member_method.cpp(74): error C2220: the following warning is treated as an error test_member_method.cpp(74): warning C4714: function 'ZooKeeper::openmethod_gensym_19::trampoline<virtual_ptr<Cat, ...>>' marked as __forceinline not inlined The trampoline is reached only through `fn`, the function pointer formed from its address on the next line. An explicit `fn(args)` call - which test_member_method.cpp makes, and which is a documented use of BOOST_OPENMETHOD_OVERRIDER_MEM - is therefore an indirect call that cannot be inlined, and MSVC emits C4714 for a __forceinline function it did not inline. The suite builds with /W4 /WX, so it is fatal. It needs /O2 as well as the explicit call, which is why the plain /W4 /WX check run during development missed it: C4714 is only emitted when optimizing. BOOST_FORCEINLINE buys nothing on MSVC anyway - __forceinline is ignored under /Od, so the unoptimized extra call it removes on gcc and clang was never removed there. Guard it, keeping the attribute everywhere it pays. Guarded on BOOST_MSVC rather than _MSC_VER so clang-cl, which does not implement C4714, keeps the attribute. Verified by reproducing the failure locally under the CI condition (cl /std:c++17 /O2 /Ob2 /W4 /WX with an explicit fn call), confirming the guard clears it and the binary still runs, that gcc still expands to inline __attribute__((__always_inline__)), and 204/204 on Linux. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
459eb0e routed the _MEM return type through detail::va_args_no_registry<...>::return_type, a nested typedef, which needs `typename` when the return type is dependent. The macro cannot ask the caller for it, so a shape that compiled at 2192199 stopped compiling: template<class R> struct Probe { using key = BOOST_OPENMETHOD_OVERRIDER_MEM(S, m, (virtual_ptr<D>), R); }; failed with "expected type-specifier before '::' token" pointing into macros.hpp rather than at the user's line - precisely the diagnostic failure 459eb0e set out to remove. Supply `typename` in all four macros; it has been legal outside a template since C++11. No test covered a dependent return type, which is why the suite stayed green. One is added alongside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
BOOST_OPENMETHOD_MEM did not work across translation units, which is
fatal for it: a member method has nowhere to live but a header.
The tag, the alias, the overrider KEY and the registrar were all named
with BOOST_OPENMETHOD_GENSYM, i.e. __COUNTER__. Those are class
*members*, so a class in a header acquired a different
member-specification - and Zoo::poke a different method<> type - in
every TU whose counter state differed at the point of inclusion. Two
TUs, one with a single `enum { x = __COUNTER__ };` before the include,
compiled and linked clean and then reported "not implemented" at the
call. It is also a plain ODR violation no compiler diagnoses.
Name them from __LINE__ instead: stable for a given header across TUs,
and two _MEM macros on one line collide loudly ("member declared
twice"), never silently. The free macros were never affected - their
tag is ID##_boost_openmethod, and their one gensym names a
namespace-scope alias or an internal-linkage variable, neither part of
a class's member-specification.
The registrar leaves the class body entirely, on the model of
inplace_vptr_base's inplace_vptr_use_classes: the trampoline odr-uses
detail::mem_registrar<R>, an `inline` variable template keyed on its
own type. It therefore needs no invented name at all, and is one entity
program-wide - the same overrider seen from any number of TUs registers
once, by linkage.
test/cross_tu is the first test here built from more than one
translation unit, which is why this went uncovered: nothing built from
a single .cpp can see it. Verified that it fails against the previous
commit's header and passes against this one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
augment_methods() merges the copies of an overrider that several
state-sharing modules each registered. It keyed that merge on the
overrider's *shape* - its function type plus its virtual parameter type
ids - gated on the inline_ flag. A shape is not an identity, and two
genuinely different overriders share one routinely:
namespace a { BOOST_OPENMETHOD_INLINE_OVERRIDE(poke, (virtual_ptr<Dog>),
const char*) { return "a"; } }
namespace b { BOOST_OPENMETHOD_INLINE_OVERRIDE(poke, (virtual_ptr<Dog>),
const char*) { return "b"; } }
printed "a" and reported no error, where the non-inline spelling of the
same pair correctly reports them ambiguous. This is a pre-existing bug,
reachable with the free macros alone and with no member method in
sight. BOOST_OPENMETHOD_OVERRIDE_MEM only makes it easy to meet: an
in-class body is implicitly inline, so every member overrider is
flagged inline_, and two classes may each add an overrider of one
method with one signature - which the free macros cannot express in a
single translation unit.
overrider_info gains `identity`, the type id of the registrar itself.
Its mangled name carries the registered function as a non-type template
argument, so it is distinct between two different overriders and equal
between the copies of one. Verified equal across a shared-library
boundary under -fvisibility=hidden, which is the case the merge exists
to serve.
Identity refines the test rather than replacing it, deliberately. It is
a type id of a non-class type, and an rtti policy may return one
sentinel for every type it does not recognise - the custom_rtti
examples returned 0. Keying on identity alone made every overrider in
those examples compare equal and collapse into one. As a conjunct it is
safe: under such a policy the behaviour is exactly what it was, and
under a well-behaved one the false merge is gone.
A copy dropped by the merge now has its `next` filled from the copy
that was kept. Dispatch reads the variable belonging to the module it
runs in, so a dropped copy otherwise left next<Fn> null while
has_next<Fn>() - which tests only for the not_implemented and ambiguous
thunks - reported true.
The custom_rtti examples now allocate a distinct id for each
non-polymorphic type, counting down from the largest a type_id holds,
clear of the ids the Node hierarchy assigns counting up. The RttiFn
blueprint said these ids were "for diagnostic and trace purposes",
which invited exactly the sentinel that breaks consolidation; it now
states that static_type must be injective, and that dynamic_type is
under no such obligation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
f9c0ba3 renamed friends.adoc to privacy.adoc and updated every link in the tree, but nothing re-established the published URL. Both deployed sites currently serve .../openmethod/friends.html, and the rendered output now emits privacy.html only, so after this merges the old path 404s for external bookmarks, mailing-list links and search results - friends.html#friendship among them, and that anchor was deliberately kept inside the new page, so inbound deep links were meant to survive. Antora's :page-aliases: emits the redirect. test_member_method.cpp still named friends.adoc in a comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
The overrider consolidation this branch reworked had no test that crossed a module boundary, and nothing else in test/ exercises a _MEM macro from more than one module. A member method and a member overrider are declared in a header that both the executable and the shared library include, so each module registers its own copy of the overrider. They are copies of one overrider, not two, and initialize() has to recognise that: keeping both leaves the Dog cell ambiguous, since neither copy is more specific than the other. Built with -fvisibility=hidden, which is what the Boost super-project uses and what makes the test worth having. At default visibility the registrar - an inline variable template since d6bdd83 - is merged across the modules by the dynamic linker, one registration survives, and the consolidation path is never reached. Confirmed by switching BOOST_OPENMETHOD_OVERRIDE_MEM to a non-inline registrar: the test reports ambiguous == 1 and fails, and passes again when it is restored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
The marker was `// expected-error: .*`, on the grounds that gcc and clang word the diagnostic differently. That makes the test incapable of failing: openmethod_compile_fail_test sets PASS_REGULAR_EXPRESSION and nothing else, and CTest ignores a test's exit status once that property is set, so `.*` matches whatever comes out - including the output of a file that compiled cleanly. CLAUDE.md sanctions `.*` in place of a `;` inside a regex, not as the whole regex. The wordings do differ - gcc "cannot be overloaded with", clang "class member cannot be redeclared", MSVC C2556 "overloaded function differs only by return type" - but all three name boost_openmethod_overrider_key, the accessor whose overload set the collision happens in. Verified on gcc 13, clang 18 and MSVC v18. Checked that the test now fails when it should: giving the second overrider a different parameter list makes the file compile, and the test reports "Required regular expression not found". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E53cDWKgiva4cfH48EtvMP
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Alternative design for #53, exploring a different API shape than #120
(
feature/member-overriders). Independent branches - neither depends on theother, and this is deliberately a separate PR to compare approaches, not a
replacement.
Six new macros mirroring
BOOST_OPENMETHODand its overrider macros, butdeclaring static member functions instead of free ones. No implicit
this, no receiver binding - dispatch is still entirely by the method's ownvirtual parameters, exactly as for a free method.
BOOST_OPENMETHOD_MEMdeclares a method as a static member function,overloadable exactly like a free method (its tag is generated per
expansion, not
ID-derived, which is what makes the overload possible).BOOST_OPENMETHOD_TYPE_MEMnames a member method's coremethod<>type,since
BOOST_OPENMETHOD_TYPEcan't.BOOST_OPENMETHOD_OVERRIDE_MEM/_DECLARE_OVERRIDER_MEM/_DEFINE_OVERRIDER_MEMadd an overrider, as a static member function, toeither a free method or a member method (
Class::method). Being a membergives it the same access to its class's private state as any other member,
with no
frienddeclaration.BOOST_OPENMETHOD_OVERRIDER_MEMfinds a member overrider's key (fn,method_type) from outside, for explicit calls and fornext/has_nextvia the core API.
No
core.hppchanges: each overrider registers through a per-overrider"key" struct holding a
BOOST_FORCEINLINEtrampoline, sooverride_aux/thunk/validate_overrider_parameterall see an ordinaryfunction pointer, unchanged.
Known limitation: a
_MEMoverrider's body and key-accessor are eachnamed once, overloaded purely on
(return type, parameters)- never onwhich method they override, since a qualified
IDcan't be pasted into anew declaration. At most one overrider of a given exact signature per class,
regardless of method. Pinned by
test/compile_fail_member_overrider_signature_collision.cpp.doc/modules/ROOT/pages/friends.adocis retitled "Members and Friends" inthe nav and gains a
_MEMtutorial before the existingfriendcontent,which it supersedes for classes under the caller's control.
Test plan
ctest- 203/203 passing (197 baseline +test_member_method.cpp(2cases) + 3 new compile-fail tests)
design leans on nested-class complete-class-context bodies and
function-template address deduction, both flagged as MSVC risk during
design)
doc/build_antora.sh- full site build, no new warnings; verified nostray backticks and no MrDocs artifact leakage in the touched/new
pages and six new reference pages
rolex/8example andmember.cppsnippet run manually, outputmatches expected ($5000 / $10000 / $985000, bark/hiss)
🤖 Generated with Claude Code