interop with std and boost any - #64
Conversation
|
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-22 17:50:51 UTC |
There was a problem hiding this comment.
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.hppwithvirtual_traits/ registration helpers forstd::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 usevirtual_traits::dynamic_vptrwhen provided, and adds a newstd::anydispatch test.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
include/boost/openmethod/interop/std_any.hpp |
New std::any interop layer (virtual traits + registration helper). |
include/boost/openmethod/core.hpp |
Adds detection/branch intended to let virtual_traits provide vptr acquisition. |
include/boost/openmethod/policies/vptr_vector.hpp |
Adds type_vptr(type_id) helper and refactors dynamic_vptr to reuse it. |
include/boost/openmethod/policies/vptr_map.hpp |
Adds type_vptr(type_id) helper and refactors dynamic_vptr to reuse it. |
include/boost/openmethod/preamble.hpp |
Adds a macro for generating “has static function” detection traits. |
test/test_dispatch_std_any.cpp |
New tests for std::any dispatch (currently with some cases compiled out). |
.gitignore |
Ignores generated doc output and Coverity directory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #64 +/- ##
===========================================
- Coverage 94.81% 94.68% -0.14%
===========================================
Files 95 95
Lines 3956 4043 +87
Branches 1947 1996 +49
===========================================
+ Hits 3751 3828 +77
- Misses 151 158 +7
- Partials 54 57 +3
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
# Conflicts: # include/boost/openmethod/policies/vptr_map.hpp # include/boost/openmethod/preamble.hpp
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>
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>
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 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>
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. Closes boostorg#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>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Conflict in the four virtual_ptr ctor/assign doc comments: this branch no longer routes virtual_ptr through boost_openmethod_vptr. Kept this branch's wording, and reworked the incoming reference page to match: the hook is for virtual_ parameters only, and wrapping an object that provides one in a virtual_ptr is rejected at compile time (acquire_vptr's static_assert). Dropped the paragraph the merge brought into virtual_ptr_alt.adoc, which claimed virtual_ptr honors the hook. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
method::vptr tries virtual_traits::vptr between the boost_openmethod_vptr hook and the vptr policy - it is what the any interop rides on - but the "how a vptr is deduced" lists on `method` and BOOST_OPENMETHOD only had three steps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # doc/modules/ROOT/pages/virtual_ptr_alt.adoc
Dispatching on a std::any or boost::any keys on the std::type_info returned by any::type(). That is a valid type_id only for a registry whose rtti policy identifies classes by &typeid(T). Under any other policy the key is meaningless, and type_id being const void*, the conversion compiles silently and the call resolves to the wrong v-table or reports a spurious missing_class at run time. Assert the requirement in virtual_traits::vptr, via one detail helper per header, and document it in the reference comments and the guide. The assert is in the vptr body rather than at class scope so that it fires only when the RTTI-based lookup is actually used. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"the rtti policy must derive from std_rtti" describes how the check is implemented. What is required is that the policy be std_rtti. Say that, in both any headers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Turn detail::is_virtual_any_aux into IsVirtualAny, a variable template inside the OPEN/CLOSE_NAMESPACE_DETAIL_UNLESS_MRDOCS block, so MrDocs documents it as exposition only, the way IsPolymorphic, IsSmartPtr and SameSmartPtr already are. The PascalCase name follows those; the block puts it in namespace detail for every compiler other than MrDocs, so the constraint it appears in resolves to a documented symbol. Add compile_fail_virtual_any_from_ref, which copy-initializes a virtual_any from a virtual_any_ref. That is ill-formed because the value constructor is constrained away and storing the handle would take two user-defined conversions. Without the virtual_any_ref specialization the value constructor accepts the handle, stores it inside the `any`, and looks up static_vptr for a type that is not a registered class, which is null: an assertion failure in a debug build, and a null v-table pointer carried to the first dispatch in a release one. msvc needs /permissive- for that file. In its default mode, which /std:c++17 does not turn off, it accepts the extra user-defined conversion and compiles the file, so the test would not fail. The b2 target is therefore spelled out rather than globbed. Note the constraint does not actually protect msvc users building in the default mode - and the direct-initialization form, `virtual_any<std::any> va(ref)`, is worse: one user-defined conversion suffices there, so no compiler rejects it and the handle is stored with a null v-table pointer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The handle's documentation already called it "a cheap, two-word handle with pointer semantics", and methods take it by value - but its name said reference, its only accessor was `get() const -> const Any&`, it had no assignment operators, and it shared a header with the type it is the counterpart of. Rename it, move it to <boost/openmethod/interop/virtual_any_ptr.hpp>, and give it virtual_ptr's shape: `element_type`, `get`/`operator->`/`pointer` returning a pointer, `operator*`, one assignment operator per constructor, deduction guides, and free `==`/`!=`. std_any.hpp and boost_any.hpp keep including only virtual_any.hpp, so the handle is opt-in, like the interop headers themselves. The accessors are const in both instantiations. Handing out a mutable `any&` would let the contained value be replaced behind the handle's back, leaving it carrying the v-table pointer of the previous value - the invariant the whole interop rests on. `Any`'s constness governs what overriders may take, which is where that distinction already lived, in virtual_traits::cast. The operations that do change what the handle designates re-derive the pointer, and are safe for that reason: assignment rebinds it, as on virtual_ptr; `emplace` replaces the contained value, as on virtual_any, and is guarded by a static_assert for a const handle. The rvalue assignment overloads are deleted, mirroring the constructors: virtual_ptr's smart pointer specialization accepts rvalues because it owns what it stores, and this does not. The two guides taking a virtual_any deduce its registry rather than defaulting to BOOST_OPENMETHOD_DEFAULT_REGISTRY, which virtual_ptr cannot do. virtual_traits::cast collapses to a single virtual_traits<Any&, Registry> call, `Any&` being `const virtual_type&` exactly when `Any` is const. It keeps reading the private `obj`: an overrider taking `Dog&` needs the mutable `Any&` the public accessors withhold, and confining that to one place is the point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Its invariant - the v-table pointer matches the type of the value in the `any` - cannot be maintained: the handle points to an `any` owned by someone else, who can replace the contained value behind its back. The handle is then stale, and the next call dispatches to the wrong overrider and casts to the wrong type, silently. It buys nothing that is not already available: a method parameter of type `const virtual_any&` caches the v-table pointer just as well, at no copy, and with the invariant intact. The only ground the handle covers on its own is precisely the one where the invariant is unenforceable. The cost of keeping it was a header, three deduction guides, ==/!=, its own virtual_traits and the whole set of detail specializations, plus the defensive machinery guarding the hole: const-only get/*/->, `emplace` as the sole mutation route, deleted rvalue overloads. And it was crippled anyway - overriders on the contained value could not use BOOST_OPENMETHOD_OVERRIDE. IsVirtualAny stays: it still keeps a `virtual_any` of another specialization from being stored inside the `any`. The /permissive- exception in the compile-fail tests went with compile_fail_virtual_any_from_ptr, so the b2 glob loses its exclusion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Remove use_std_any_types and use_boost_any_types. The registration they performed carried no information the library does not already have: each type always got the `any` root as its sole base, and every useful type is statically visible at an instantiation point the library controls. An inline registrar variable template (detail::use_any_classes) is now odr-used from the any interops' virtual_traits - cast registers the overrider's parameter type under the root, vptr registers the root - and from virtual_any's value constructor, value assignment and emplace, which register the stored type. All registrars run at static initialization, like every other registrar, so the types are known before initialize(). A registered type with no specific overrider still falls back on the catch-all overrider. A type never named statically anywhere remains a missing_class error at the dynamic vptr lookup - including when constructing or assigning a virtual_any from an `any` that contains one. The inline variable has vague linkage; the dlclose exposure is the same as the existing inplace_vptr_use_classes precedent. Under hidden visibility, per-module copies are retained by augment_classes' (type, static_vptr) dedup and all get patched at initialize(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
virtual_any delivered its v-table pointer through a boost_openmethod_vptr hidden friend - the hook meant for objects that carry their own pointer. That is the wrong category for a wide type, and it cost: - the friend returned the pointer by value, where virtual_traits::vptr is required to return a reference, so that an indirect registry observes a re-initialize; - has_vptr_fn<virtual_any> was true, so acquire_vptr rejected it with a message written for inplace_vptr classes; - the friend needed a deduced-Self constraint, because MSVC's permissive mode injects hidden friends into the enclosing namespace, where a plain `any` became a candidate through its implicit conversion to virtual_any, and the conversion acquires the pointer, calling the friend again; - the `any` root class was registered only as a side effect of cast's non-catch-all branch, not by dispatch itself. Replace it with vptr on the three virtual_traits specializations, reading the cached pointer through a private vptr_ref(). virtual_any now follows the same convention as every other interop's traits. Wrapping a virtual_any in a virtual_ptr was previously rejected only as a side effect of the friend. Reject it explicitly, with a partial specialization on virtual_ptr's sfinae parameter. That also closes a hole final_virtual_ptr had all along: it does not go through acquire_vptr, so it silently returned the static_vptr of the `any` root class rather than the one for the contained value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
boostlook gives a `stretch` table both min-width:100% and margin-left:2rem, so the table overhangs its section by 2rem; the enclosing .sect3, which boostlook makes overflow-x:auto, then always shows a horizontal scrollbar. %autowidth emits fit-content instead, which no boostlook rule targets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
inter-operate with 'any'