diff --git a/CMakeLists.txt b/CMakeLists.txt index cdd278df..dd18d861 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/doc/modules/ROOT/examples/virtual_any.cpp b/doc/modules/ROOT/examples/virtual_any.cpp new file mode 100644 index 00000000..f1a6adf8 --- /dev/null +++ b/doc/modules/ROOT/examples/virtual_any.cpp @@ -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 +#include +#include + +#include +#include + +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); + +BOOST_OPENMETHOD(name, (virtual_), 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 + +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[] diff --git a/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp b/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp index 56bba033..460d9bc1 100644 --- a/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp +++ b/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp @@ -28,7 +28,6 @@ struct Times : Node { const Node& left; const Node& right; }; -// tag::content[] #include #include diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index edaebaa3..3f46dabe 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -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] diff --git a/doc/modules/ROOT/pages/interop_any.adoc b/doc/modules/ROOT/pages/interop_any.adoc new file mode 100644 index 00000000..a2e4a829 --- /dev/null +++ b/doc/modules/ROOT/pages/interop_any.adoc @@ -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 ``. It is not +included by ``, 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_, virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const Dog& dog, virtual_ptr), 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 Dog&`, `Dog` + +| `virtual_` +| `Dog&`, `const Dog&`, `Dog` + +| `virtual_` +| `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::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +``` + +#### `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` - +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::string); + +std::any spot_any = Dog{"Spot"}; +virtual_any_ref 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`, +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_` 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 +``, 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. diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index ba911432..afb5774e 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -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[] + +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[] + +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[] + +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 diff --git a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc index b9ceca52..29b45cdb 100644 --- a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc +++ b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc @@ -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 @@ -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. diff --git a/doc/modules/ROOT/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp new file mode 100644 index 00000000..63dc95cd --- /dev/null +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -0,0 +1,208 @@ +// 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) + +#include +#include + +#include +#include +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +namespace std_any { + +// tag::classes[] +struct Dog { + std::string name; +}; + +// `std::any` becomes the common base of the types it may contain. +BOOST_OPENMETHOD_REGISTER(use_std_any_types); +// end::classes[] + +// tag::method[] +BOOST_OPENMETHOD(name, (const virtual_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 `virtual_any` itself, which makes it a catch-all. +BOOST_OPENMETHOD_OVERRIDE(name, (const virtual_std_any& value), std::string) { + return value.get().has_value() ? "something else" : "nothing"; +} +// end::method[] + +} // namespace std_any + +namespace boost_any { + +// tag::boost_classes[] +struct Dog { + std::string name; +}; + +// `boost::any` is a root class of its own, distinct from the one used for +// `std::any`, so both may be used in the same program and registry. +BOOST_OPENMETHOD_REGISTER(use_boost_any_types); + +BOOST_OPENMETHOD(name, (const virtual_boost_any&), std::string); + +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; +} +// end::boost_classes[] + +} // namespace boost_any + +namespace any_ref { + +using std_any::Dog; + +// tag::ref[] +BOOST_OPENMETHOD(poke, (virtual_any_ref), std::string); + +// A plain value does not convert to a virtual_any_ref, so overriders +// that take the contained value are registered with the core API. +using poke_method = + BOOST_OPENMETHOD_TYPE(poke, (virtual_any_ref), std::string); + +auto poke_dog(Dog& dog) -> std::string { + dog.name += "!"; + return dog.name; +} + +BOOST_OPENMETHOD_REGISTER(poke_method::override); +// end::ref[] + +} // namespace any_ref + +BOOST_AUTO_TEST_CASE(std_any_examples) { + using namespace std_any; + + initialize(); + + { + capture_cout cout; + + // tag::dispatch[] + virtual_std_any spot = Dog{"Spot"}; + + std::cout << name(spot) << "\n"; // Spot the dog + + // `float` is registered, but has no overrider of its own, so the + // catch-all applies. The value converts to a temporary + // `virtual_std_any` at the call site. + std::cout << name(3.14f) << "\n"; // something else + // end::dispatch[] + + BOOST_TEST(cout.str() == "Spot the dog\nsomething else\n"); + } + + { + capture_cout cout; + + // tag::from_any[] + std::any spot_any = Dog{"Spot"}; + + // the v-table pointer is looked up from the type of the value the + // `any` contains + virtual_std_any spot = spot_any; + + std::cout << name(spot) << "\n"; // Spot the dog + // end::from_any[] + + BOOST_TEST(cout.str() == "Spot the dog\n"); + } + + { + capture_cout cout; + + // tag::from_value[] + // the type is known at compile time, so the v-table pointer is read + // from a static variable - there is no lookup + virtual_std_any answer = 42; + + std::cout << name(answer) << "\n"; // 42 the integer + // end::from_value[] + + BOOST_TEST(cout.str() == "42 the integer\n"); + } + + { + capture_cout cout; + + // tag::emplace[] + virtual_std_any value; + + value.emplace("Felix the cat"); + + std::cout << name(value) << "\n"; // Felix the cat + // end::emplace[] + + BOOST_TEST(cout.str() == "Felix the cat\n"); + } +} + +BOOST_AUTO_TEST_CASE(virtual_any_ref_examples) { + using namespace any_ref; + + initialize(); + + { + capture_cout cout; + + // tag::ref_dispatch[] + std::any spot_any = Dog{"Spot"}; + + // one lookup; the handle refers to the `any` + virtual_any_ref spot = spot_any; + + std::cout << poke(spot) << "\n"; // Spot! + std::cout << poke(spot) << "\n"; // Spot!! - no lookup on any call + // end::ref_dispatch[] + + BOOST_TEST(cout.str() == "Spot!\nSpot!!\n"); + } +} + +BOOST_AUTO_TEST_CASE(boost_any_examples) { + using namespace boost_any; + + initialize(); + + { + capture_cout cout; + + // tag::boost_dispatch[] + virtual_boost_any felix = std::string("Felix the cat"); + + std::cout << name(felix) << "\n"; // Felix the cat + // end::boost_dispatch[] + + BOOST_TEST(cout.str() == "Felix the cat\n"); + } +} diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 3adf2e51..791c1d54 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -459,13 +459,10 @@ class use_classes { //! given argument type and registry, that overload is used to acquire a v-table //! pointer instead of the registry's @ref policies::vptr policy. //! -//! The library uses `boost_openmethod_vptr` (if found): -//! -//! @li when dispatching via a @ref virtual_ parameter (*not* a @ref -//! virtual_ptr); -//! -//! @li when a @ref virtual_ptr is constructed from, or assigned, a reference or -//! a pointer to an object (*not* by the "final" constructs). +//! The library uses `boost_openmethod_vptr`, if found, when dispatching via a +//! @ref virtual_ parameter. It is not used by @ref virtual_ptr; wrapping an +//! object that has an overload in a @ref virtual_ptr is rejected at compile +//! time. //! //! @par Requirements //! @@ -597,12 +594,25 @@ constexpr bool has_vptr_fn = std::is_same_v< std::declval(), std::declval())), vptr_type>; +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(vptr); + template decltype(auto) acquire_vptr(const ArgType& arg) { + // A class with a boost_openmethod_vptr overload does not need to be + // wrapped: virtual_ptr and the hook fill the same goal, fast access + // to the v-table pointer. The hook also returns the vptr by value, + // which indirect registries cannot store (see box_vptr). + static_assert( + !has_vptr_fn, + "do not wrap an object that has a boost_openmethod_vptr overload " + "in a virtual_ptr; call methods directly on the object"); + Registry::require_initialized(); - if constexpr (detail::has_vptr_fn) { - return boost_openmethod_vptr(arg, static_cast(nullptr)); + if constexpr (has_vptr< + virtual_traits, + const ArgType&>) { + return virtual_traits::vptr(arg); } else { return Registry::template policy::dynamic_vptr(arg); } @@ -657,8 +667,7 @@ inline vptr_type null_vptr = nullptr; //! //! @par Example //! -//! See [the default-registry overload](xref:reference:boost/openmethod/final_virtual_ptr-08.adoc#_example) -//! for an example. +//! include:virtual_ptr.cpp#non_polymorphic_classes;final_virtual_ptr //! //! @tparam Registry A @ref registry. //! @tparam Arg The type of the argument. @@ -819,10 +828,12 @@ class virtual_ptr { //! Construct a `virtual_ptr` from a reference to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::VptrFn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @param other A reference to a polymorphic object //! @@ -854,10 +865,12 @@ class virtual_ptr { //! Construct a `virtual_ptr` from a pointer to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::VptrFn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @par Example //! include:virtual_ptr.cpp#ctor_pointer @@ -923,10 +936,12 @@ class virtual_ptr { //! Assign a `virtual_ptr` from a reference to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::VptrFn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @par Example //! include:virtual_ptr.cpp#assign_ref @@ -961,10 +976,12 @@ class virtual_ptr { //! Assign a `virtual_ptr` from a pointer to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::VptrFn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @par Example //! include:virtual_ptr.cpp#assign_pointer @@ -1963,7 +1980,9 @@ struct validate_method_parameter< //! 2. If @ref boost_openmethod_vptr can be called with `result` and a //! `Registry*`, and it returns a `vptr_type`, call it. //! -//! 3. Call the @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` +//! 3. If @ref virtual_traits provides a `vptr` function, call it. +//! +//! 4. Call the @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` //! policy. //! //! @par N2216 Handling of Ambiguous Calls @@ -2179,7 +2198,7 @@ class method void resolve_type_ids(); - template + template auto vptr(const ArgType& arg) const -> vptr_type; template @@ -2333,7 +2352,7 @@ method::operator()( typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS StripVirtualDecorator::type... args) const -> ReturnType { using namespace detail; - auto pf = resolve(parameter_traits::peek(args)...); + auto pf = resolve(args...); return pf(std::forward::type>( args)...); @@ -2365,13 +2384,23 @@ BOOST_FORCEINLINE template< typename Id, typename... Parameters, typename ReturnType, class Registry> -template +template BOOST_FORCEINLINE auto method::vptr( const ArgType& arg) const -> vptr_type { if constexpr (detail::is_virtual_ptr) { return arg.vptr(); } else { - return detail::acquire_vptr(arg); + decltype(auto) obj = virtual_traits::peek(arg); + + if constexpr (detail::has_vptr_fn) { + return boost_openmethod_vptr(obj, static_cast(nullptr)); + } else if constexpr (detail::has_vptr< + virtual_traits, + decltype(obj)>) { + return virtual_traits::vptr(obj); + } else { + return Registry::template policy::dynamic_vptr(obj); + } } } @@ -2388,7 +2417,7 @@ method::resolve_uni( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr(arg); + vptr_type vtbl = vptr>>(arg); return vtbl[this->slots_strides[0]]; } else { return resolve_uni>(more_args...); @@ -2407,7 +2436,7 @@ method::resolve_multi_first( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr(arg); + vptr_type vtbl = vptr>>(arg); std::size_t slot = this->slots_strides[0]; // The first virtual parameter is special. Since its stride is @@ -2437,7 +2466,7 @@ method::resolve_multi_next( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr(arg); + vptr_type vtbl = vptr>>(arg); std::size_t slot = this->slots_strides[VirtualArg]; std::size_t stride = this->slots_strides[Arity + VirtualArg - 1]; dispatch = dispatch + vtbl[slot].i * stride; @@ -2745,6 +2774,35 @@ struct VirtualTraits { //! @return A reference to an object. static auto peek(T arg) -> const virtual_type&; + // Added by the `std::any` interop, under the name `type_vptr`. An `any` + // dispatches on the type of the value it contains, which the rtti policy + // cannot see: `dynamic_type` on the `any` itself yields the wrapper. + + //! Returns a *reference* to the v-table pointer for an object. + //! + //! `vptr` is optional. It is called on the object returned by @ref peek, + //! not on the method argument itself. A method acquires the v-table + //! pointer of a virtual argument from the first of the following that is + //! available: a `boost_openmethod_vptr` function, found by ADL on the + //! peeked object; `vptr`; @ref policies::VptrFn::dynamic_vptr of the + //! registry's @ref policies::vptr policy. + //! + //! Implement `vptr` only if the v-table pointer cannot be obtained from + //! the dynamic type of the peeked object, as reported by the registry's + //! @ref policies::rtti policy. This is the case for `any`-like types: + //! their dynamic type is the wrapper, not the value they contain. The + //! `std::any` specializations read the @ref type_id of the contained + //! value from `arg.type()`, and pass it to + //! @ref policies::VptrFn::vptr. + //! + //! `vptr` must return a *reference*, not a value, so that the caller + //! observes the current v-table pointer if the registry contains the + //! @ref policies::indirect_vptr policy and `initialize` is called again. + //! + //! @param arg The object returned by @ref peek. + //! @return A reference to the v-table pointer for `arg`. + static auto vptr(const virtual_type& arg) -> const vptr_type&; + //! Casts a virtual argument. //! //! `cast` is responsible for passing virtual arguments from method to diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index 7505bf32..cf1328b0 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -76,6 +76,10 @@ class inplace_vptr_base_tag {}; //! @ref policies::vptr policy, nor any policy it depends on (like @ref //! policies::type_hash). //! +//! An object that embeds its v-table pointer does not need to be wrapped +//! in a @ref virtual_ptr - the two fill the same goal, fast access to the +//! v-table pointer - and wrapping one is rejected at compile time. +//! //! If `Registry` contains the @ref has_indirect_vptr policy, the v-table //! pointer is stored as a pointer to a pointer, and remains valid after a call //! to @ref initialize. diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp new file mode 100644 index 00000000..1e639633 --- /dev/null +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -0,0 +1,338 @@ +// 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) + +#ifndef BOOST_OPENMETHOD_INTEROP_BOOST_ANY_HPP +#define BOOST_OPENMETHOD_INTEROP_BOOST_ANY_HPP + +#include +#include +#include +#include + +namespace boost::openmethod { + +namespace detail { + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +// `boost::any::type()` yields a `std::type_info`, which is a valid `type_id` +// only for an rtti policy that identifies classes by `&typeid(T)`. Under any +// other policy the lookup key is meaningless, and `type_id` being +// `const void*`, nothing would diagnose it. +template +constexpr void assert_std_rtti_boost_any() { + static_assert( + std::is_base_of_v< + policies::std_rtti, + find_first_derived_of< + policies::rtti, typename Registry::policy_list>>, + "requires standard RTTI"); +} + +} // namespace detail + +//! Specialize virtual_traits for `const boost::any&` (const reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `boost::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = boost::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `boost::any`. + //! @return A const reference to `arg`. + static auto peek(const boost::any& arg) -> const boost::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the dynamic @ref type_id of the value stored in `arg`, using + //! `boost::any::type()`. This requires the registry's @ref rtti policy to + //! be @ref std_rtti; the requirement is enforced with a `static_assert`. + //! `boost::any::type()` yields the same `std::type_info` object as + //! `&typeid(T)`, provided Boost.TypeIndex uses `stl_type_index`. + //! + //! Passes the type id to the registry's @ref policies::vptr policy, which + //! must provide @ref policies::VptrFn::vptr. Both + //! @ref policies::vptr_vector and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const boost::any& arg) -> const vptr_type& { + detail::assert_std_rtti_boost_any(); + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `boost::any_cast`. + //! + //! Since the `any` argument is const, `U` cannot be a mutable reference. + //! `boost::any_cast` rewrites `U` to a const reference for a const `any`, + //! and would fail inside Boost.Any; this overload is removed from the + //! overload set instead. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg A reference to a const `boost::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + !std::is_reference_v || + std::is_const_v>>> + static auto cast(const boost::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `boost::any&` (mutable reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `boost::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = boost::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `boost::any`. + //! @return A const reference to `arg`. + static auto peek(const boost::any& arg) -> const boost::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the dynamic @ref type_id of the value stored in `arg`, using + //! `boost::any::type()`. This requires the registry's @ref rtti policy to + //! be @ref std_rtti; the requirement is enforced with a `static_assert`. + //! `boost::any::type()` yields the same `std::type_info` object as + //! `&typeid(T)`, provided Boost.TypeIndex uses `stl_type_index`. + //! + //! Passes the type id to the registry's @ref policies::vptr policy, which + //! must provide @ref policies::VptrFn::vptr. Both + //! @ref policies::vptr_vector and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a `boost::any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const boost::any& arg) -> const vptr_type& { + detail::assert_std_rtti_boost_any(); + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `boost::any_cast`. Supports mutable + //! references (e.g. `Dog&`) because the `any` argument is not const; + //! modifications through the result are visible through the `any`. + //! + //! `U` cannot be an rvalue reference. Unlike `std::any_cast`, + //! `boost::any_cast` binds an rvalue reference to the value stored in an + //! lvalue `any`; moving the value out must go through an explicit + //! `virtual_` parameter, so this overload is removed from + //! the overload set. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg A mutable reference to the `boost::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template< + typename U, typename = std::enable_if_t>> + static auto cast(boost::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `boost::any&&` (xvalue reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `boost::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = boost::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `boost::any`. + //! @return A const reference to `arg`. + static auto peek(const boost::any& arg) -> const boost::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the dynamic @ref type_id of the value stored in `arg`, using + //! `boost::any::type()`. This requires the registry's @ref rtti policy to + //! be @ref std_rtti; the requirement is enforced with a `static_assert`. + //! `boost::any::type()` yields the same `std::type_info` object as + //! `&typeid(T)`, provided Boost.TypeIndex uses `stl_type_index`. + //! + //! Passes the type id to the registry's @ref policies::vptr policy, which + //! must provide @ref policies::VptrFn::vptr. Both + //! @ref policies::vptr_vector and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a `boost::any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const boost::any& arg) -> const vptr_type& { + detail::assert_std_rtti_boost_any(); + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `boost::any_cast`. + //! + //! `U` cannot be a mutable lvalue reference: that would bind a reference + //! to the value contained in a temporary. Boost.Any rejects it with a + //! static assertion; this overload is removed from the overload set + //! instead, for consistency with the other reference categories. + //! + //! @tparam U The target type (e.g. `Dog&&`, `const Dog&`, `Dog`). + //! @param arg An rvalue reference to the `boost::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + !std::is_lvalue_reference_v || + std::is_const_v>>> + static auto cast(boost::any&& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return std::move(arg); + } else { + return boost::any_cast(std::move(arg)); + } + } +}; + +//! Register the types that a `boost::any` virtual parameter may contain. +//! +//! Registers `boost::any` as a class, and each `T` as a class derived from +//! `boost::any`. This makes the contained types visible to the dispatch +//! machinery, which resolves a call on the `type_id` returned by +//! `boost::any::type()`. +//! +//! The root class is `boost::any`, distinct from the one used by +//! @ref use_std_any_types for `std::any`, so both may be used in the same +//! program, and with the same registry. +//! +//! @tparam T... The types that may be stored in the `any`, optionally +//! followed by a @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#boost_classes;boost_dispatch +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +struct use_boost_any_types + : detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>, + detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>... {}; + +//! Alias for a `virtual_any`, in the default registry. +//! +//! With another registry, use `virtual_any` directly. +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +using virtual_boost_any = virtual_any; + +// The primary final_virtual_ptr would silently use static_vptr +// - the v-table of the `any` root class, not of the contained value. +// Delete the combination. Both call forms need covering: the non-template +// overloads catch calls that deduce the default registry, and are removed +// from consideration when an explicit template argument list is given, so +// the Registry-only templates - more specialized than the primary - catch +// those. +// +// Hidden from the reference: they are a guard, not API, and six deleted +// overloads would crowd the `final_virtual_ptr` overload list. + +#ifndef __MRDOCS__ +template +void final_virtual_ptr(const boost::any&) = delete; +template +void final_virtual_ptr(boost::any&) = delete; +template +void final_virtual_ptr(boost::any&&) = delete; +void final_virtual_ptr(const boost::any&) = delete; +void final_virtual_ptr(boost::any&) = delete; +void final_virtual_ptr(boost::any&&) = delete; +#endif + +namespace aliases { +using boost::openmethod::use_boost_any_types; +using boost::openmethod::virtual_boost_any; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp new file mode 100644 index 00000000..0962ab31 --- /dev/null +++ b/include/boost/openmethod/interop/std_any.hpp @@ -0,0 +1,304 @@ +// 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) + +#ifndef BOOST_OPENMETHOD_INTEROP_STD_ANY_HPP +#define BOOST_OPENMETHOD_INTEROP_STD_ANY_HPP + +#include +#include +#include +#include + +namespace boost::openmethod { + +namespace detail { + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +// `std::any::type()` yields a `std::type_info`, which is a valid `type_id` +// only for an rtti policy that identifies classes by `&typeid(T)`. Under any +// other policy the lookup key is meaningless, and `type_id` being +// `const void*`, nothing would diagnose it. +template +constexpr void assert_std_rtti_std_any() { + static_assert( + std::is_base_of_v< + policies::std_rtti, + find_first_derived_of< + policies::rtti, typename Registry::policy_list>>, + "requires standard RTTI"); +} + +} // namespace detail + +//! Specialize virtual_traits for `const std::any&` (const reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = std::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `std::any`. + //! @return A const reference to `arg`. + static auto peek(const std::any& arg) -> const std::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. This requires the registry's @ref rtti policy to be + //! @ref std_rtti; the requirement is enforced with a `static_assert`. + //! + //! Passes it to the registry's @ref policies::vptr policy, which must + //! provide @ref policies::VptrFn::vptr. Both @ref policies::vptr_vector + //! and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const std::any& arg) -> const vptr_type& { + detail::assert_std_rtti_std_any(); + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `std::any_cast`. Since + //! the `any` argument is const, `U` cannot be a mutable reference. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg A reference to a const `std::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(const std::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `std::any&` (mutable reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = std::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `std::any`. + //! @return A const reference to `arg`. + static auto peek(const std::any& arg) -> const std::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. This requires the registry's @ref rtti policy to be + //! @ref std_rtti; the requirement is enforced with a `static_assert`. + //! + //! Passes it to the registry's @ref policies::vptr policy, which must + //! provide @ref policies::VptrFn::vptr. Both @ref policies::vptr_vector + //! and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a `std::any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const std::any& arg) -> const vptr_type& { + detail::assert_std_rtti_std_any(); + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself, returns `arg` unchanged, which is how a + //! catch-all overrider is written. Otherwise, extracts the stored value + //! using `std::any_cast`. Supports mutable references (e.g. `Dog&`) + //! because the `any` argument is not const; modifications through the + //! result are visible through the `any`. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg A mutable reference to the `std::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(std::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `std::any&&` (xvalue reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = std::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `std::any`. + //! @return A const reference to `arg`. + static auto peek(const std::any& arg) -> const std::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. This requires the registry's @ref rtti policy to be + //! @ref std_rtti; the requirement is enforced with a `static_assert`. + //! + //! Passes it to the registry's @ref policies::vptr policy, which must + //! provide @ref policies::VptrFn::vptr. Both @ref policies::vptr_vector + //! and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const std::any& arg) -> const vptr_type& { + detail::assert_std_rtti_std_any(); + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself, returns `arg` unchanged, which is how a + //! catch-all overrider is written. Otherwise, extracts the stored value + //! using `std::any_cast`. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg An rvalue reference to the `std::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(std::any&& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return std::move(arg); + } else { + return std::any_cast(std::move(arg)); + } + } +}; + +//! Register the types that a `std::any` virtual parameter may contain. +//! +//! Registers `std::any` as a class, and each `T` as a class derived from +//! `std::any`. This makes the contained types visible to the dispatch +//! machinery, which resolves a call on the `type_id` returned by +//! `std::any::type()`. +//! +//! @tparam T... The types that may be stored in the `any`, optionally +//! followed by a @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#classes +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +struct use_std_any_types + : detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>, + detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>... {}; + +//! Alias for a `virtual_any`, in the default registry. +//! +//! With another registry, use `virtual_any` directly. +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +using virtual_std_any = virtual_any; + +// The primary final_virtual_ptr would silently use static_vptr +// - the v-table of the `any` root class, not of the contained value. +// Delete the combination. Both call forms need covering: the non-template +// overloads catch calls that deduce the default registry, and are removed +// from consideration when an explicit template argument list is given, so +// the Registry-only templates - more specialized than the primary - catch +// those. +// +// Hidden from the reference: they are a guard, not API, and six deleted +// overloads would crowd the `final_virtual_ptr` overload list. + +#ifndef __MRDOCS__ +template +void final_virtual_ptr(const std::any&) = delete; +template +void final_virtual_ptr(std::any&) = delete; +template +void final_virtual_ptr(std::any&&) = delete; +void final_virtual_ptr(const std::any&) = delete; +void final_virtual_ptr(std::any&) = delete; +void final_virtual_ptr(std::any&&) = delete; +#endif + +namespace aliases { +using boost::openmethod::use_std_any_types; +using boost::openmethod::virtual_std_any; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp new file mode 100644 index 00000000..c2b0c58a --- /dev/null +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -0,0 +1,771 @@ +// 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) + +#ifndef BOOST_OPENMETHOD_INTEROP_VIRTUAL_ANY_HPP +#define BOOST_OPENMETHOD_INTEROP_VIRTUAL_ANY_HPP + +#include + +#include +#include + +namespace boost::openmethod { + +template +class virtual_any; + +template +class virtual_any_ref; + +BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS + +//! Test if argument is a wide `any` (exposition only) +//! +//! Evaluates to `true` if `T` is a specialization of @ref virtual_any or of +//! @ref virtual_any_ref, and `false` otherwise. +//! +//! This constrains the constructor and the assignment operator of +//! @ref virtual_any that take a value, excluding both wide types - every +//! specialization of them, not only the ones matching this `virtual_any`. A +//! @ref virtual_any argument then selects the copy or move operation instead +//! of being stored inside the `any`, and a @ref virtual_any_ref argument is +//! rejected outright rather than stored: a handle is not a registered class, +//! so its @ref registry::static_vptr would be null. +//! +//! @tparam T A type. +template +constexpr bool IsVirtualAny = false; + +//! Recognize a virtual_any (exposition only) +//! +//! The specialization of @ref IsVirtualAny that matches a +//! `virtual_any`, and evaluates to `true`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +template +constexpr bool IsVirtualAny> = true; + +//! Recognize a virtual_any_ref (exposition only) +//! +//! The specialization of @ref IsVirtualAny that matches a +//! `virtual_any_ref`, and evaluates to `true`. +//! +//! @tparam Any An `any` type, possibly const-qualified. +//! @tparam Registry A @ref registry. +template +constexpr bool IsVirtualAny> = true; + +BOOST_OPENMETHOD_CLOSE_NAMESPACE_DETAIL_UNLESS_MRDOCS + +//! A wide `any`, combining an `any` and a pointer to a v-table. +//! +//! `virtual_any` is to `any` what @ref virtual_ptr is to a pointer: it +//! carries the v-table pointer for the value stored in the `any`, so +//! methods dispatch on the contained type without looking it up on every +//! call. Unlike `virtual_ptr`, it *owns* its object: the `any` is held by +//! value. +//! +//! The v-table pointer is acquired when the `virtual_any` is created: +//! either from the dynamic type of an existing `any` (a hash table +//! lookup, via `virtual_traits::vptr`), or +//! statically, when the contained type is known at compile time (the +//! value constructor and @ref emplace use @ref registry::static_vptr). +//! +//! Methods take `virtual_any` parameters by reference: `const +//! virtual_any&`, `virtual_any&` or `virtual_any&&`. Overriders receive +//! the *contained* type, by a reference of a compatible category - or the +//! `virtual_any` itself, unchanged, for a catch-all overrider. +//! +//! The contained value cannot be replaced through a `virtual_any` other +//! than via assignment or @ref emplace, which re-derive the v-table +//! pointer, thus maintaining the invariant that the v-table pointer +//! corresponds to the contained type. +//! +//! `Any` can be `std::any`, `boost::any`, or any type that has an +//! `any`-like interface, and specializes `virtual_traits` for its +//! reference types, providing `vptr` and `cast`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#classes;method;dispatch +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +class virtual_any { + static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; + + Any obj; + std::conditional_t vp; + + template + friend struct virtual_traits; + + template + friend class virtual_any_ref; + + public: + //! Construct an empty `virtual_any`. + //! + //! The `any` is empty, and the v-table pointer is null. + virtual_any() + : obj(), vp(detail::box_vptr(detail::null_vptr)) { + } + + //! Construct from an `any` (copy). + //! + //! Copies `other`, and acquires the v-table pointer for the contained + //! value, using `virtual_traits::vptr`. + //! + //! @param other An `any`. + //! + //! @par Example + //! include:virtual_any.cpp#from_any + virtual_any(const Any& other) + : obj(other), vp(detail::box_vptr( + detail::acquire_vptr(obj))) { + } + + //! Construct from an `any` (move). + //! + //! Moves `other`, and acquires the v-table pointer for the contained + //! value, using `virtual_traits::vptr`. + //! + //! @param other An `any`. + virtual_any(Any&& other) + : obj(std::move(other)), vp(detail::box_vptr( + detail::acquire_vptr(obj))) { + } + + //! Construct from a value. + //! + //! Stores `value` in the `any`, and sets the v-table pointer to the + //! @ref registry::static_vptr for its type - no hash table lookup is + //! involved. The type of `value`, stripped from reference and + //! cv-qualifiers, must be registered in `Registry`. + //! + //! @tparam T The type of the value. + //! @param value The value to store. + //! + //! @par Example + //! include:virtual_any.cpp#from_value + template< + typename T, + typename = std::enable_if_t< + !BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + IsVirtualAny> && + !std::is_same_v, Any> && + std::is_constructible_v>> + virtual_any(T&& value) + : obj(std::forward(value)), + vp(detail::box_vptr( + Registry::template static_vptr>)) { + Registry::require_initialized(); + BOOST_ASSERT(detail::unbox_vptr(vp) != nullptr); + } + + //! Copy constructor. + virtual_any(const virtual_any& other) = default; + + //! Move constructor. + //! + //! Moves the `any`, and sets `other`'s v-table pointer to null. + //! + //! @param other A `virtual_any`. + virtual_any(virtual_any&& other) : obj(std::move(other.obj)), vp(other.vp) { + other.vp = detail::box_vptr(detail::null_vptr); + } + + //! Copy assignment operator. + auto operator=(const virtual_any& other) -> virtual_any& = default; + + //! Move assignment operator. + //! + //! Moves the `any`, and sets `other`'s v-table pointer to null. + //! + //! @param other A `virtual_any`. + auto operator=(virtual_any&& other) -> virtual_any& { + obj = std::move(other.obj); + vp = other.vp; + other.vp = detail::box_vptr(detail::null_vptr); + return *this; + } + + //! Assign from an `any` (copy). + //! + //! Copies `other`, and re-acquires the v-table pointer for the + //! contained value. + //! + //! @param other An `any`. + auto operator=(const Any& other) -> virtual_any& { + obj = other; + vp = detail::box_vptr( + detail::acquire_vptr(obj)); + return *this; + } + + //! Assign from an `any` (move). + //! + //! Moves `other`, and re-acquires the v-table pointer for the + //! contained value. + //! + //! @param other An `any`. + auto operator=(Any&& other) -> virtual_any& { + obj = std::move(other); + vp = detail::box_vptr( + detail::acquire_vptr(obj)); + return *this; + } + + //! Assign from a value. + //! + //! Stores `value` in the `any`, and sets the v-table pointer to the + //! @ref registry::static_vptr for its type - no hash table lookup is + //! involved. + //! + //! @tparam T The type of the value. + //! @param value The value to store. + template< + typename T, + typename = std::enable_if_t< + !BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + IsVirtualAny> && + !std::is_same_v, Any> && + std::is_constructible_v>> + auto operator=(T&& value) -> virtual_any& { + obj = std::forward(value); + Registry::require_initialized(); + vp = detail::box_vptr( + Registry::template static_vptr>); + BOOST_ASSERT(detail::unbox_vptr(vp) != nullptr); + return *this; + } + + //! Construct a value in place. + //! + //! Stores a `Class` constructed from `args`, and sets the v-table + //! pointer to the @ref registry::static_vptr for `Class` - no hash + //! table lookup is involved. + //! + //! @tparam Class The type of the value to construct. + //! @tparam T Types of the arguments to pass to the constructor. + //! @param args Arguments to pass to the constructor of `Class`. + //! + //! @par Example + //! include:virtual_any.cpp#emplace + template + auto emplace(T&&... args) -> void { + obj = Class(std::forward(args)...); + Registry::require_initialized(); + vp = detail::box_vptr( + Registry::template static_vptr); + BOOST_ASSERT(detail::unbox_vptr(vp) != nullptr); + } + + //! Return a reference to the (non-modifiable) `any`. + auto get() const -> const Any& { + return obj; + } + + //! Return the v-table pointer. + auto vptr() const -> vptr_type { + return detail::unbox_vptr(vp); + } + +#ifndef __MRDOCS__ + // The parameter is deduced, and constrained to be exactly this + // `virtual_any`, so that the function is not viable for a type that + // is merely convertible to it. MSVC, in its default (permissive) + // mode, injects friend functions into the enclosing namespace, where + // ordinary lookup finds them. An unconstrained `const virtual_any&` + // parameter would then make this a candidate for a plain `Any`, + // which converts implicitly to `virtual_any` - and the conversion + // acquires the v-table pointer, which calls this function, ad + // infinitum. + template + friend auto boost_openmethod_vptr(const Self& va, Registry*) + -> std::enable_if_t, vptr_type> { + return detail::unbox_vptr(va.vp); + } +#endif +}; + +//! Specialize virtual_traits for `const virtual_any&`. +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&, Registry> { + //! The type used for dispatch. + using virtual_type = Any; + + //! Returns a const reference to the `virtual_any` argument. + //! @param arg A reference to a `virtual_any`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any& arg) + -> const virtual_any& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any` itself (by any reference category), + //! returns `arg` unchanged. Otherwise, extracts the stored value + //! using `virtual_traits::cast`. Since the + //! `any` is not modifiable, `U` cannot be a mutable reference. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg A reference to a const `virtual_any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(const virtual_any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any>) { + return (arg); + } else { + return virtual_traits::template cast( + arg.obj); + } + } +}; + +//! Specialize virtual_traits for `virtual_any&` (mutable reference). +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&, Registry> { + //! The type used for dispatch. + using virtual_type = Any; + + //! Returns a const reference to the `virtual_any` argument. + //! @param arg A reference to a `virtual_any`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any& arg) + -> const virtual_any& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any` itself (by mutable reference), returns + //! `arg` unchanged. Otherwise, extracts the stored value using + //! `virtual_traits::cast`. Supports mutable + //! references (e.g. `Dog&`); modifications through the result are + //! visible through the `virtual_any`. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg A mutable reference to the `virtual_any` method + //! argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(virtual_any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any>) { + return (arg); + } else { + return virtual_traits::template cast(arg.obj); + } + } +}; + +//! Specialize virtual_traits for `virtual_any&&` (xvalue reference). +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&&, Registry> { + //! The type used for dispatch. + using virtual_type = Any; + + //! Returns a const reference to the `virtual_any` argument. + //! @param arg A reference to a `virtual_any`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any& arg) + -> const virtual_any& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any` itself (by rvalue reference), returns + //! `arg` unchanged. Otherwise, extracts the stored value using + //! `virtual_traits::cast`. + //! + //! @tparam U The target type (e.g. `Dog&&`, `const Dog&`, `Dog`). + //! @param arg An rvalue reference to the `virtual_any` method + //! argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(virtual_any&& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any>) { + return std::move(arg); + } else { + return virtual_traits::template cast( + std::move(arg.obj)); + } + } +}; + +namespace detail { + +template +struct is_virtual&> : std::true_type {}; + +template +struct is_virtual&> : std::true_type {}; + +template +struct is_virtual&&> : std::true_type {}; + +template +struct parameter_traits&, Registry> + : virtual_traits&, Registry> {}; + +template +struct parameter_traits&, Registry> + : virtual_traits&, Registry> {}; + +template +struct parameter_traits&&, Registry> + : virtual_traits&&, Registry> {}; + +template +struct validate_method_parameter< + virtual_any, MethodRegistry, void> : std::false_type { + static_assert( + false_t, "virtual_any must be passed by reference"); +}; + +template +struct validate_method_parameter< + virtual_any&, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +template +struct validate_method_parameter< + const virtual_any&, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +template +struct validate_method_parameter< + virtual_any&&, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +// A virtual_any method parameter places no compile-time constraint on the +// corresponding overrider parameter: the adjustment is delegated entirely +// to virtual_traits::cast, like for virtual_ +// parameters. The exact-pair specializations disambiguate with the +// generic specialization in core.hpp, which is neither more nor +// less specialized than . + +template +struct validate_overrider_parameter&, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + virtual_any&, virtual_any&, void> + : std::true_type {}; + +template +struct validate_overrider_parameter&, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + const virtual_any&, const virtual_any&, void> + : std::true_type {}; + +template +struct validate_overrider_parameter&&, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + virtual_any&&, virtual_any&&, void> + : std::true_type {}; + +template +struct select_overrider_virtual_type_aux< + virtual_any&, Q, Registry> { + using type = virtual_type; +}; + +template +struct select_overrider_virtual_type_aux< + const virtual_any&, Q, Registry> { + using type = virtual_type; +}; + +template +struct select_overrider_virtual_type_aux< + virtual_any&&, Q, Registry> { + using type = virtual_type; +}; + +} // namespace detail + +//! A wide reference to an `any`: a pointer to an `any`, and a pointer to +//! a v-table. +//! +//! `virtual_any_ref` is the non-owning counterpart of @ref virtual_any: +//! it *refers to* an existing `any` instead of holding a copy, and carries +//! the v-table pointer for the contained value, so methods dispatch on +//! the contained type without looking it up on every call. It is a +//! cheap, two-word handle with pointer semantics - copying it copies the +//! two words - and, like the reference-wrapper flavors of +//! Boost.TypeErasure's `any`, it is passed to methods *by value*. +//! +//! `Any` may be const-qualified: through `virtual_any_ref`, +//! overriders can only take the contained value by value or by const +//! reference; `virtual_any_ref` also supports mutable references, +//! and modifications reach the referent. A `virtual_any_ref` +//! converts to a `virtual_any_ref`. +//! +//! The v-table pointer is acquired when the handle is created: from the +//! dynamic type of the value contained in the `any` (a hash table +//! lookup), or at no cost from a @ref virtual_any, which already carries +//! it. 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. +//! +//! An overrider takes the *contained* value - or, for a catch-all +//! overrider, the `virtual_any_ref` itself, by value. Since a plain +//! value does not convert to a `virtual_any_ref`, overriders taking the +//! contained value are registered with the core API +//! (`method<...>::override`) rather than with +//! @ref BOOST_OPENMETHOD_OVERRIDE, which locates the method by +//! convertibility. +//! +//! `Any` can be `std::any`, `boost::any`, or any type that has an +//! `any`-like interface, and specializes `virtual_traits` for its +//! reference types, providing `vptr` and `cast`. +//! +//! @tparam Any An `any` type, possibly const-qualified. +//! @tparam Registry A @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#ref;ref_dispatch +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +class virtual_any_ref { + static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; + + using owner_type = std::conditional_t< + std::is_const_v, + const virtual_any, Registry>, + virtual_any, Registry>>; + + Any* obj; + std::conditional_t vp; + + template + friend struct virtual_traits; + + template + friend class virtual_any_ref; + + public: + //! Construct from an `any`. + //! + //! Acquires the v-table pointer for the contained value, using + //! `virtual_traits::vptr` - a hash table + //! lookup. + //! + //! @param other An `any` lvalue. + virtual_any_ref(Any& other) + : obj(&other), vp(detail::box_vptr( + detail::acquire_vptr(other))) { + } + + //! A `virtual_any_ref` cannot refer to a temporary `any`. + virtual_any_ref(std::remove_const_t&&) = delete; + + //! Construct from a `virtual_any`. + //! + //! Refers to the `any` held by `other`, and copies its v-table pointer + //! - no lookup is involved. A `virtual_any_ref` can + //! refer to a const `virtual_any`; a mutable one requires a + //! mutable `virtual_any`. + //! + //! @param other A `virtual_any` lvalue. + virtual_any_ref(owner_type& other) : obj(&other.obj), vp(other.vp) { + } + + //! A `virtual_any_ref` cannot refer to a temporary `virtual_any`. + virtual_any_ref(std::remove_const_t&&) = delete; + + //! Convert a mutable `virtual_any_ref` to a const one. + template< + class Other, + typename = std::enable_if_t< + std::is_const_v && + std::is_same_v>>> + virtual_any_ref(virtual_any_ref other) + : obj(other.obj), vp(other.vp) { + } + + //! Return a reference to the (non-modifiable) `any`. + auto get() const -> const Any& { + return *obj; + } + + //! Return the v-table pointer. + auto vptr() const -> vptr_type { + return detail::unbox_vptr(vp); + } + +#ifndef __MRDOCS__ + // Constrained to exactly this `virtual_any_ref`, for the same reason + // as in `virtual_any`: MSVC, in its default (permissive) mode, + // injects friend functions into the enclosing namespace, where an + // unconstrained parameter would make this a candidate for anything + // convertible to `virtual_any_ref`. + template + friend auto boost_openmethod_vptr(const Self& va, Registry*) + -> std::enable_if_t, vptr_type> { + return detail::unbox_vptr(va.vp); + } +#endif +}; + +//! Specialize virtual_traits for `virtual_any_ref`, passed by value. +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any_ref`. +//! +//! @tparam Any An `any` type, possibly const-qualified. +//! @tparam Registry A @ref registry. +template +struct virtual_traits, Registry> { + //! The type used for dispatch. + using virtual_type = std::remove_const_t; + + //! Returns a const reference to the `virtual_any_ref` argument. + //! @param arg A reference to a `virtual_any_ref`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any_ref& arg) + -> const virtual_any_ref& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any_ref` itself, returns a copy of the + //! handle. Otherwise, extracts the referent's value using the + //! `virtual_traits` for the `any`'s reference type: mutable + //! references (e.g. `Dog&`) are supported unless `Any` is + //! const-qualified. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg The `virtual_any_ref` method argument. + //! @return The value referred to by `arg`, cast to `U`. + template + static auto cast(virtual_any_ref arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any_ref>) { + // by value: a reference would dangle when this function's + // parameter goes out of scope + return arg; + } else if constexpr (std::is_const_v) { + return virtual_traits::template cast< + U>(*arg.obj); + } else { + return virtual_traits::template cast( + *arg.obj); + } + } +}; + +namespace detail { + +template +struct is_virtual> : std::true_type {}; + +template +struct parameter_traits, Registry> + : virtual_traits, Registry> {}; + +template +struct validate_method_parameter< + virtual_any_ref, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +template +struct validate_method_parameter< + virtual_any_ref&, MethodRegistry, void> : std::false_type { + static_assert( + false_t, + "virtual_any_ref is a cheap handle, pass it by value"); +}; + +template +struct validate_method_parameter< + const virtual_any_ref&, MethodRegistry, void> + : std::false_type { + static_assert( + false_t, + "virtual_any_ref is a cheap handle, pass it by value"); +}; + +template +struct validate_method_parameter< + virtual_any_ref&&, MethodRegistry, void> : std::false_type { + static_assert( + false_t, + "virtual_any_ref is a cheap handle, pass it by value"); +}; + +template +struct validate_overrider_parameter, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + virtual_any_ref, virtual_any_ref, void> + : std::true_type {}; + +template +struct select_overrider_virtual_type_aux< + virtual_any_ref, Q, Registry> { + using type = virtual_type; +}; + +} // namespace detail + +namespace aliases { +using boost::openmethod::virtual_any; +using boost::openmethod::virtual_any_ref; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 9fcbc84c..87371943 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -163,6 +163,8 @@ inline constexpr bool method_not_found = false; //! can be called with `result` and a `Registry*`, and it returns a //! `vptr_type`, call it. //! +//! @li If `virtual_traits` provides a `vptr` function, call it. +//! //! @li Call the //! [dynamic_vptr](xref:reference:boost/openmethod/policies/VptrFn/dynamic_vptr.adoc) //! of the registry's `vptr` policy. diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index 61927e2b..ef5d2c09 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -83,7 +83,7 @@ class vptr_map : public vptr { st().vptrs.swap(new_vptrs); } - //! Returns a reference to a v-table pointer for an object. + //! Returns a *reference* to a v-table pointer for an object. //! //! Acquires the dynamic @ref type_id of `arg`, using the registry's //! @ref rtti policy. @@ -96,10 +96,23 @@ class vptr_map : public vptr { //! //! @tparam Class A registered class. //! @param arg A reference to a const object of type `Class`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { - auto type = Registry::rtti::dynamic_type(arg); + return vptr(Registry::rtti::dynamic_type(arg)); + } + + //! Returns a *reference* to a v-table pointer for a type. + //! + //! If the registry contains the @ref runtime_checks policy, checks that + //! the map contains the type id. If it does not, and if the registry + //! contains a @ref error_handler policy, calls its + //! @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param type A `type_id`. + //! @return A reference to the v-table pointer for `type`. + static auto vptr(type_id type) -> const vptr_type& { auto iter = st().vptrs.find(type); if constexpr (Registry::has_runtime_checks) { diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index 3c4ec999..304d2ab9 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -152,15 +152,31 @@ struct vptr_vector : vptr { //! //! @tparam Class A registered class. //! @param arg A reference to a const object of type `Class`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { - auto dynamic_type = Registry::rtti::dynamic_type(arg); + return vptr(Registry::rtti::dynamic_type(arg)); + }; + + //! Returns a *reference* to a v-table pointer for a type. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param type A `type_id`. + //! @return A reference to the v-table pointer for `type`. + static auto vptr(type_id type) -> const vptr_type& { std::size_t index; if constexpr (has_type_hash) { - index = type_hash::hash(dynamic_type); + index = type_hash::hash(type); } else { - index = std::size_t(dynamic_type); + index = std::size_t(type); if constexpr (Registry::has_runtime_checks) { std::size_t max_index = st().vptrs.size(); @@ -168,7 +184,7 @@ struct vptr_vector : vptr { if (index >= max_index) { if constexpr (Registry::has_error_handler) { missing_class error; - error.type = dynamic_type; + error.type = type; Registry::error_handler::error(error); } diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 7fe9270d..0d53ee71 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -707,10 +707,29 @@ struct VptrFn { //! //! @tparam Class A registered class. //! @param arg A reference to a const object of type `Class`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type&; + // Added by the `std::any` interop, under the name `type_vptr`. An `any` + // knows the `type_id` of the value it contains, but has no object of that + // type to hand to `dynamic_vptr`. + + //! Return a *reference* to the v-table pointer for a type. + //! + //! Return a reference to the v-table pointer that `initialize` associated + //! to `type`. + //! + //! This function is optional. Implement it if the registry is to be used + //! with virtual parameters whose `virtual_traits` supply a `type_id` + //! themselves, instead of an object - see @ref VirtualTraits::vptr. Both + //! @ref vptr_vector and @ref vptr_map provide it, and implement + //! `dynamic_vptr` in terms of it. + //! + //! @param type A `type_id`. + //! @return A reference to the v-table pointer for `type`. + static auto vptr(type_id type) -> const vptr_type&; + //! Release the resources allocated by `initialize`. //! //! This function is optional. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f9f4524b..912c39e0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -155,6 +155,46 @@ openmethod_compile_fail_test( compile_fail_repeated_inheritance "repeated inheritance") openmethod_compile_fail_test( compile_fail_override_method_not_found "cannot find 'speak' method that accepts the same arguments as the overrider") +# The constrained `cast` is removed from the overload set, so the diagnostic is +# the compiler's own overload resolution failure, whose wording varies: "no +# matching function for call to" on clang and gcc, "no matching overloaded +# function found" on MSVC. +openmethod_compile_fail_test( + compile_fail_boost_any_const_ref_to_mutable_ref "no matching") +openmethod_compile_fail_test( + compile_fail_boost_any_mutable_ref_to_rvalue_ref "no matching") +openmethod_compile_fail_test( + compile_fail_virtual_any_by_value "virtual_any must be passed by reference") +openmethod_compile_fail_test( + compile_fail_virtual_any_ref_by_ref + "virtual_any_ref is a cheap handle, pass it by value") +# Copy-initializing a virtual_any from a virtual_any_ref is ill-formed: the +# value constructor is constrained away, so storing the handle inside the +# `any` would take two user-defined conversions. The diagnostic is the +# compiler's own, and the wording varies: "conversion from ... to non-scalar +# type ... requested" on gcc, "no viable conversion from" on clang, C2440 +# "cannot convert from" on MSVC. +# +# MSVC needs /permissive- here. 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. Note this means the constraint does not +# actually protect MSVC users building in the default mode; only the +# direct-initialization form, which no compiler rejects, is worse. +openmethod_compile_fail_test( + compile_fail_virtual_any_from_ref "conversion from|cannot convert from") +if (MSVC) + target_compile_options( + boost_openmethod-compile_fail_virtual_any_from_ref PRIVATE /permissive-) +endif() +# "use of a deleted function" on gcc, "call to deleted function" on clang, +# "attempting to reference a deleted function" on MSVC. +openmethod_compile_fail_test( + compile_fail_final_virtual_ptr_std_any "deleted function") +openmethod_compile_fail_test( + compile_fail_virtual_ptr_inplace_vptr + "do not wrap an object that has a boost_openmethod_vptr overload") +openmethod_compile_fail_test( + compile_fail_std_any_custom_rtti "requires standard RTTI") if (TARGET Boost::dll) add_subdirectory(dynamic_loading) diff --git a/test/Jamfile b/test/Jamfile index a1c69c4e..b67ca9fd 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -20,6 +20,7 @@ project cxx17_structured_bindings ] /boost/openmethod//boost_openmethod + /boost/any//boost_any extra @@ -41,11 +42,18 @@ for local src in [ glob test_*.cpp ] run mix_release_debug/main.cpp mix_release_debug/lib.cpp unit_test_framework ; -for local src in [ glob compile_fail_*.cpp ] +for local src in [ glob compile_fail_*.cpp : compile_fail_virtual_any_from_ref.cpp ] { compile-fail $(src) ; } +# Excluded from the glob above because it needs /permissive- on msvc: copy- +# initializing a virtual_any from a virtual_any_ref takes two user-defined +# conversions, which msvc accepts in its default mode, so the compile would +# succeed and the test fail. See test/CMakeLists.txt for the details. +compile-fail compile_fail_virtual_any_from_ref.cpp + : msvc:/permissive- ; + build-project dynamic_loading ; build-project implicit_shared_libraries ; diff --git a/test/compile_fail_boost_any_const_ref_to_mutable_ref.cpp b/test/compile_fail_boost_any_const_ref_to_mutable_ref.cpp new file mode 100644 index 00000000..99058c55 --- /dev/null +++ b/test/compile_fail_boost_any_const_ref_to_mutable_ref.cpp @@ -0,0 +1,31 @@ +// 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) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_boost_any_types); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +// The `any` is const, so boost::any_cast cannot produce a mutable reference to +// the value it contains. Without the constraint on `cast`, this would fail +// inside Boost.Any instead of at the trait. +BOOST_OPENMETHOD_OVERRIDE(name, (Dog & dog), std::string) { + return dog.name; +} + +int main() { + return 0; +} diff --git a/test/compile_fail_boost_any_mutable_ref_to_rvalue_ref.cpp b/test/compile_fail_boost_any_mutable_ref_to_rvalue_ref.cpp new file mode 100644 index 00000000..d738e2d2 --- /dev/null +++ b/test/compile_fail_boost_any_mutable_ref_to_rvalue_ref.cpp @@ -0,0 +1,41 @@ +// 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) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_boost_any_types); + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +// Unlike std::any_cast, boost::any_cast binds an rvalue reference to the value +// stored in an lvalue `any`, which would let this overrider move the value out +// of an `any` the caller still owns. Moving the value out must go through a +// virtual_ parameter. +// +// The overrider is registered via method<...>::override because +// BOOST_OPENMETHOD_OVERRIDE cannot locate a method whose virtual parameter is +// a mutable lvalue reference to `any` - see test_dispatch_boost_any.cpp. +auto bump_dog(Dog&& dog) -> std::string { + return std::move(dog.name); +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +int main() { + return 0; +} diff --git a/test/compile_fail_final_virtual_ptr_std_any.cpp b/test/compile_fail_final_virtual_ptr_std_any.cpp new file mode 100644 index 00000000..60910da3 --- /dev/null +++ b/test/compile_fail_final_virtual_ptr_std_any.cpp @@ -0,0 +1,27 @@ +// 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) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +int main() { + // The primary final_virtual_ptr would use static_vptr - the + // v-table of the `any` root class, not of the contained value. The + // combination is deleted; use virtual_any instead. + std::any spot(Dog{"Spot"}); + final_virtual_ptr(spot); + return 0; +} diff --git a/test/compile_fail_std_any_custom_rtti.cpp b/test/compile_fail_std_any_custom_rtti.cpp new file mode 100644 index 00000000..de4a9e13 --- /dev/null +++ b/test/compile_fail_std_any_custom_rtti.cpp @@ -0,0 +1,61 @@ +// 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) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +template +struct type_tag { + static constexpr char id = 0; +}; + +// A complete rtti policy that identifies classes by the address of a per-class +// static variable, rather than by `&typeid(T)`. Nothing else in the library +// objects to it - only the `any` interop does. +struct custom_rtti : policies::rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = false; + + template + static auto static_type() -> type_id { + return &type_tag::id; + } + + template + static auto dynamic_type(const T&) -> type_id { + return &type_tag::id; + } + }; +}; + +struct custom_rtti_registry + : default_registry::with::without {}; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +// Dispatching on a `std::any` keys on the `std::type_info` returned by +// `std::any::type()`, so the registry's rtti policy must identify classes the +// same way. This one does not: the lookup key would be meaningless, and +// `type_id` being `const void*`, the call would otherwise compile silently. +BOOST_OPENMETHOD( + name, (virtual_), std::string, custom_rtti_registry); + +int main() { + // Call the method: declaring it is not enough to instantiate it on + // every compiler, and the guard lives in `virtual_traits::vptr`. + std::any dog = Dog{"Snoopy"}; + return name(dog).size(); +} diff --git a/test/compile_fail_virtual_any_by_value.cpp b/test/compile_fail_virtual_any_by_value.cpp new file mode 100644 index 00000000..fa30d78e --- /dev/null +++ b/test/compile_fail_virtual_any_by_value.cpp @@ -0,0 +1,27 @@ +// 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) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +// A virtual_any method parameter must be a reference: passing it by value +// would copy the `any` - and its payload - on every call. +BOOST_OPENMETHOD(name, (virtual_std_any), std::string); + +int main() { + virtual_std_any dog = Dog{"Snoopy"}; + return name(dog).size(); +} diff --git a/test/compile_fail_virtual_any_from_ref.cpp b/test/compile_fail_virtual_any_from_ref.cpp new file mode 100644 index 00000000..5513417d --- /dev/null +++ b/test/compile_fail_virtual_any_from_ref.cpp @@ -0,0 +1,34 @@ +// 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) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +int main() { + std::any dog(Dog{"Snoopy"}); + virtual_any_ref ref(dog); + + // A virtual_any cannot be copy-initialized from a virtual_any_ref. The + // value constructor is constrained to reject every wide type, so storing + // the handle inside the `any` would take two user-defined conversions - + // virtual_any_ref to std::any, then std::any to virtual_any - which is + // one more than an implicit conversion sequence allows. Were the handle + // stored, its static_vptr would be null: a handle is not a registered + // class. + virtual_std_any copy = ref; + + return 0; +} diff --git a/test/compile_fail_virtual_any_ref_by_ref.cpp b/test/compile_fail_virtual_any_ref_by_ref.cpp new file mode 100644 index 00000000..492e5996 --- /dev/null +++ b/test/compile_fail_virtual_any_ref_by_ref.cpp @@ -0,0 +1,27 @@ +// 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) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +// A virtual_any_ref method parameter is passed by value: it is a cheap, +// two-word handle; a reference would add an indirection for nothing. +BOOST_OPENMETHOD(name, (const virtual_any_ref&), std::string); + +int main() { + std::any dog(Dog{"Snoopy"}); + return name(virtual_any_ref(dog)).size(); +} diff --git a/test/compile_fail_virtual_ptr_inplace_vptr.cpp b/test/compile_fail_virtual_ptr_inplace_vptr.cpp new file mode 100644 index 00000000..f0043950 --- /dev/null +++ b/test/compile_fail_virtual_ptr_inplace_vptr.cpp @@ -0,0 +1,22 @@ +// 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) + +#include +#include + +using namespace boost::openmethod; + +struct Animal : inplace_vptr_base { + virtual ~Animal() = default; +}; + +// An object with a boost_openmethod_vptr overload carries its own v-table +// pointer; wrapping it in a virtual_ptr is rejected at compile time. + +int main() { + Animal animal; + virtual_ptr p(animal); + return 0; +} diff --git a/test/test_core.cpp b/test/test_core.cpp index 5fd126b6..cc2d9695 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -283,20 +283,16 @@ namespace TEST_NS { using test_registry = test_registry_<__COUNTER__>; -const detail::word value; - struct Animal { - friend auto boost_openmethod_vptr(const Animal&, test_registry*) { - return &value; - } + friend auto + boost_openmethod_vptr(const Animal&, test_registry*) -> vptr_type; }; static_assert(detail::has_vptr_fn); static_assert(!detail::has_vptr_fn); -BOOST_AUTO_TEST_CASE(vptr_from_function) { - initialize(); - BOOST_TEST(detail::acquire_vptr(Animal{}) == &value); -} +// The hook serves dispatch (method::vptr), not virtual_ptr: acquire_vptr +// rejects classes with a boost_openmethod_vptr overload at compile time - +// see compile_fail_virtual_ptr_inplace_vptr.cpp. } // namespace TEST_NS diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp new file mode 100644 index 00000000..7142f9bc --- /dev/null +++ b/test/test_dispatch_boost_any.cpp @@ -0,0 +1,289 @@ +// 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) + +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE dispatch_boost_any +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const boost::any& (const ref) + +static_assert(detail::has_vptr< + virtual_traits, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +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) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(boost_any_by_const_ref) { + initialize(trace()); + + const boost::any spot(Dog{"Spot"}); + const boost::any felix(std::string{"Felix the cat"}); + const boost::any answer(42); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(felix) == "Felix the cat"); + BOOST_TEST(name(answer) == "42 the integer"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as boost::any& (mutable ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this. It locates the method by +// checking that the overrider's parameter types can be passed to the method's +// forwarder (see enable_forwarder and the guide function in macros.hpp), and +// `Dog&` does not convert to `boost::any&`. A temporary `boost::any` binds to +// `const boost::any&` and to `boost::any&&`, which is why the other two +// reference categories can use the macro; nothing binds to a mutable lvalue +// reference. Register directly via method<...>::override instead - the +// primitive the macro itself expands to. + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_string(std::string& name) -> std::string { + name += "!"; + return name; +} + +auto bump_int(int& value) -> std::string { + ++value; + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(boost_any_by_mutable_ref) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + boost::any felix(std::string{"Felix the cat"}); + boost::any answer(41); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(boost::any_cast(spot).name == "Spot Jr."); + + BOOST_TEST(bump(felix) == "Felix the cat!"); + BOOST_TEST(boost::any_cast(felix) == "Felix the cat!"); + + BOOST_TEST(bump(answer) == "42 the integer"); + BOOST_TEST(boost::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as boost::any&& (xvalue ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (int&& value), std::string) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(boost_any_by_xvalue_ref) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the `any` still owns the Dog + BOOST_TEST(!spot.empty()); + BOOST_TEST(boost::any_cast(spot).name == ""); + + boost::any felix(std::string{"Felix the cat"}); + BOOST_TEST(steal(std::move(felix)) == "Felix the cat"); + BOOST_TEST(!felix.empty()); + BOOST_TEST(boost::any_cast(felix) == ""); + + // moving an int copies it + boost::any answer(42); + BOOST_TEST(steal(std::move(answer)) == "42 the integer"); + BOOST_TEST(boost::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// catch-all overriders + +#define MAKE_CATCH_ALL_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +MAKE_CATCH_ALL_CLASSES(); + +// An overrider may take the `any` itself. Since every registered type +// derives from it, such an overrider is a catch-all, applying to any +// contained type that has no more specific overrider. + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const boost::any&), std::string) { + return "something else"; +} + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_any(boost::any&) -> std::string { + return "something else"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (boost::any&&), std::string) { + return "something else"; +} + +BOOST_AUTO_TEST_CASE(boost_any_catch_all) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + boost::any pi(3.14f); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(pi) == "something else"); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(bump(pi) == "something else"); + + BOOST_TEST(steal(boost::any(Dog{"Fido"})) == "Fido the dog"); + BOOST_TEST(steal(std::move(pi)) == "something else"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// `any` and ordinary virtual parameters mixed in one method + +MAKE_CATCH_ALL_CLASSES(); + +struct Animal { + virtual ~Animal() { + } +}; + +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat); + +BOOST_OPENMETHOD( + meet, (virtual_, virtual_ptr), + std::string); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const Dog& dog, virtual_ptr), std::string) { + return dog.name + " meets a cat"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const boost::any&, virtual_ptr), std::string) { + return "someone meets an animal"; +} + +BOOST_AUTO_TEST_CASE(boost_any_mixed_with_virtual_ptr) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + boost::any pi(3.14f); + Cat felix; + + BOOST_TEST(meet(spot, felix) == "Spot meets a cat"); + BOOST_TEST(meet(pi, felix) == "someone meets an animal"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp new file mode 100644 index 00000000..61ee19f5 --- /dev/null +++ b/test/test_dispatch_std_any.cpp @@ -0,0 +1,288 @@ +// 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) + +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const std::any& (const ref) + +static_assert(detail::has_vptr< + virtual_traits, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +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) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(std_any_by_const_ref) { + initialize(trace()); + + const std::any spot(Dog{"Spot"}); + const std::any felix(std::string{"Felix the cat"}); + const std::any answer(42); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(felix) == "Felix the cat"); + BOOST_TEST(name(answer) == "42 the integer"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as std::any& (mutable ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this. It locates the method by +// checking that the overrider's parameter types can be passed to the method's +// forwarder (see enable_forwarder and the guide function in macros.hpp), 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 reference +// categories can use the macro; nothing binds to a mutable lvalue reference. +// Register directly via method<...>::override instead - the primitive the +// macro itself expands to. + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_string(std::string& name) -> std::string { + name += "!"; + return name; +} + +auto bump_int(int& value) -> std::string { + ++value; + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(std_any_by_mutable_ref) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + std::any felix(std::string{"Felix the cat"}); + std::any answer(41); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(std::any_cast(spot).name == "Spot Jr."); + + BOOST_TEST(bump(felix) == "Felix the cat!"); + BOOST_TEST(std::any_cast(felix) == "Felix the cat!"); + + BOOST_TEST(bump(answer) == "42 the integer"); + BOOST_TEST(std::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as std::any&& (xvalue ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (int&& value), std::string) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(std_any_by_xvalue_ref) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the `any` still owns the Dog + BOOST_TEST(spot.has_value()); + BOOST_TEST(std::any_cast(spot).name == ""); + + std::any felix(std::string{"Felix the cat"}); + BOOST_TEST(steal(std::move(felix)) == "Felix the cat"); + BOOST_TEST(felix.has_value()); + BOOST_TEST(std::any_cast(felix) == ""); + + // moving an int copies it + std::any answer(42); + BOOST_TEST(steal(std::move(answer)) == "42 the integer"); + BOOST_TEST(std::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// catch-all overriders + +#define MAKE_CATCH_ALL_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +MAKE_CATCH_ALL_CLASSES(); + +// An overrider may take the `any` itself. Since every registered type +// derives from it, such an overrider is a catch-all, applying to any +// contained type that has no more specific overrider. + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::any&), std::string) { + return "something else"; +} + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_any(std::any&) -> std::string { + return "something else"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::any&&), std::string) { + return "something else"; +} + +BOOST_AUTO_TEST_CASE(std_any_catch_all) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + std::any pi(3.14f); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(pi) == "something else"); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(bump(pi) == "something else"); + + BOOST_TEST(steal(std::any(Dog{"Fido"})) == "Fido the dog"); + BOOST_TEST(steal(std::move(pi)) == "something else"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// `any` and ordinary virtual parameters mixed in one method + +MAKE_CATCH_ALL_CLASSES(); + +struct Animal { + virtual ~Animal() { + } +}; + +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat); + +BOOST_OPENMETHOD( + meet, (virtual_, virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const Dog& dog, virtual_ptr), std::string) { + return dog.name + " meets a cat"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const std::any&, virtual_ptr), std::string) { + return "someone meets an animal"; +} + +BOOST_AUTO_TEST_CASE(std_any_mixed_with_virtual_ptr) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + std::any pi(3.14f); + Cat felix; + + BOOST_TEST(meet(spot, felix) == "Spot meets a cat"); + BOOST_TEST(meet(pi, felix) == "someone meets an animal"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp new file mode 100644 index 00000000..a9e87588 --- /dev/null +++ b/test/test_virtual_any_boost.cpp @@ -0,0 +1,244 @@ +// 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) + +#include +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const virtual_boost_any& (const ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_boost_any&), std::string); + +// The overriders can use the macro: the value constructor of virtual_any +// makes the overrider's parameter convertible to the method's, so the +// method is located. + +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; +} + +// A catch-all overrider may keep the wrapper. +BOOST_OPENMETHOD_OVERRIDE(name, (const virtual_boost_any& va), std::string) { + return !va.get().empty() ? "something" : "nothing"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_const_ref) { + initialize(trace()); + + // from an `any`: the v-table pointer is looked up from the dynamic + // type of the contained value + const boost::any spot_any(Dog{"Spot"}); + virtual_boost_any spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(name(spot) == "Spot the dog"); + + // from a value: the v-table pointer is set statically + virtual_boost_any rex = Dog{"Rex"}; + BOOST_TEST(rex.vptr() == default_registry::static_vptr); + BOOST_TEST(name(rex) == "Rex the dog"); + + virtual_boost_any felix = std::string("Felix the cat"); + BOOST_TEST(felix.vptr() == default_registry::static_vptr); + BOOST_TEST(name(felix) == "Felix the cat"); + + // a value converts to a (temporary) virtual_any at the call site + BOOST_TEST(name(Dog{"Fido"}) == "Fido the dog"); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `boost::any` root, applies + BOOST_TEST(name(42) == "something"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_boost_any& (mutable ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_boost_any&), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this: a temporary virtual_any +// binds to `const virtual_boost_any&` and to `virtual_boost_any&&`, but +// nothing binds to a mutable lvalue reference. Register directly via +// method<...>::override instead - the primitive the macro itself +// expands to. + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_boost_any&), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_int(int& value) -> std::string { + ++value; + return "bumped"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_by_mutable_ref) { + initialize(trace()); + + virtual_boost_any spot = Dog{"Spot"}; + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + // the mutation is visible through the virtual_any + BOOST_TEST(boost::any_cast(spot.get()).name == "Spot Jr."); + + virtual_boost_any answer = 41; + BOOST_TEST(bump(answer) == "bumped"); + BOOST_TEST(boost::any_cast(answer.get()) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_boost_any&& (xvalue ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_boost_any&&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_xvalue_ref) { + initialize(trace()); + + virtual_boost_any spot = Dog{"Spot"}; + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the virtual_any still owns the Dog + BOOST_TEST(!spot.get().empty()); + BOOST_TEST(boost::any_cast(spot.get()).name == ""); + + BOOST_TEST( + steal(virtual_boost_any(std::string("Felix the cat"))) == + "Felix the cat"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// value semantics + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_boost_any&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_value_semantics) { + initialize(trace()); + + virtual_boost_any empty; + BOOST_TEST(empty.get().empty()); + BOOST_TEST(empty.vptr() == nullptr); + + virtual_boost_any rex = Dog{"Rex"}; + + // copy: independent payloads, same v-table pointer + auto copy = rex; + BOOST_TEST(copy.vptr() == rex.vptr()); + BOOST_TEST(name(copy) == "Rex the dog"); + BOOST_TEST(name(rex) == "Rex the dog"); // original unaffected + + // move: the source's v-table pointer is nulled + auto moved = std::move(copy); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(copy.vptr() == nullptr); + BOOST_TEST(name(moved) == "Rex the dog"); + + // assignment from an `any` re-derives the v-table pointer + boost::any felix_any(std::string{"Felix"}); + moved = felix_any; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + + // assignment from a value sets it statically + moved = Dog{"Snoopy"}; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(name(moved) == "Snoopy the dog"); + + // emplace constructs in place and sets it statically + moved.emplace("Sylvester"); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(boost::any_cast(moved.get()) == "Sylvester"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// indirect vptrs + +struct Dog { + std::string name; +}; + +use_boost_any_types + BOOST_OPENMETHOD_GENSYM; + +using name_method = method< + struct name_id, + std::string(const virtual_any&), + indirect_registry>; + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_indirect_vptr) { + initialize(); + + boost::any spot_any(Dog{"Spot"}); + virtual_any spot = spot_any; + BOOST_TEST(spot.vptr() == indirect_registry::static_vptr); + BOOST_TEST(name_method::fn(spot) == "Spot the dog"); + + virtual_any rex = Dog{"Rex"}; + BOOST_TEST(name_method::fn(rex) == "Rex the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_any_ref.cpp b/test/test_virtual_any_ref.cpp new file mode 100644 index 00000000..7904c966 --- /dev/null +++ b/test/test_virtual_any_ref.cpp @@ -0,0 +1,238 @@ +// 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) + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// const handle: virtual_any_ref + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_any_ref), std::string); + +// A plain value does not convert to a virtual_any_ref, so +// BOOST_OPENMETHOD_OVERRIDE cannot locate the method for overriders that +// take the contained value. Register them with the core API instead - the +// primitive the macro itself expands to. + +using name_method = + BOOST_OPENMETHOD_TYPE(name, (virtual_any_ref), std::string); + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +auto name_string(const std::string& name) -> std::string { + return name; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); +BOOST_OPENMETHOD_REGISTER(name_method::override); + +// The catch-all overrider takes the handle itself, by value; the macro +// locates the method, since the conversion is the identity. +BOOST_OPENMETHOD_OVERRIDE( + name, (virtual_any_ref va), std::string) { + return va.get().has_value() ? "something" : "nothing"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_ref_const) { + initialize(trace()); + + // from an `any`: the v-table pointer is looked up from the dynamic + // type of the contained value + const std::any spot_any(Dog{"Spot"}); + virtual_any_ref spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(&spot.get() == &spot_any); + BOOST_TEST(name(spot) == "Spot the dog"); + + // an `any` lvalue converts to a (temporary) handle at the call site + std::any felix_any(std::string{"Felix the cat"}); + BOOST_TEST(name(felix_any) == "Felix the cat"); + + // from a virtual_any: the v-table pointer is copied - no lookup + const virtual_std_any rex = Dog{"Rex"}; + virtual_any_ref rex_ref = rex; + BOOST_TEST(rex_ref.vptr() == rex.vptr()); + BOOST_TEST(name(rex_ref) == "Rex the dog"); + + // a mutable handle converts to a const one + std::any answer_any(42); + virtual_any_ref answer = answer_any; + virtual_any_ref const_answer = answer; + BOOST_TEST(const_answer.vptr() == answer.vptr()); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `std::any` root, applies + BOOST_TEST(name(const_answer) == "something"); + + // copying a handle copies the two words; both refer to the same `any` + auto copy = spot; + BOOST_TEST(©.get() == &spot_any); + BOOST_TEST(copy.vptr() == spot.vptr()); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// mutable handle: virtual_any_ref + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_any_ref), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_any_ref), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_int(int& value) -> std::string { + ++value; + return "bumped"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_ref_mutable) { + initialize(trace()); + + // the handle refers to the `any`; mutations reach the referent + std::any spot_any(Dog{"Spot"}); + BOOST_TEST(bump(spot_any) == "Spot Jr. the dog"); + BOOST_TEST(std::any_cast(spot_any).name == "Spot Jr."); + + std::any answer_any(41); + virtual_any_ref answer = answer_any; + BOOST_TEST(bump(answer) == "bumped"); + BOOST_TEST(std::any_cast(answer_any) == 42); + + // referring to a virtual_any: mutations reach the owner's value + virtual_std_any rex = Dog{"Rex"}; + virtual_any_ref rex_ref = rex; + BOOST_TEST(rex_ref.vptr() == rex.vptr()); + BOOST_TEST(bump(rex_ref) == "Rex Jr. the dog"); + BOOST_TEST(std::any_cast(rex.get()).name == "Rex Jr."); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// boost::any: virtual_any_ref is generic over the `any` type + +struct Dog { + std::string name; +}; + +use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +BOOST_OPENMETHOD(name, (virtual_any_ref), std::string); + +using name_method = BOOST_OPENMETHOD_TYPE( + name, (virtual_any_ref), std::string); + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_OPENMETHOD_OVERRIDE( + name, (virtual_any_ref va), std::string) { + return va.get().empty() ? "nothing" : "something"; +} + +BOOST_OPENMETHOD(bump, (virtual_any_ref), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_any_ref), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_ref_boost_any) { + initialize(trace()); + + const boost::any spot_any(Dog{"Spot"}); + virtual_any_ref spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(name(spot) == "Spot the dog"); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `boost::any` root, applies + boost::any answer_any(42); + BOOST_TEST(name(answer_any) == "something"); + + // mutations through a mutable handle reach the referent + boost::any rex_any(Dog{"Rex"}); + BOOST_TEST(bump(rex_any) == "Rex Jr. the dog"); + BOOST_TEST(boost::any_cast(rex_any).name == "Rex Jr."); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// indirect vptrs + +struct Dog { + std::string name; +}; + +use_std_any_types + BOOST_OPENMETHOD_GENSYM; + +using name_method = method< + struct name_id, + std::string(virtual_any_ref), + indirect_registry>; + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_ref_indirect_vptr) { + initialize(); + + std::any spot_any(Dog{"Spot"}); + virtual_any_ref spot = spot_any; + BOOST_TEST(spot.vptr() == indirect_registry::static_vptr); + BOOST_TEST(name_method::fn(spot) == "Spot the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp new file mode 100644 index 00000000..bb41a1b7 --- /dev/null +++ b/test/test_virtual_any_std.cpp @@ -0,0 +1,244 @@ +// 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) + +#include +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const virtual_std_any& (const ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_std_any&), std::string); + +// The overriders can use the macro: the value constructor of virtual_any +// makes the overrider's parameter convertible to the method's, so the +// method is located. + +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; +} + +// A catch-all overrider may keep the wrapper. +BOOST_OPENMETHOD_OVERRIDE(name, (const virtual_std_any& va), std::string) { + return va.get().has_value() ? "something" : "nothing"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_const_ref) { + initialize(trace()); + + // from an `any`: the v-table pointer is looked up from the dynamic + // type of the contained value + const std::any spot_any(Dog{"Spot"}); + virtual_std_any spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(name(spot) == "Spot the dog"); + + // from a value: the v-table pointer is set statically + virtual_std_any rex = Dog{"Rex"}; + BOOST_TEST(rex.vptr() == default_registry::static_vptr); + BOOST_TEST(name(rex) == "Rex the dog"); + + virtual_std_any felix = std::string("Felix the cat"); + BOOST_TEST(felix.vptr() == default_registry::static_vptr); + BOOST_TEST(name(felix) == "Felix the cat"); + + // a value converts to a (temporary) virtual_any at the call site + BOOST_TEST(name(Dog{"Fido"}) == "Fido the dog"); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `std::any` root, applies + BOOST_TEST(name(42) == "something"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_std_any& (mutable ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_std_any&), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this: a temporary virtual_any +// binds to `const virtual_std_any&` and to `virtual_std_any&&`, but +// nothing binds to a mutable lvalue reference. Register directly via +// method<...>::override instead - the primitive the macro itself +// expands to. + +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 + " the dog"; +} + +auto bump_int(int& value) -> std::string { + ++value; + return "bumped"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_by_mutable_ref) { + initialize(trace()); + + virtual_std_any spot = Dog{"Spot"}; + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + // the mutation is visible through the virtual_any + BOOST_TEST(std::any_cast(spot.get()).name == "Spot Jr."); + + virtual_std_any answer = 41; + BOOST_TEST(bump(answer) == "bumped"); + BOOST_TEST(std::any_cast(answer.get()) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_std_any&& (xvalue ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_std_any&&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_xvalue_ref) { + initialize(trace()); + + virtual_std_any spot = Dog{"Spot"}; + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the virtual_any still owns the Dog + BOOST_TEST(spot.get().has_value()); + BOOST_TEST(std::any_cast(spot.get()).name == ""); + + BOOST_TEST( + steal(virtual_std_any(std::string("Felix the cat"))) == + "Felix the cat"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// value semantics + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_std_any&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_value_semantics) { + initialize(trace()); + + virtual_std_any empty; + BOOST_TEST(!empty.get().has_value()); + BOOST_TEST(empty.vptr() == nullptr); + + virtual_std_any rex = Dog{"Rex"}; + + // copy: independent payloads, same v-table pointer + auto copy = rex; + BOOST_TEST(copy.vptr() == rex.vptr()); + BOOST_TEST(name(copy) == "Rex the dog"); + BOOST_TEST(name(rex) == "Rex the dog"); // original unaffected + + // move: the source's v-table pointer is nulled + auto moved = std::move(copy); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(copy.vptr() == nullptr); + BOOST_TEST(name(moved) == "Rex the dog"); + + // assignment from an `any` re-derives the v-table pointer + std::any felix_any(std::string{"Felix"}); + moved = felix_any; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + + // assignment from a value sets it statically + moved = Dog{"Snoopy"}; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(name(moved) == "Snoopy the dog"); + + // emplace constructs in place and sets it statically + moved.emplace("Sylvester"); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(std::any_cast(moved.get()) == "Sylvester"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// indirect vptrs + +struct Dog { + std::string name; +}; + +use_std_any_types + BOOST_OPENMETHOD_GENSYM; + +using name_method = method< + struct name_id, + std::string(const virtual_any&), + indirect_registry>; + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_indirect_vptr) { + initialize(); + + std::any spot_any(Dog{"Spot"}); + virtual_any spot = spot_any; + BOOST_TEST(spot.vptr() == indirect_registry::static_vptr); + BOOST_TEST(name_method::fn(spot) == "Spot the dog"); + + virtual_any rex = Dog{"Rex"}; + BOOST_TEST(name_method::fn(rex) == "Rex the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM