Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f9aacc6
BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN
jll63 Jan 26, 2026
1eb22d8
inter-operate with 'any'
jll63 Feb 28, 2026
f0aafd3
inter-operate with 'any'
jll63 Mar 7, 2026
4b00b38
Merge branch 'develop' into feature/any
jll63 Jul 28, 2026
3a1fbc1
Merge branch 'develop' into feature/any
jll63 Jul 30, 2026
4914053
support std::any by mutable and xvalue reference
jll63 Jul 31, 2026
810a981
support boost::any
jll63 Jul 31, 2026
f086985
type_vptr -> vptr
jll63 Aug 2, 2026
7ecd96c
dynamic_vptr -> vptr
jll63 Aug 8, 2026
65cd1b5
fix leftovers of the dynamic_vptr -> vptr rename
jll63 Aug 8, 2026
57b6c32
add virtual_any
jll63 Aug 8, 2026
78143c8
fix infinite recursion in virtual_any's vptr friend on MSVC
jll63 Aug 8, 2026
01c863d
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 8, 2026
9dcba28
doc: an Interoperation page, and reference examples for the `any`s
jll63 Aug 8, 2026
8bcb1a3
alias use_std_any_types and use_boost_any_types
jll63 Aug 8, 2026
50d5ec9
support catch-all overriders on plain `any` virtual parameters
jll63 Aug 8, 2026
ff11651
doc: lead the Interoperation page with `virtual_`, not `virtual_any`
jll63 Aug 8, 2026
acdd8e0
test: make the virtual_any-by-value compile-fail test actually fail o…
jll63 Aug 8, 2026
e946dd2
doc: fix the broken `any` header links on the Headers page
jll63 Aug 8, 2026
f1d3cd8
doc: narrow the Interoperation page to `any`
jll63 Aug 8, 2026
3bedb21
doc: document `vptr` in VirtualTraits and VptrFn
jll63 Aug 9, 2026
608f1b6
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 9, 2026
a735c45
doc: make the `any` header links follow the deployment too
jll63 Aug 9, 2026
65904e3
doc: inline the final_virtual_ptr example instead of linking to it
jll63 Aug 9, 2026
1fc8ea3
doc: hide the deleted `final_virtual_ptr` overloads from the reference
jll63 Aug 9, 2026
437c666
reject virtual_ptr over classes with a boost_openmethod_vptr overload
jll63 Aug 9, 2026
516606c
doc: trim the any-interop entries on the Headers page
jll63 Aug 9, 2026
69b5d43
remove the make_*_virtual factories
jll63 Aug 9, 2026
df3c0b7
add virtual_any_ref, a non-owning counterpart of virtual_any
jll63 Aug 9, 2026
cb44af6
test: exercise virtual_any_ref with boost::any
jll63 Aug 10, 2026
8b21152
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 10, 2026
9221d15
doc: point the `any` header links at GitHub too
jll63 Aug 10, 2026
b45feb4
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 10, 2026
235344f
doc: touch up `any` doc
jll63 Aug 15, 2026
926574c
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 15, 2026
1f8ed77
doc: add the virtual_traits::vptr step to the dispatch lists
jll63 Aug 15, 2026
eb92a95
Merge branch 'develop' into feature/any
jll63 Aug 16, 2026
a01efed
doc: more `any` touch-up
jll63 Aug 16, 2026
49bfbf5
require std_rtti in the any interop
jll63 Aug 16, 2026
6730af4
doc: state the any interop's rtti requirement as what, not how
jll63 Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ set(

if (BOOST_OPENMETHOD_BUILD_TESTS OR BOOST_OPENMETHOD_MRDOCS_BUILD)
list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::smart_ptr)
list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::any)
endif()

foreach (BOOST_OPENMETHOD_DEPENDENCY ${BOOST_OPENMETHOD_DEPENDENCIES})
Expand Down
63 changes: 63 additions & 0 deletions doc/modules/ROOT/examples/virtual_any.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2018-2026 Jean-Louis Leroy
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

// clang-format off

// tag::content[]
#include <any>
#include <iostream>
#include <string>

#include <boost/openmethod.hpp>
#include <boost/openmethod/interop/std_any.hpp>

using namespace boost::openmethod;

struct Dog {
std::string name;
};

// `std::any` becomes the common base of the types it may contain.
BOOST_OPENMETHOD_REGISTER(use_std_any_types<Dog, std::string, int, float>);

BOOST_OPENMETHOD(name, (virtual_<const std::any&>), std::string);

// An overrider takes the contained value...
BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) {
return dog.name + " the dog";
}

BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) {
return name;
}

BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) {
return std::to_string(value) + " the integer";
}

// ...or the `any` itself, which makes it a catch-all.
BOOST_OPENMETHOD_OVERRIDE(name, (const std::any&), std::string) {
return "something else";
}

#include <boost/openmethod/initialize.hpp>

