interop with Boost.TypeErasure - #86
Open
jll63 wants to merge 61 commits into
Open
Conversation
MrDocs now extracts `#define` directives as symbols (cppalliance/mrdocs#1192), so the macro reference no longer has to be written by hand. Move the content of the sixteen hand-written BOOST_OPENMETHOD*.adoc pages into doc comments on the macros themselves, and delete the pages. `ref_macros.adoc` stays as the curated basic/advanced index, now pointing at the generated reference pages. Two macros needed restructuring to have a single documented definition: * BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS is only ever tested, never defined by the library, so there was no directive to extract. Add a documentation-only `#define` under `__MRDOCS__`, after `default_registry`, so documenting it cannot change what it documents. * BOOST_OPENMETHOD_EXPORT_REGISTRY and BOOST_OPENMETHOD_INSTANTIATE_REGISTRY had one definition per ABI. Move the per-platform bodies into BOOST_OPENMETHOD_DETAIL_* macros so the public macros are defined - and documented - once. Rename the macro parameters NAME and ARGS to ID and PARAMETERS. The generated synopsis prints the real parameter names, and the prose has always called them ID and PARAMETERS. Convert the {{MACRO}} placeholders in doc comments to `@ref MACRO`, which MrDocs resolves to a proper xref, and retarget the guide pages' xrefs at the generated pages. This removes two perl substitutions from build_antora.sh: the one that rewrote {{MACRO}} into a hand-built relative link, and the {{BASE_URL}} pass over the macro pages, which MrDocs now handles itself via base-url. Requires a MrDocs new enough to support macros; an older one silently produces no macro pages, which breaks the reference xrefs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MrDocs escapes prose punctuation but emits markdown-link targets verbatim, so a
literal Antora resource ID survives a doc comment: the link
[Shared Libraries](xref:ROOT:shared_libraries.adoc)
comes out as `xref:ROOT:shared_libraries.adoc[Shared Libraries]`, which Antora
resolves from the reference module to the component's ROOT module. Use it on
all three registry-sharing macro pages, in place of the plain-text "see the
Shared Libraries section of the documentation" that assumed the xref could not
survive.
The link must be on one line: MrDocs parses it after the comment has been split
into lines, and a link broken across two `//!` lines falls through as escaped
literal text. That is what happened to the two [CRTP mixin] links in
inplace_vptr.hpp, which render today as
[CRTP] mixin](https://en.wikipedia...)
Shorten their text to [CRTP] so the link fits on one line inside the column
limit, and move `mixin` into the surrounding prose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every guide page links into the reference; nothing linked back out. Give each macro page a See Also section pointing at the guide that covers it, using the markdown-link-to-Antora-xref form established in the previous commit. `@see` is the right vehicle: MrDocs renders `symbol.doc.sees` under a "See Also" heading, each entry through the same inline path as description text, so a markdown link works there. Link text matches the nav labels, and a symbol is linked to a guide page only where that page actually discusses it - so BOOST_OPENMETHOD_OVERRIDERS points at Header and Implementation Files (overrider containers) while BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS points at Registries and Policies. The three registry-sharing macros move their inline guide link into See Also, so all the macro pages have the same shape. Only macro pages get these links. MrDocs writes `:relfileprefix: ../../` into its nested reference pages - by design, its template says so - and Asciidoctor prepends that to the xref target Antora resolves, so `xref:ROOT:basics.adoc` arrives as `../../ROOT:basics.adoc` and does not resolve. Macro pages sit at the reference module root, get no prefix, and work. Clearing the attribute fixes the nested pages but breaks ~1250 breadcrumb links, because the `boost::openmethod::` xrefs in the document title bypass Antora's resolver and genuinely need it. To be reported upstream: the title partial should inline the prefix itself rather than rely on a document attribute that corrupts module-qualified xrefs. Also turn three dead `@see` entries into real references: `@see indirect_vptr.` rendered as escaped plain text, and two `@see The main template for documentation.` had nothing to click. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # include/boost/openmethod/policies/vptr_map.hpp # include/boost/openmethod/preamble.hpp
ref_headers.adoc linked each public header to its source through `{{BASE_URL}}`,
which is not an AsciiDoc construct. Asciidoctor saw the inner `{BASE_URL}`,
found no such attribute and - under Antora's default `attribute-missing: skip` -
left the text alone, so the literal `{{BASE_URL}}` reached the HTML and
build_antora.sh rewrote it with perl after the site was built.
That cost 17 "skipping reference to missing attribute" warnings on every build,
indistinguishable from real ones, and left local builds with 17 broken links:
BASE_URL is only computed when a CI environment is detected, so outside CI the
perl step never ran.
Use `link:{base-url}/...` instead, give antora.yml a fallback pointing at
master - matching the one already in mrdocs.yml, which serves the same purpose
for the generated reference - and have build_antora.sh pass
`--attribute base-url=...` when it can determine the commit. A command-line
attribute outranks the component descriptor, so the exact commit still wins in
CI. The perl rewrite is gone.
The mrdocs.yml patch-and-restore stays: that base-url is MrDocs configuration
rather than an AsciiDoc attribute, and the reference extension builds a fixed
MrDocs argument list with no hook to inject one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
List the stock policies in the order a registry declares them, so the table reads the same way as `default_registry`: type_hash before vptr. Drop the "This section discusses" preamble from the shared libraries page and state the fact directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The See Also sections added in b2dcdb8 stopped at the macro pages. The other 52 links, on the C++ symbol pages, had to be dropped: MrDocs sets `:relfileprefix: ../../` on nested pages, Asciidoctor folds it into the xref target Antora resolves, and `../../ROOT:basics.adoc` is not a valid resource id. Clearing the attribute is not an option - it breaks ~1250 breadcrumb links, because the xrefs in the document title bypass Antora's resolver and need it (cppalliance/mrdocs#1245). Override `markup/a.adoc.hbs` instead, through `addons-supplemental` - the documented way to replace a few templates while falling back to the built-ins for the rest. The new branch recognises an `xref:ROOT:` href and, on a nested page, emits a `link:` rather than an `xref:`. A link macro is not an inter-document xref, so relfileprefix never touches it; `relfileprefix` reaches the reference module root and the guide sits one level above it, hence the extra `../`. At the root the href is passed through unchanged, so the macro pages keep emitting real xrefs and Antora still validates them. Everything else in the file is upstream verbatim, so it diffs cleanly against a newer MrDocs, and the header comment says when to delete it. With that in place, restore the 52 links: the error types and policy categories in preamble.hpp, the smart-pointer traits and aliases in interop, the stock policies, `method`, `use_classes` and `virtual_ptr` in core.hpp, `initialize` and `finalize`, and the two inplace_vptr mixins. 73 guide links now render across 65 reference pages, at depths 0, 2 and 3. The whole-site link check is unchanged at 19 broken links, all of them the `file://` edit-page links a local build always produces - in particular the breadcrumbs are intact, which is what the earlier attempt at clearing relfileprefix broke. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The built-in see-also partial renders each @see entry through `doc/block/see`, i.e. as a block, and MrDocs separates blocks with a blank line - which AsciiDoc reads as a paragraph break. A symbol with several @see entries therefore got a paragraph each, one link per line. Override the partial to render the entries inline instead. `doc/inline-container` is what `doc/block/see` reaches through `doc/block/paragraph`, so going straight to it drops the block separation; the entries are then joined with ", " and the whole list is one paragraph. The blank line before the section closes keeps the page footer out of it. Four @see entries were sentences rather than bare references, which a comma join would have read as "... for data members., Error Handling". Move the clause into the description, where it is more visible anyway, and leave the reference bare: no_overrider and ambiguous_call pointing at bad_call, the policies namespace pointing at registry, and inplace_vptr_derived pointing at inplace_vptr_base. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The base-url MrDocs puts behind every "Declared in <header>" link comes from mrdocs.yml, and the Antora extension invokes MrDocs with a fixed argument list, so pointing it at the commit means editing the file in place. The restore ran at the end of the script, which `set -e` skips: a build that failed anywhere after the edit left mrdocs.yml patched, and the next run then copied the patched file to mrdocs.yml.bak and restored that - losing the original base-url for good. Move the restore into an EXIT trap armed right after the backup is taken, so it runs whether the build succeeds or aborts. Paths are absolute so the trap does not depend on the working directory at exit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It was the only bare label in the navigation with somewhere obvious to point: the reference module's index page, which lists the namespaces and the macros. "Basic Features" and "Advanced Features" stay labels, having no page of their own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add virtual_traits<std::any&>, and test dispatch on a std::any passed by mutable lvalue reference and by xvalue reference. virtual_<std::any&> silently bound the generic virtual_traits<Class&>, whose cast goes through optimal_cast - a static_cast/dynamic_cast that cannot compile against an overrider taking a reference to the contained type. Add a specialization with the full member set. virtual_traits<std::any&&>::cast passed its parameter to std::any_cast as an lvalue, selecting the any_cast(any&) overload, which asserts is_constructible_v<U, _Up&> - false for an rvalue reference U. Forward it as an rvalue so any_cast(any&&) is selected. Also fix dynamic_vptr in that same specialization: it named the rtti policy, which has no type_vptr, and passed a type_info by value where a type_id is wanted. It compiles today only because acquire_vptr normalizes every reference category to const& before looking dynamic_vptr up, so the body is never instantiated. The mutable reference overriders cannot use BOOST_OPENMETHOD_OVERRIDE: the macro locates the method by checking that the overrider's parameter types can be passed to the method's forwarder, and nothing converts to a mutable lvalue reference to std::any. Register them via method<...>::override<Fn> instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add interop/boost_any.hpp, mirroring interop/std_any.hpp: virtual_traits specializations for const boost::any&, boost::any& and boost::any&&, and a use_boost_any_types registrar. Dispatch is on the type of the contained value, obtained from boost::any::type(), which yields the same std::type_info object std_rtti keys on. boost::any_cast is looser than std::any_cast. Its any& overload is unconstrained, so it binds an rvalue reference to the value held in an lvalue any - letting an overrider move out of an any the caller still owns - and its const any& overload fails inside Boost.Any rather than at the trait. Constrain cast with SFINAE in all three specializations, so the bad instantiations are removed from the overload set instead. Two compile_fail tests cover them; the diagnostic is the compiler's own overload resolution failure, whose wording varies, hence the loose fail_regex. Rename use_any_types to use_std_any_types, for symmetry with use_boost_any_types. One registrar cannot serve both: it names the any type twice, as the root class and as the synthetic base of the contained types, and that root must be the class the method registers for its virtual parameter. Boost.Any is not in the transitive closure of the library's declared dependencies, so declare it in the test Jamfile, and in CMakeLists.txt alongside Boost::smart_ptr - the mrdocs build compiles every header. Also document both any headers in ref_headers.adoc; std_any.hpp was missed when it landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A paragraph of a doc comment reading
include:<path>[#<tag>[;<tag>...]]
is now replaced by the file, or its `// tag::name[]` regions, rendered as
a code block. The point is that the example on a reference page is a
region of a file the build compiles and runs, so it cannot drift from the
library without a build failing.
Implemented as a MrDocs corpus transform in Lua, per Alan de Freitas'
suggestion. Regions are selected in file order the way Asciidoctor's
`tags=` attribute selects them, and each contiguous run is dedented on its
own before the runs are joined, so a snippet can draw its setup from
namespace scope and its body from inside a test case and still render
flush. A missing file or unknown tag aborts the build naming both.
The transform rebuilds a comment's whole block list rather than patching
the marker in place, because three gaps in the 0.8.0 extension API leave
no alternative: array proxies expose no indexed write to Lua, proxies read
out of the corpus are rejected as setter input, and `level` is refused by
the generic setter. The header comment records all three; if they are
fixed upstream the script collapses to a few lines.
Snippets live in doc/modules/ROOT/snippets, which Antora ignores as an
unrecognised family, and are built and run with the examples -- Boost
keeps tests under test/.
Converts two `virtual_ptr` examples, both of which were broken:
- `operator=(std::nullptr_t)` had a mangled opening fence written after
the body, so the example rendered as escaped prose run onto one line,
trailed by a stray `//!` and an empty code block.
- `cast()` had an empty `@code`/`@endcode` pair.
Generating the reference over the whole corpus produces byte-identical
output apart from those two pages, so the block-list rebuild is lossless.
Replaces the 24 remaining `@code` blocks in core.hpp with markers into doc/modules/ROOT/snippets/virtual_ptr.cpp, which the build compiles and runs. core.hpp now has no `@code` left. A marker names a class-setup tag only where the example deviates from the norm -- that is, where it relies on non-polymorphic classes, which is the point of those examples. The other markers render the body alone, since repeating four lines of Animal/Dog on every page is noise. Deletes test/test_virtual_ptr_doc.cpp. Its only purpose was to compile these examples a second time, by hand, with nothing keeping the two copies in step; the snippets do that job now. Both build systems glob, so no build file changes. Note that b2 builds only test/, so it no longer compiles these examples at all -- adding a Jamfile for the snippets would route them back through the test build we just moved them out of. Two examples were attached to the wrong overload: the assignment operators briefed "from a (const) smart pointer" and "move-assign from a smart pointer" both showed assigning from a *virtual* pointer, which is what the following two overloads document, with near-identical bodies. They get bodies that assign from a bare std::shared_ptr, matching their briefs. Being compiled from now on, they cannot drift again. A third malformed block is fixed by the conversion: the smart-pointer default constructor had a stray `@par Example` inside its `@code` fence. Generating the reference produces exactly 20 changed pages -- 24 blocks less the four that share a doc comment with another -- and no other difference across the 353 generated files.
The constructor from another `virtual_ptr` introduced its examples with "Assigning from...", though it documents a constructor. Both that comment and the assignment one prepended the non-polymorphic class setup to two consecutive examples, so the same five lines rendered twice on one page. Only the first names the setup tag now. Also backticks `virtual_ptr` in those lines: unformatted, it rendered as escaped text beside the correctly formatted mention in the third paragraph of the same section.
Two gaps: the 13 examples on the `virtual_ptr<SmartPtr>` specialization were all `std::shared_ptr`, and the three interop headers had no examples at all -- `unique_virtual_ptr`, `make_unique_virtual` and the `virtual_traits` specializations were documented in prose only. Adds unique_ptr examples to the four move overloads of `virtual_ptr<SmartPtr>`, alongside the shared_ptr ones, plus a static_assert on the copy constructor recording that a move-only smart pointer cannot be copied from. Those five are where the two pointer flavours actually diverge; copying an example that only differs in the pointer type would not earn its place on the page. Adds snippets/smart_pointers.cpp and snippets/intrusive_ptr.cpp, and markers on all three interop headers: each `virtual_traits` specialization now shows a method declared with that smart pointer as a virtual parameter, and each alias and factory shows a use. The by-reference specializations show what the by-value ones cannot -- that passing by const reference does not bump the reference count. Grouped two files rather than three: shared_ptr and unique_ptr share a class hierarchy, while intrusive_ptr needs an intrusive_ref_counter base. 16 reference pages change, and no others.
`make_shared_virtual`, `make_unique_virtual` and `make_boost_intrusive_virtual` showed the returned pointer's vptr rather than what the pointer is for. Each now dispatches a call. Each needs a method taking a `virtual_ptr` to the smart pointer, which is a different signature from the by-value and by-reference ones already in the files, hence the extra namespaces. The unique version reads `poke(std::move(animal))`: the pointer is move-only, so passing it to a method consumes it. That is worth showing on the page rather than hiding behind a temporary.
The eight policies headers documented registry composition entirely in prose. policies/static_rtti.hpp was worse than empty: its example read `TODO` followed by `include::example$static_rtti.cpp[tag=all]`, a tag that does not exist -- examples/static_rtti.cpp uses Quickbook `//[ all` markers, not Antora ones -- so the page shipped a broken instruction. Adds snippets/policies.cpp, one registry per policy since policies are registry-level and several are mutually exclusive, and snippets/static_rtti.cpp, which needs its own translation unit because the policy has to be selected before <boost/openmethod.hpp> is included. The tagged region is usually the registry declaration, because that is the line a user writes; the classes, method and assertions around it are compiled but untagged, so the rendered snippet stays short while the whole thing is verified. Writing them turned up a constraint the reference understates. Composing `std_rtti` with `vptr_vector` and no `type_hash` throws `std::bad_alloc` on the first `initialize()`: the vector is indexed by the type id, and `std_rtti` makes a type id a pointer, so it is sized to the address space. The `fast_perfect_hash` example now says so. Also drops a stray `@ref` from stderr_output.hpp's brief, which was rendering "Writes" as a broken code reference on that page and on the three listing pages that repeat the brief.
preamble.hpp's four error examples and the accompanying fix, plus the programs in initialize.hpp and inplace_vptr.hpp, now come from compiled snippets. What is left is pseudo-code and stays: macros.hpp's eight synopses, and the DLL incantation in preamble.hpp and default_registry.hpp, which shows IMPORT and EXPORT of one registry together and so cannot compile in a single translation unit. The four error examples live in one snippets/errors.cpp, each in its own registry so that one deliberate mistake does not mask another. That needs a marker policy carrying an integer: registries deriving from the same `registry<...>` specialization share one state, so a single alias would have pooled the registrations -- the same device, and the same reason, as `test_registry_` in test/test_util.hpp. Compiling them showed the first example did not demonstrate its own error. It registered `Animal`, the method's virtual parameter, and left an unused `Dog` unregistered; that raises nothing. What raises `missing_class` is the reverse -- registering `Dog` and leaving `Animal` unregistered -- so the example now does that. The markers also needed a blank `//!` line above them. The `@code` fence used to end the preceding paragraph; without it the marker would have been swallowed into the prose line above and never matched.
A registry's identity is the `registry<Policy...>` specialization: state, class and method lists, dispatch tables and `static_vptr` are all keyed on it. Two structs deriving from the same specialization are therefore one registry, sharing everything. Nothing said so. The guide said only to derive a class rather than typedef, which invites exactly the wrong conclusion -- that the class is the registry. Writing snippets/errors.cpp ran straight into it: four registries derived from one alias pooled their registrations, so three of the four error examples silently stopped raising their error. The trap is worst in a case the library recommends, isolating one set of methods from another: two such registries would naturally carry the same policies and so would silently be one. The way out is to give each a policy of its own, which is what test_util.hpp's `test_registry_` does and what snippets/errors.cpp had to reinvent. Both are now documented. The example is a `static_assert` on `registry_type`, which states the rule exactly and cannot go stale. It lives under examples/ rather than snippets/ because the guide reaches it with `include::example$`, which cannot see snippets/ -- Antora ignores the directory as an unrecognised family. The reference markers point at the same file, so there is one copy.
macros.hpp had eight @code blocks and not one of them showed how to use a macro: they are all synopses of what the macro expands to, under Implementation Notes. The page explained dispatch semantics precisely and never declared a method. Adds snippets/macros.cpp, one file for the macro family so the rest can add tags to it, and a marker on BOOST_OPENMETHOD placed above Implementation Notes so usage comes before internals. The rendered example is the declaration and two calls, nothing else. The classes, the registration, the overriders and initialize() are in the file but outside the tags -- overriders belong on the BOOST_OPENMETHOD_OVERRIDE page, and the `// hiss` and `// bark` comments already tell the reader they exist. The calls write to std::cout, which on its own proves nothing, so the snippet redirects std::cout to an ostringstream and checks what came out. Both the redirect and the BOOST_TEST sit outside the tagged region, so the page shows the idiomatic printing form while the build verifies that dispatch really picks the two overriders.
Tags the two overriders already in snippets/macros.cpp and points the macro at declare;override;call, so the page shows the method declaration for context, both overriders, and the calls they answer -- the same example BOOST_OPENMETHOD renders, with the overriders no longer elided. Placed above Implementation Notes, matching BOOST_OPENMETHOD.
…at it The two pages were rendering overlapping halves of one example. Now BOOST_OPENMETHOD's Example section is a link to BOOST_OPENMETHOD_OVERRIDE's, which shows the declaration, both overriders and the calls -- a method declared with nothing overriding it was the less useful half anyway. The link is a markdown link whose target is an Antora resource ID with a fragment, which the markup/a template emits verbatim; Antora resolves it to BOOST_OPENMETHOD_OVERRIDE.html#_example. Macro pages sit at the reference module root, so no relfileprefix is in play. snippets/macros.cpp is unchanged: the `declare` and `call` tags are still rendered, now only through the overrider page.
Four registrations of the same hierarchy - Cat and Dog under Animal, Bulldog under Dog - showing what does and does not describe the inheritance. All four were run before being written down. The two that work: one call listing everything, or several calls where each class appears alongside its direct bases, `Dog` repeated to attach `Bulldog` to it. The two that do not differ in how they fail. Registering one class per call describes no inheritance and initialize reports missing_base. But listing a class with an ancestor in place of its direct base - `(Animal, Bulldog)` when Bulldog derives from Dog - is accepted: initialize succeeds, and a call passing a Bulldog quietly runs the overrider for Animal rather than the one for Dog. That silence is what makes it worth a paragraph. These stay as @code. They are contrasting registrations of one hierarchy, two of them wrong on purpose, so a compiled snippet would have to be four translation units to say what four blocks say plainly. Also reorders the macro table in ref_macros.adoc to declare, override, register rather than alphabetically.
Points the macro at the rolex_3 example, which is the case the macro exists for, already compiled and run and already tagged: roles.hpp declares `pay` and supplies a default overrider that three translation units include, and salesman.cpp adds a more specialized one. The second block is what makes the first legible -- it shows the specialized overrider using plain BOOST_OPENMETHOD_OVERRIDE, because it is defined once, and reaching the header's overrider through BOOST_OPENMETHOD_OVERRIDER. The page also said only that the overrider is "marked inline", which does not tell a reader when to reach for it. Adds the reason, from the implementation comment above the macro and from test/dynamic_loading/shared_overrider.hpp: inline is what makes the repeated definition legal, and it is what lets initialize merge the repeated registrations instead of recording them as distinct overriders for one class and marking the call ambiguous.
The four regions in snippets/errors.cpp rendered BOOST_CHECK_THROW(initialize<...>(), missing_class) on three reference pages. Boost.Test scaffolding is not what a reader needs, and the registry carried throw_error_handler, which implied you must select that policy to see the error at all. Each region is now the faulty registration and the one operation that reports it, with the description the library writes as a comment above it. The registry drops throw_error_handler and keeps the default handler. The harness moved out of the tags. It has to install an error handler because of two things the library does: the `output` policy writes to the C stderr stream, which a streambuf redirect cannot capture, and abort() follows as soon as the handler returns, so only throwing gets control back. The installed handler writes the same description to std::cerr -- same std::visit over the error variant as default_error_handler::default_handler -- and throws a type the caller catches, both outside the tagged regions. The assertions check the captured text, so the comments cannot drift from what is printed. The notes on the three pages now describe the default behavior, which is what the examples show.
Applies the treatment errors.cpp got to the rest, and finishes the job on errors.cpp itself. Assertions split two ways. Pointer and v-table identity -- the ~72 in virtual_ptr.cpp, plus the use_count ones -- stay as BOOST_TEST: stating which object and which v-table a pointer holds is what those examples are for. The 18 that checked what a method returned now print it, with the output as a trailing comment and the capture and BOOST_TEST outside the tag, the way macros.cpp already worked. Two shared helpers in capture.hpp replace what would have been six copies of the same redirect. The two error-handler examples in policies.cpp keep their try/catch -- there the fact that the error reaches your code is the example, not scaffolding -- and lose only BOOST_CHECK_THROW. initialize.cpp's exit(1) is neutralised by a #define outside the tag, as it is an example of what a program does, not something a test may do. errors.cpp is split into four translation units, one per mistake. It had carried a registry argument on every line so that one deliberate mistake could not poison another; separate translation units give that isolation for free, so the examples now use the default registry and no line mentions a registry at all. The marker-policy device and the reporting<N> alias go with it. The call example defines BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS itself rather than relying on a debug build, which is also what its page documents; verified at -O2 with no external define.
`{ /* ... */` on the brace line was clang-format's doing, and it renders
awkwardly. A line comment on its own line cannot be folded back up.
The example was on the `template<class Registry, typename Arg>` overload, so the plain `final_virtual_ptr(obj)` -- the one a reader reaches for -- had none, and the example looked as though it had been lost. It now sits on the default-registry overload, and the explicit-registry one links to it. That link needed the markup/a override extended. It rewrote `xref:ROOT:` on a nested page as a `link:`, because MrDocs sets relfileprefix there and Asciidoctor folds it into an xref target; a link from one reference page to another has exactly the same problem, and the same fix, minus the extra `../` since those targets are already relative to the reference module root.
`final_virtual_ptr` consults no dynamic type -- that is the whole point.
It takes the argument's static type as the object's class and uses
static_vptr for it, skipping the RTTI lookup. "Known dynamic type" says
close to the opposite.
"Known exact class" is also the phrasing the library already uses for the
same idea, in the three interop headers ("Since the exact class of the
object is known") and in static_rtti.
The two `virtual_ptr::final` members get it too. Their briefs were
"Construct a virtual_ptr from a reference to an object" and "... from a
smart pointer to an object", which describe an ordinary constructor and
leave out the precondition entirely.
The comment described what it does -- take the static type as the class, use static_vptr -- without saying why anyone would want that. Two reasons, now stated: it looks nothing up at runtime, where constructing from a reference or a pointer goes through the rtti policy for the dynamic type and the vptr policy for the v-table; and it is the only way to build a virtual_ptr under static_rtti, which has no dynamic type to consult and disables those constructors.
Commit 7ecd96c renamed acquire_vptr's registry-policy fallback from dynamic_vptr(arg) to vptr(arg), but the policies' object-taking overload is still named dynamic_vptr - vptr(type_id) is the id-taking one. The fallback is reached whenever a plain virtual_ptr is constructed from a reference or pointer to a polymorphic object, so every such construction failed to compile; stale incremental builds masked it. Restore dynamic_vptr, matching method::vptr's own fallback. Also update test_dispatch_boost_any.cpp's has_dynamic_vptr static_asserts to has_vptr; the rename had updated the std counterpart only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
virtual_any<Any, Registry> is to `any` what virtual_ptr is to a pointer: it combines an `any` - held by value - with the v-table pointer for the contained value, so methods dispatch on the contained type without looking it up on every call. The v-table pointer is acquired at construction: from the dynamic type of an existing `any` (a hash table lookup via virtual_traits<const Any&>::vptr), or statically when the contained type is known (the value constructor, emplace, and the make_*_virtual factories use static_vptr, like make_unique_virtual). Assignment and emplace re-derive it, and no mutable accessor to the `any` is exposed, so the vptr always matches the payload. Methods take virtual_any by const, mutable or rvalue reference; overriders receive the contained type by a reference of a compatible category - the casts delegate to the existing virtual_traits<Any cvref> specializations - or the virtual_any itself, unchanged, for a catch-all overrider. Passing virtual_any by value is rejected: it would copy the payload on every call. The value constructor makes overrider parameters convertible to the method's, so BOOST_OPENMETHOD_OVERRIDE locates virtual_any methods; the mutable lvalue case still needs method<...>::override<Fn>, as with virtual_<Any&>. No changes to core.hpp: dispatch reads the stored vptr through the boost_openmethod_vptr hook (a friend, so ADL only finds it when a virtual_any is an argument), and the detail templates (is_virtual, parameter_traits, validate_method_parameter, validate_overrider_parameter, select_overrider_virtual_type_aux) are specialized on the concrete class. The exact-pair validate_overrider_parameter specializations disambiguate with the generic <T, T> one, which partial ordering ranks neither above nor below <virtual_any cvref, T2>. The class is generic: it only requires virtual_traits<Any cvref> with vptr and cast, so it serves std::any, boost::any, and future any-likes. std_any.hpp and boost_any.hpp provide the default-registry aliases virtual_std_any and virtual_boost_any and the make_std_any_virtual and make_boost_any_virtual factories. They also delete the final_virtual_ptr overloads for their `any` type: the primary template would silently use static_vptr<any> - the v-table of the `any` root class, not of the contained value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
acquire_vptr and method::vptr detected a traits-supplied vptr with has_vptr<virtual_traits<...>, type_id>, i.e. by asking whether traits::vptr is callable with a type_id (a const void*). The member takes a reference to the any, so the probe only passed because std::any and boost::any happen to have a greedy converting constructor that accepts a const void*. An any-like type without such a constructor would silently fail the probe and fall through to the vptr policy's dynamic_vptr, which keys the lookup on typeid(wrapper) - the wrapper class itself, not the contained value. Probe with the actual argument type instead, making the detection ask the intended question: does this specialization provide a vptr member. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An overrider may take the method's `any` parameter itself, acting as a catch-all for contained types that have no more specific overrider. The virtual_traits cast<U> members passed U to any_cast unconditionally, and any_cast to the any's own type throws unless the any contains an any. Return the argument unchanged when U is the any, by value or by any reference category - as virtual_any's traits already did. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The registrars expanded their whole template parameter pack into use_class_aux instantiations, so a trailing registry argument - accepted, and used to select the registry - was also registered as a class derived from the any root. Harmless, but wrong. Factor the expansion into detail::use_any_types_aux (in virtual_any.hpp, shared by all the any interop headers), driven by extract_registry's `others` list, which excludes the registry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add interop/boost_type_erasure.hpp: dispatch on the type bound to a boost::type_erasure::any, via virtual_traits and the vptr policies' type-id-keyed entry point - the same approach as the std::any and boost::any interop, with no custom rtti policy or registry. Dispatch keys on the std::type_info returned by typeid_of, so the only requirement on the user's Concept is typeid_<>, which `relaxed` already implies. virtual_traits specializations, generic over the Concept, cover the owning flavor by const, mutable and rvalue reference, and the reference-wrapper flavors (any<C, _self&>, any<C, const _self&>) by value - they are cheap, two-word handles, and te's idiomatic parameter carriers. All use the owning flavor as their virtual_type, so a single registered root per Concept serves every parameter form; overriders receive the bound type by a reference of a compatible category, or the any itself as a catch-all. type_erasure's any_cast has no rvalue overload, so the xvalue trait moves the result of a mutable-reference cast - for the owning flavor only, since the rvalue-ness of a reference wrapper says nothing about the referent's ownership. Casts that cannot work (mutable access to const-bound values, moving out of borrowed referents) are removed from the overload set, mirroring the boost::any constraints. use_type_erasure_types<Any, T...> registers the bound types under the Concept's root, normalizing Any to the owning flavor. virtual_any composes with no extra code: virtual_any<any<Concept>> looks the v-table pointer up once, at construction - recovering O(1) vptr acquisition, which the concept-interface-injection approach sketched in boostorg#21 obtained at the cost of naming the policy inside the user's Concept. The final_virtual_ptr overloads for type_erasure::any are deleted: the primary would silently use the root's static v-table pointer. Dispatch on the reference flavors is on the type bound at construction, never the C++ RTTI dynamic type of the referent; an empty relaxed any yields typeid(void), reported as missing_class under runtime checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MSVC's /std:c++17 does not imply /permissive-, and in permissive mode MSVC injects friend functions into the enclosing namespace, where detail::acquire_vptr's unqualified call finds them. Called with a plain `Any`, boost_openmethod_vptr was viable through virtual_any's implicit converting constructor - which acquires the v-table pointer, calling the friend again. The recursion is unconditional: release builds failed with warning C4717 under /WX, debug builds overflowed the stack at runtime. Constrain the friend's parameter to a deduced type that must be exactly this virtual_any, so no implicit conversion can make it viable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`virtual_any` shipped with tests but no narrative documentation: nothing in the nav mentioned `any`, no guide page covered it, and the reference pages carried no examples. Add an "Interoperation with Other Libraries" page under Advanced Features, structured to take a `boost::intrusive_ptr` section later. It covers, for `std::any`: why dispatch on an `any` at all, registering the contained types, `virtual_std_any` and where its v-table pointer comes from, what overriders receive, the three reference categories and why the macro cannot express the mutable one, and when to prefer a plain `virtual_<const std::any&>` instead. `boost::any` gets a mention rather than a repeat. The page's example is a new top-level doc example. The reference examples are regions of doc/modules/ROOT/snippets/virtual_any.cpp, pulled in with `include:` markers, so they are compiled and run like the rest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `any` headers aliased their wrapper type and their `make_` function but not the registration helper, so a program that imported `aliases` still had to spell `boost::openmethod::use_std_any_types` - as the doc example did. Alias them too, and let the example use `aliases` like the others. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`virtual_traits<const virtual_any&>::cast` returns the wrapper unchanged when the overrider asks for it, which is how a catch-all overrider is written. The `std::any` and `boost::any` traits had no such case: they always `any_cast` to the overrider's parameter type, so an overrider taking `const std::any&` looked for an `any` stored inside the `any` and threw `bad_any_cast` at run time - the overrider was selected correctly, only the cast was wrong. Give the six `cast` overloads the same `if constexpr` as `virtual_any`, so a method with a `virtual_<const std::any&>` parameter - or `&`, or `&&` - can have a catch-all, as one with a `virtual_any` parameter already could. The new tests also cover an `any` virtual parameter dispatching alongside a `virtual_ptr` in the same method, which had no coverage either. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The page opened on `virtual_std_any`, which put the wrapper - an optimization - before the plain thing it optimizes. Lead with `virtual_<const std::any&>` instead: the example loses the construction dance and shrinks to a registration, four overriders and four calls. `virtual_std_any` becomes a section of its own, saying what it buys (the v-table lookup happens once, or not at all) and what limits it: the wrapper is not what an overrider receives, so an overrider cannot pass it on and save the lookup again. Only a catch-all overrider gets it. Also note that `any` virtual parameters and ordinary ones mix freely in a multi-method. The example and the reference snippets now use the classes and overriders of test/test_dispatch_std_any.cpp, so a reader moving between them meets one cast rather than two. `float` is registered without an overrider of its own, which is what the catch-all demonstrates - previously that role fell to `int`, which read as if it were registered for no reason. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n MSVC `BOOST_OPENMETHOD` only declares a forwarder function template; it does not instantiate `method<...>`. The guard against a by-value `virtual_any` lives in the `method` class body, so GCC and Clang - which instantiate the class at the declaration - diagnosed it, while MSVC waited until the method was used. The test never used it, so it compiled clean and the `*fail` target failed on both Windows Drone stages. Call the method in `main()`, like every other compile-fail test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The virtual_any, std_any and boost_any entries spelled the source link as
`{{BASE_URL}}/...`, which Antora does not substitute, so the three links
rendered with the placeholder as literal text. Use `{base-url}`, the
attribute defined in antora.yml and used by the other 17 header links.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every section of the page is about dispatching on the type contained in an `any`, but the title, the file name and the opening paragraph all promised a broader page. Rename interop.adoc to interop_any.adoc, retitle it "Interoperation with `any`", and drop the intro's "or a pointer class of their own" clause, which anticipated content the page does not have. Update the nav entry, the page anchor, and the eight `@see` links in the interop headers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # include/boost/openmethod/interop/std_any.hpp # test/test_dispatch_std_any.cpp
…mples Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
An automated preview of the documentation is available at https://86.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-08-08 20:20:15 UTC |
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.
No description provided.