Skip to content

interop with std and boost any - #64

Draft
jll63 wants to merge 70 commits into
boostorg:developfrom
jll63:feature/any
Draft

interop with std and boost any#64
jll63 wants to merge 70 commits into
boostorg:developfrom
jll63:feature/any

Conversation

@jll63

@jll63jll63 commented Mar 1, 2026

Copy link
Copy Markdown
Collaborator

inter-operate with 'any'

@cppalliance-bot

cppalliance-bot commented Mar 1, 2026

Copy link
Copy Markdown

An automated preview of the documentation is available at https://64.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-10 22:59:53 UTC

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds interoperability between Boost.OpenMethod dispatch and std::any, enabling method calls where virtual parameters are passed as std::any (or references), using the contained runtime type to drive dispatch.

Changes:

  • Introduces boost/openmethod/interop/std_any.hpp with virtual_traits / registration helpers for std::any.
  • Extends vptr policies with type_vptr(type_id) to support vptr lookup directly from a type id.
  • Updates core dispatch (acquire_vptr) to optionally use virtual_traits::dynamic_vptr when provided, and adds a new std::any dispatch test.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 10 comments.

Show a summary per file
FileDescription
include/boost/openmethod/interop/std_any.hppNew std::any interop layer (virtual traits + registration helper).
include/boost/openmethod/core.hppAdds detection/branch intended to let virtual_traits provide vptr acquisition.
include/boost/openmethod/policies/vptr_vector.hppAdds type_vptr(type_id) helper and refactors dynamic_vptr to reuse it.
include/boost/openmethod/policies/vptr_map.hppAdds type_vptr(type_id) helper and refactors dynamic_vptr to reuse it.
include/boost/openmethod/preamble.hppAdds a macro for generating “has static function” detection traits.
test/test_dispatch_std_any.cppNew tests for std::any dispatch (currently with some cases compiled out).
.gitignoreIgnores generated doc output and Coverity directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadinclude/boost/openmethod/interop/std_any.hpp Outdated
Comment threadinclude/boost/openmethod/interop/std_any.hpp
Comment threadinclude/boost/openmethod/interop/std_any.hpp
Comment threadtest/test_dispatch_std_any.cpp Outdated
Comment threadinclude/boost/openmethod/policies/vptr_vector.hpp Outdated
Comment threadinclude/boost/openmethod/core.hpp Outdated
Comment threadinclude/boost/openmethod/interop/std_any.hpp Outdated
Comment threadtest/test_dispatch_std_any.cpp
Comment threadtest/test_dispatch_std_any.cpp
Comment threadinclude/boost/openmethod/policies/vptr_map.hpp Outdated
@codecov

codecovBot commented Mar 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.66%. Comparing base (5995745) to head (1eb22d8).
⚠️ Report is 15 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@ Coverage Diff @@## develop #64 +/- ##
===========================================
+ Coverage 92.66% 94.66% +2.00% 
===========================================
Files 43 45 +2 Lines 3110 2945 -165 Branches 1544 0 -1544 ===========================================
- Hits 2882 2788 -94 + Misses 188 157 -31 + Partials 40 0 -40 
Files with missing linesCoverage Δ
include/boost/openmethod/core.hpp97.36% <100.00%> (+2.42%)⬆️
include/boost/openmethod/interop/std_any.hpp100.00% <100.00%> (ø)
include/boost/openmethod/policies/vptr_map.hpp100.00% <100.00%> (ø)
include/boost/openmethod/policies/vptr_vector.hpp100.00% <100.00%> (ø)
include/boost/openmethod/preamble.hpp72.72% <ø> (+0.98%)⬆️
test/test_dispatch_std_any.cpp100.00% <100.00%> (ø)

... and 18 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

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

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