int main() {
initialize();

std::any spot = Dog{"Spot"};
std::any felix = std::string("Felix the cat");
std::any answer = 42;
std::any pi = 3.14f;

std::cout << name(spot) << "\n"; // Spot the dog
std::cout << name(felix) << "\n"; // Felix the cat
std::cout << name(answer) << "\n"; // 42 the integer

// `float` is registered, but has no overrider of its own, so the
// catch-all applies.
std::cout << name(pi) << "\n"; // something else
}
// end::content[]
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ struct Times : Node {
const Node& left; const Node& right;
};

// tag::content[]
#include <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>

Expand Down
1 change: 1 addition & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
** xref:custom_rtti.adoc[Custom RTTI]
** xref:error_handling.adoc[Error Handling]
** xref:virtual_ptr_alt.adoc[Virtual Pointer Alternatives]
** xref:interop_any.adoc[Interoperation with `any`]
** xref:shared_libraries.adoc[Shared Libraries]
* Reference
** xref:ref_headers.adoc[Headers]
Expand Down
169 changes: 169 additions & 0 deletions doc/modules/ROOT/pages/interop_any.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@

[#interop_any]
## Interoperation with `any`

OpenMethod can take `any` (both the `std` and `boost` flavors) as virtual
arguments, and dispatch on the type of the contained value. For this purpose, it
regards the types as "deriving" from `any`.

#### Requirements

Dispatch keys on the `std::type_info` returned by `any::type()`, so the
registry's `rtti` policy must be cpp:std_rtti[], or a policy derived from it.
`default_registry` and `indirect_registry` both qualify. A registry with, say,
cpp:static_rtti[] identifies classes by a different kind of `type_id`, and would
look up the wrong v-table; the requirement is enforced with a `static_assert`.

#### `std::any`

Support is provided by `<boost/openmethod/interop/std_any.hpp>`. It is not
included by `<boost/openmethod.hpp>`, so it must be included explicitly.

Dispatch works on classes known to a registry, so the types the `any` may
contain have to be registered. cpp:use_std_any_types[] does that, registering
`std::any` as a class, and each of the types as a class derived from it. A type
that is not registered cannot be dispatched on; a call with such a value in the
`any` is a cpp:missing_class[] error - see
xref:error_handling.adoc[Error Handling].

The `any` is then passed like any other virtual argument that is not a
`virtual_ptr`: wrapped in `virtual_`, as described in
xref:virtual_ptr_alt.adoc[Alternatives to virtual_ptr]. Overriders receive the
_contained_ value by reference. An overrider may also take the `any` itself;
such an overrider is a catch-all, applying to any contained type that has no
more specific overrider:

[source,c++]
----
include::example$virtual_any.cpp[tag=content]
----

#### Mixing with ordinary virtual parameters

A multi-method can take any combination of ordinary virtual parameters and
virtual `any` in the same call:

```c++
BOOST_OPENMETHOD(
meet, (virtual_<const std::any&>, virtual_ptr<const Animal>), std::string);

BOOST_OPENMETHOD_OVERRIDE(
meet, (const Dog& dog, virtual_ptr<const Cat>), std::string) {
return dog.name + " meets a cat";
}
```

#### Reference categories

All three reference categories are supported, and they determine what the
overriders may take:

[cols="1,2"]
|===
| Method parameter | Overrider parameter

| `virtual_<const std::any&>`
| `const Dog&`, `Dog`

| `virtual_<std::any&>`
| `Dog&`, `const Dog&`, `Dog`

| `virtual_<std::any&&>`
| `Dog&&`, `const Dog&`, `Dog`
|===

The mutable lvalue reference is the awkward one.
xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] locates
the method by checking that the overrider's parameters can be passed to the
method's forwarder, and `Dog&` does not convert to `std::any&`. A temporary
`std::any` binds to `const std::any&` and to `std::any&&`, which is why the
other two categories can use the macro; nothing binds to a mutable lvalue
reference. Those overriders are registered with the core API instead - the
primitive the macro itself expands to:

```c++
using bump_method =
BOOST_OPENMETHOD_TYPE(bump, (virtual_<std::any&>), std::string);

auto bump_dog(Dog& dog) -> std::string {
dog.name += " Jr.";
return dog.name;
}

BOOST_OPENMETHOD_REGISTER(bump_method::override<bump_dog>);
```

#### `virtual_std_any`

Every call above looks the v-table up in a hash table, keyed on the type the
`any` contains. cpp:virtual_std_any[] - an alias for `virtual_any<std::any>` -
removes that cost: it bundles an `any` with the v-table pointer for the value
inside it, acquiring it once, on construction, and maintaining it across
assignment and `emplace`. It is similar to cpp:virtual_ptr[], except that it
_owns_ the object: the `any` is held by value.

The pointer comes from a lookup when the `virtual_std_any` is built from an
existing `any`, and from a static variable - no lookup at all - when it is built
from a value, or by `emplace`, since the type is then known at compile time.

That makes it worthwhile when the same value is dispatched on repeatedly. Its
usefulness is limited, though, by the fact that the wrapper is not what an
overrider receives: an overrider takes the contained value, as before, so it
cannot pass the `virtual_std_any` on to another method and save the lookup
there. Only a catch-all overrider, which takes `const virtual_std_any&`, gets
it.

A `virtual_std_any` method parameter must be a reference - passing it by value
would copy the `any`, and the value inside it, on every call. The three
categories, and the limitation on the mutable one, are as above.

For the same reason that a `virtual_std_any` caches what a plain `any` does not,
cpp:final_virtual_ptr[] is _deleted_ for `std::any`: it would silently produce
the v-table of the `any` root class rather than the one for the contained value.

#### `virtual_any_ref`

`virtual_std_any` owns its `any`. cpp:virtual_any_ref[] is its non-owning
counterpart: it _refers to_ an `any` that lives elsewhere, bundling its address
with the v-table pointer for the value inside it - acquired once, when the
handle is created, or taken at no cost from a `virtual_any`. It is a cheap,
two-word handle with pointer semantics. Unlike the owning wrapper, it is passed
to methods _by value_, like the reference-wrapper flavors of Boost.TypeErasure's
`any`:

```c++
BOOST_OPENMETHOD(poke, (virtual_any_ref<std::any>), std::string);

std::any spot_any = Dog{"Spot"};
virtual_any_ref<std::any> spot = spot_any; // one lookup

poke(spot); // no lookup
poke(spot); // no lookup; mutations reach spot_any
```

`Any` may be const-qualified: through `virtual_any_ref<const std::any>`,
overriders receive the contained value by value or by const reference only. A
mutable handle converts to a const one.

A plain value does not convert to a `virtual_any_ref` - there is no `any` for
the handle to refer to - so `BOOST_OPENMETHOD_OVERRIDE`, which locates the
method by convertibility, cannot register overriders that take the contained
value.
Register them with the core API instead, as in the `virtual_<std::any&>` case
above; the catch-all overrider, which takes the handle itself, can use the
macro.

The handle does not track its referent: if the value inside the `any` is
replaced, the handle is stale - like an iterator into a modified container -
and must be re-created.

#### `boost::any`

`boost::any` is supported as well, by
`<boost/openmethod/interop/boost_any.hpp>`, with cpp:use_boost_any_types[] and
cpp:virtual_boost_any[] - the exact counterparts of the constructs above. The two root classes are distinct, so
`std::any` and `boost::any` may be used in the same program, and with the same
registry.

cpp:virtual_any[] itself is generic: it can serve any type with an `any`-like
interface, given cpp:virtual_traits[] specializations for its reference types.
20 changes: 20 additions & 0 deletions doc/modules/ROOT/pages/ref_headers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ Provides a `virtual_traits` specialization that makes it possible to use a
Provides a `virtual_traits` specialization that makes it possible to use a
`boost::intrusive_ptr` in place of a raw pointer or reference in virtual parameters.

[#virtual_any]
### link:{headers-url}/boost/openmethod/interop/virtual_any.hpp[<boost/openmethod/interop/virtual_any.hpp>]

Provides `virtual_any`, a wide `any` that combines an `any`, held by value, with
a pointer to the v-table for the contained value - similar to `virtual_ptr`.
Also provides `virtual_any_ref`, a non-owning counterpart that refers to an
existing `any`.

[#std_any]
### link:{headers-url}/boost/openmethod/interop/std_any.hpp[<boost/openmethod/interop/std_any.hpp>]

Provides `virtual_traits` specializations that make it possible to use a
`std::any` in virtual parameters.

[#boost_any]
### link:{headers-url}/boost/openmethod/interop/boost_any.hpp[<boost/openmethod/interop/boost_any.hpp>]

Provides `virtual_traits` specializations that make it possible to use a
`boost::any` in virtual parameters.

*The headers below are for advanced use*.

## Pre-Core Headers
Expand Down
9 changes: 7 additions & 2 deletions doc/modules/ROOT/pages/virtual_ptr_alt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ can be called with the virtual argument (passed by const reference) and a
pointer to a registry, and returns a `vptr_type`. If one is found, it is called
to acquire the vptr.

`virtual_ptr` honors it as well, when it is constructed from - or assigned - a
reference or a pointer to an object.
This is specific to `virtual_` parameters. `virtual_ptr` does not use the
function - it is an alternative to it, not a client of it - and wrapping an
object that provides one is rejected at compile time.

In the following example, we embed the vptr in the object, just like the vptr
for native virtual functions. The v-table for a registered class can be found
Expand Down Expand Up @@ -106,3 +107,7 @@ v-table for the bases, just like what C++ does for its native vptrs.

`inplace_vptr_base` and `inplace_vptr_derived` are aliased in `namespace
boost::openmethod::aliases`.

An object that embeds its v-table pointer does not need to be wrapped in a
`virtual_ptr` - the two fill the same goal, fast access to the v-table
pointer - and wrapping one is rejected at compile time.
Loading
Loading