jll63and others added 21 commits March 7, 2026 12:41
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
&lsqb;CRTP&rsqb; mixin&rsqb;(https&colon;&sol;&sol;en&period;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.
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>
@jll63jll63 changed the title Feature/anyinterop with std and boost anyAug 8, 2026
@jll63jll63 linked an issue Aug 8, 2026 that may be closed by this pull request
jll63and others added 14 commits August 8, 2026 12:54
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>
`vptr` is a customization point at two levels, and neither was in the
exposition-only blueprints:
- `virtual_traits<T, Registry>::vptr(arg)` - optional; lets a traits
specialization override the default v-table lookup.
- `policies::vptr::fn<Registry>::vptr(type_id)` - the type-id-keyed
lookup the above calls.
Both arrived with 1eb22d8 ("inter-operate with 'any'") as `type_vptr`,
renamed by f086985 and 7ecd96c; neither commit updated the blueprints.
Document them, including when to implement them and why they exist, and
mention in the `any` specializations that the vptr policy must provide
`vptr(type_id)`.
Also fix the detection of `virtual_traits::vptr`: it probed callability
with a `type_id` (= `const void*`), which compiles for `std::any` and
`boost::any` only because their converting constructors accept a
`const void*`. An `any`-like type without such a constructor was
silently ignored and fell back to `dynamic_vptr`, dispatching on the
wrapper instead of the contained value. Probe with the actual argument
type instead.
Drive-bys: four `@ref policies::vptr::fn::dynamic_vptr` did not resolve
(rendered as plain text) - use `@ref policies::VptrFn::dynamic_vptr`;
drop a stray "a the" and align two stale std_any comments that claimed
the rtti policy supplies the type id.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The links to the header sources were written against a `base-url` attribute
that no longer exists, so they rendered as a literal
`href="{base-url}/include/boost/openmethod/core.hpp"`.
Point them at the headers relative to the built page, which lands in
doc/html/openmethod/. The links then work wherever the docs are deployed --
the local tree, a PR preview, or a boost.org version -- with no attribute to
define and none to go stale. This is also what dynamic_bitset does: its
antora.yml defines no base-url either.
They have to be `link:`, not `xref:`: Antora resolves an `xref:` target as a
resource id and rejects a relative path, while a `link:` target is passed
through verbatim to the stock converter.
While here, fix the label on initialize.hpp, which read
`<boost/initialize.hpp>`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The merge brought in the relative header links, but the three `any` interop
headers were added on this branch and still pointed at the `base-url`
attribute, which no longer exists. Convert them like the rest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jll63and others added 12 commits August 9, 2026 13:02
The explicit-`Registry` overload pointed at the default-registry overload's
example with a hardcoded page name, `final_virtual_ptr-08.adoc`. MrDocs
disambiguates overload pages with a content-derived hash, so adding the `any`
overloads renamed that page to `final_virtual_ptr-08ea.adoc` and the link went
dead.
It went dead silently: mrdocs-addons rewrites `xref:reference:` into a plain
`link:` on nested pages, to work around cppalliance/mrdocs#1245, and Antora
does not validate a link macro. Any cross-reference to an *overload* page is
therefore a link that rots without warning -- the two in macros.hpp are safe
only because macro page names carry no hash.
Pull in the snippet instead, with the same `include:` directive the
default-registry overload already uses. There is no page name left to rot, and
the example now comes from a file the build compiles and runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `any` headers delete twelve `final_virtual_ptr` overloads to stop the
primary from silently using `static_vptr<any>`. They are a guard, not API, and
MrDocs gave each one its own page: the overload list went from 3 entries to 15.
Guard them with `#ifndef __MRDOCS__`, as the friend declarations in core.hpp
already are. The symbol is defined only while generating the reference, so the
overloads are unchanged for every real compiler -- confirmed by
compile_fail_final_virtual_ptr_std_any.cpp, which still fails with "use of
deleted function".
This also silences cppalliance/mrdocs#1251: the malformed link on the
`aliases::final_virtual_ptr` page only appeared once the overload set grew, and
the table is empty again now, so the Antora build is back to zero errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
virtual_ptr and the intrinsic hook fill the same goal - fast access to
the v-table pointer - so combining them buys nothing; and, with an
indirect registry, it was outright broken: acquire_vptr preferred the
hook, which returns the vptr by value, and box_vptr stored the address
of the temporary - a dangling pointer read back on every dispatch
(caught by ASan as stack-use-after-return).
acquire_vptr is only called from virtual_ptr and virtual_any
construction and assignment - dispatch uses method::vptr, which keeps
the hook fast path. Make acquire_vptr static_assert that no hook
applies, and drop its now-unreachable hook branch; the remaining
branches (virtual_traits, vptr policy) return references into stable
storage, so box_vptr is safe for everything acquire_vptr can return.
Closesboostorg#87
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
make_any_virtual<Class, Any>(args...) constructs the Class and moves it
into the any - exactly what constructing the virtual_any from a value
does, with more characters and one more name to learn; and constructing
in place, the one thing a factory could add, is already covered by the
emplace member. Remove make_any_virtual, make_std_any_virtual and
make_boost_any_virtual.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
virtual_any_ref borrows an existing any instead of holding a copy, and
carries the v-table pointer for the contained value: a cheap, two-word
handle with pointer semantics, passed to methods by value - like the
reference-wrapper flavors of Boost.TypeErasure's any. The v-table
pointer is acquired once, when the handle is created, or taken at no
cost from a virtual_any. Any may be const-qualified; a mutable handle
converts to a const one.
Since a plain value does not convert to a virtual_any_ref, overriders
that take the contained value are registered with the core API; the
catch-all, which takes the handle itself, can use the macro.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A relative path cannot reach a header from any deployed site: the PR
previews publish libs/openmethod/doc alone, and boost.org serves the
headers from doc/libs/<version>/boost, not from libs/openmethod/include.
Link to the sources on GitHub instead, through a `headers-url` attribute
in antora.yml, as Boost.Test does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MrDocs develop a51d1621 validates the nodes assigned back to
symbol.doc.document and rejects `$meta`, which the deep copy carried
over from the proxy it read:
extension transform 'include': include.lua:233: field 'document':
unknown sub-field '$meta' for kind 'paragraph'
`$meta` is MrDocs' own metadata, not content, so add it to UNWRITABLE
next to `level`. Verified against 0.8.0+a51d1621bd13: the generated
adoc is byte-identical to what 0.8.0+14a36c8132df produced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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.

interop with any

3 participants

@jll63@cppalliance-bot