From f9aacc6e1a8a19c2b1888539d031d5e6eaae760a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 25 Jan 2026 21:15:34 -0500 Subject: [PATCH 01/85] BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN --- include/boost/openmethod/preamble.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 39b82c6f..4f9c3e03 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -869,6 +870,17 @@ struct initialize_aux; } // namespace detail +#define BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(FN) \ + template \ + struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux)) : std::false_type {}; \ + template \ + struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))< \ + std::void_t()...))>, T, Args...> \ + : std::true_type {}; \ + template \ + constexpr bool BOOST_PP_CAT(has_, FN) = \ + BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))::value + //! Methods, classes and policies. //! //! Methods exist in the context of a registry. Any class used as a method or From 1eb22d8877e6ca498beb4996b8c413f048d3ecbc Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Feb 2026 17:09:34 -0500 Subject: [PATCH 02/85] inter-operate with 'any' --- include/boost/openmethod/core.hpp | 9 +- include/boost/openmethod/interop/std_any.hpp | 196 ++++++++++++++++++ .../boost/openmethod/policies/vptr_map.hpp | 15 +- .../boost/openmethod/policies/vptr_vector.hpp | 24 ++- test/test_dispatch_std_any.cpp | 118 +++++++++++ 5 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 include/boost/openmethod/interop/std_any.hpp create mode 100644 test/test_dispatch_std_any.cpp diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 7c2d3837..6e8cb9a9 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -525,12 +525,19 @@ constexpr bool has_vptr_fn = std::is_same_v< std::declval(), std::declval())), vptr_type>; +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(dynamic_vptr); + template decltype(auto) acquire_vptr(const ArgType& arg) { Registry::require_initialized(); - if constexpr (detail::has_vptr_fn) { + if constexpr (has_vptr_fn) { return boost_openmethod_vptr(arg, static_cast(nullptr)); + } else if constexpr (has_dynamic_vptr< + virtual_traits, + type_id>) { + return virtual_traits::dynamic_vptr( + arg); } else { return Registry::template policy::dynamic_vptr(arg); } diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp new file mode 100644 index 00000000..89af1e59 --- /dev/null +++ b/include/boost/openmethod/interop/std_any.hpp @@ -0,0 +1,196 @@ +// 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 + +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 {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +} // namespace detail + +//! Specialize virtual_traits for std::any by value. +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. Requires the registry to use a @ref +//! rtti policy that provides `dynamic_type` (e.g. @ref std_rtti). +//! +//! @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 dynamic @ref type_id of `arg`, using the registry's + //! @ref rtti policy. + //! + //! 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 a the v-table pointer for `Class`. + static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { + return Registry::rtti::type_vptr(arg.type()); + }; + + //! Cast to a type. + //! + //! 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) { + return std::any_cast(arg); + } +}; + +//! Specialize virtual_traits for `std::any&` (mutable reference). +//! +//! @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 dynamic @ref type_id of `arg`, using the registry's + //! @ref rtti policy. + //! + //! 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 a the v-table pointer for `Class`. + static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { + return Registry::vptr::type_vptr(&arg.type()); + }; + + //! Cast to a type. + //! + //! Extracts the stored value using `std::any_cast`. Supports mutable + //! references (e.g. `Dog&`) because the `any` argument is non-const. + //! + //! @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(const std::any& arg) -> decltype(auto) { + return std::any_cast(arg); + } +}; + +//! Specialize virtual_traits for std::any by value. +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. Requires the registry to use a @ref +//! rtti policy that provides `dynamic_type` (e.g. @ref std_rtti). +//! +//! @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 dynamic @ref type_id of `arg`, using the registry's + //! @ref rtti policy. + //! + //! 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 a the v-table pointer for `Class`. + static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { + return Registry::rtti::type_vptr(arg.type()); + }; + + //! Cast to a type. + //! + //! 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) { + return std::any_cast(arg); + } +}; + +template +struct use_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>... {}; + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index c26e5de2..33a12b5d 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -79,7 +79,20 @@ class vptr_map : public vptr { //! @return A reference to a 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 type_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 a the v-table pointer for `type`. + static auto type_vptr(type_id type) -> const vptr_type& { auto iter = 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 1b1a2768..5020ba07 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -133,12 +133,28 @@ struct vptr_vector : vptr { //! @return A reference to a 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 type_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 a the v-table pointer for `type`. + static auto type_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 = 0; @@ -153,7 +169,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/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp new file mode 100644 index 00000000..258b31b8 --- /dev/null +++ b/test/test_dispatch_std_any.cpp @@ -0,0 +1,118 @@ +// 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_any_types BOOST_OPENMETHOD_GENSYM; + +#if 0 + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as std::any by value + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (Dog dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (Cat cat), std::string) { + return cat.name + " the cat"; +} + +BOOST_AUTO_TEST_CASE(std_any_by_value) { + initialize(); + + BOOST_TEST(name(std::any(Dog{"Spot"})) == "Spot the dog"); + BOOST_TEST(name(std::any(Cat{"Felix"})) == "Felix the cat"); +} +} // namespace BOOST_OPENMETHOD_GENSYM +#endif +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const std::any& (const ref) + +static_assert(detail::has_dynamic_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 +#if 0 +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as std::any&& (rvalue ref, move semantics) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (Dog dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (Cat cat), std::string) { + return cat.name + " the cat"; +} + +BOOST_AUTO_TEST_CASE(std_any_by_rvalue_ref) { + initialize(); + + std::any spot(Dog{"Spot"}); + std::any felix(Cat{"Felix"}); + + BOOST_TEST(name(std::move(spot)) == "Spot the dog"); + BOOST_TEST(name(std::move(felix)) == "Felix the cat"); +} +} // namespace BOOST_OPENMETHOD_GENSYM +#endif \ No newline at end of file From f0aafd3d2c0148bf85b9085fdddb826c6d37ea37 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 7 Mar 2026 12:41:09 -0500 Subject: [PATCH 03/85] inter-operate with 'any' --- include/boost/openmethod/interop/std_any.hpp | 2 +- test/test_dispatch_std_any.cpp | 21 +++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 89af1e59..15267772 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -77,7 +77,7 @@ struct virtual_traits { //! @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) { + static auto cast(const std::any& arg) { return std::any_cast(arg); } }; diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 258b31b8..b0b7667b 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -22,7 +22,7 @@ using namespace boost::openmethod; \ use_any_types BOOST_OPENMETHOD_GENSYM; -#if 0 +#if 1 namespace BOOST_OPENMETHOD_GENSYM { @@ -37,15 +37,26 @@ BOOST_OPENMETHOD_OVERRIDE(name, (Dog dog), std::string) { return dog.name + " the dog"; } -BOOST_OPENMETHOD_OVERRIDE(name, (Cat cat), std::string) { - return cat.name + " the cat"; +BOOST_OPENMETHOD_OVERRIDE(name, (std::string name), std::string) { + return name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (int value), std::string) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); } BOOST_AUTO_TEST_CASE(std_any_by_value) { initialize(); - BOOST_TEST(name(std::any(Dog{"Spot"})) == "Spot the dog"); - BOOST_TEST(name(std::any(Cat{"Felix"})) == "Felix the cat"); + 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 #endif From 22f74cf1b7bf6f52c5c259d39754e1eb72da0a49 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Wed, 29 Jul 2026 20:26:39 -0400 Subject: [PATCH 04/85] doc: document macros with MrDocs MrDocs now extracts `#define` directives as symbols (cppalliance/mrdocs#1192), so the macro reference no longer has to be written by hand. Move the content of the sixteen hand-written BOOST_OPENMETHOD*.adoc pages into doc comments on the macros themselves, and delete the pages. `ref_macros.adoc` stays as the curated basic/advanced index, now pointing at the generated reference pages. Two macros needed restructuring to have a single documented definition: * BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS is only ever tested, never defined by the library, so there was no directive to extract. Add a documentation-only `#define` under `__MRDOCS__`, after `default_registry`, so documenting it cannot change what it documents. * BOOST_OPENMETHOD_EXPORT_REGISTRY and BOOST_OPENMETHOD_INSTANTIATE_REGISTRY had one definition per ABI. Move the per-platform bodies into BOOST_OPENMETHOD_DETAIL_* macros so the public macros are defined - and documented - once. Rename the macro parameters NAME and ARGS to ID and PARAMETERS. The generated synopsis prints the real parameter names, and the prose has always called them ID and PARAMETERS. Convert the {{MACRO}} placeholders in doc comments to `@ref MACRO`, which MrDocs resolves to a proper xref, and retarget the guide pages' xrefs at the generated pages. This removes two perl substitutions from build_antora.sh: the one that rewrote {{MACRO}} into a hand-built relative link, and the {{BASE_URL}} pass over the macro pages, which MrDocs now handles itself via base-url. Requires a MrDocs new enough to support macros; an older one silently produces no macro pages, which breaks the reference xrefs. Co-Authored-By: Claude Opus 5 (1M context) --- doc/build_antora.sh | 4 +- doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc | 87 --- .../ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc | 20 - .../BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc | 56 -- .../BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc | 36 -- .../BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc | 18 - ...OOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc | 13 - .../BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc | 46 -- .../ROOT/pages/BOOST_OPENMETHOD_ID.adoc | 17 - .../BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc | 42 -- .../BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc | 17 - ...BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc | 40 -- .../ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc | 87 --- .../pages/BOOST_OPENMETHOD_OVERRIDER.adoc | 17 - .../pages/BOOST_OPENMETHOD_OVERRIDERS.adoc | 18 - .../ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc | 17 - .../ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc | 14 - doc/modules/ROOT/pages/basics.adoc | 6 +- doc/modules/ROOT/pages/core_api.adoc | 8 +- doc/modules/ROOT/pages/custom_rtti.adoc | 2 +- doc/modules/ROOT/pages/headers.adoc | 6 +- doc/modules/ROOT/pages/namespaces.adoc | 4 +- doc/modules/ROOT/pages/ref_headers.adoc | 2 +- doc/modules/ROOT/pages/ref_macros.adoc | 32 +- .../ROOT/pages/registries_and_policies.adoc | 8 +- doc/modules/ROOT/pages/shared_libraries.adoc | 8 +- doc/mrdocs.yml | 11 + include/boost/openmethod/core.hpp | 31 +- include/boost/openmethod/default_registry.hpp | 25 +- include/boost/openmethod/initialize.hpp | 2 +- include/boost/openmethod/inplace_vptr.hpp | 6 +- include/boost/openmethod/macros.hpp | 506 +++++++++++++++--- include/boost/openmethod/preamble.hpp | 6 +- 33 files changed, 538 insertions(+), 674 deletions(-) delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_ID.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDER.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDERS.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc delete mode 100644 doc/modules/ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc diff --git a/doc/build_antora.sh b/doc/build_antora.sh index 58958b9c..ce838990 100755 --- a/doc/build_antora.sh +++ b/doc/build_antora.sh @@ -85,7 +85,6 @@ echo "BRANCH='${BRANCH:-}'" echo "BASE_URL='${BASE_URL:-}'" for f in $(find html -name '*.html'); do - perl -i -pe "s{{{(.*?)}}}{\$1}g" "$f" perl -i -pe "s{Boost.OpenMethod}{Boost.OpenMethod}g" "$f" done @@ -96,8 +95,7 @@ if [ -n "${BASE_URL:-}" ]; then else echo "mrdocs.yml.bak not found; skipping restore" fi - perl -i -pe "s[{{BASE_URL}}][$BASE_URL]g" \ - html/openmethod/ref_headers.html html/openmethod/BOOST_OPENMETHOD*.html + perl -i -pe "s[{{BASE_URL}}][$BASE_URL]g" html/openmethod/ref_headers.html fi echo "Done" diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc deleted file mode 100644 index 99528713..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc +++ /dev/null @@ -1,87 +0,0 @@ - -# BOOST_OPENMETHOD - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD(ID, (PARAMETERS...), RETURN_TYPE [, REGISTRY]); -``` - -## Description - -Declares a method, called `ID`, with the given `PARAMETERS` and `RETURN_TYPE`, -and adds it to `REGISTRY`. - -`PARAMETERS` is a comma-separated list of types, possibly followed by parameter -names, just like in a function declaration. Parameters with a type in the form -`virtual_ptr` or `virtual_` are called virtual parameters. The dynamic -type of the arguments passed in virtual parameters determines which overrider to -call, following the same rules as overloaded function resolution: - -1. Form the set of all applicable overriders. An overrider is applicable - if it can be called with the arguments passed to the method. -2. If the set is empty, call the error handler (if present in the - registry), then terminate the program with `abort`. -3. Remove the overriders that are dominated by other overriders in the - set. Overrider A dominates overrider B if any of its virtual formal - parameters is more specialized than B's, and if none of B's virtual - parameters is more specialized than A's. -4. If the resulting set contains exactly one overrider, call it. - -If a single most specialized overrider does not exist, the program is -terminated via `abort`. If the registry contains an `error_handler` -policy, its `error` function is called with an object that describes the -error, prior calling `abort`. `error` may prevent termination by throwing an -exception. - -[] - -For each virtual argument `arg`, the dispatch mechanism calls -`virtual_traits::peek(arg)` and deduces the v-table pointer from the -`result`, using the first of the following methods that applies: - -1. If `result` is a `virtual_ptr`, get the pointer to the v-table from it. -2. If `boost_openmethod_vptr` can be called with `result` and a `Registry*`, - and it returns a `vptr_type`, call it. -3. Call `Registry::vptr::dynamic_vptr(result)`. - - -The macro creates an ordinary inline function in the current scope, with the -`virtual_` decorators removed from the parameter types. `virtual_ptr`{empty}s -are preserved. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. - -NOTE: The default value for `REGISTRY` is the value of -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` at the point `` is -included. Changing the value of this symbol has no effect after that point. - -## Implementation Notes - -The macro creates several additional constructs: - -* A `struct` forward declaration that acts as the method's identifier: - -```c++ -struct BOOST_OPENMETHOD_ID(ID); -``` - -* A class template declaration that acts as a container for the method's -overriders in the current scope: - -```c++ -template struct BOOST_OPENMETHOD_OVERRIDERS(NAME); -``` - -* A _guide_ function used to match overriders with the method: - -```c++ -auto BOOST_OPENMETHOD_ID(ID)_guide(...) - -> ::boost::openmethod::method< - BOOST_OPENMETHOD_ID(ID)(PARAMETERS...), RETURN_TYPE [, REGISTRY]>; -``` - -* A xref:BOOST_OPENMETHOD_REGISTER.adoc[registrar] that adds the method to the -registry. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc deleted file mode 100644 index 979a165c..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc +++ /dev/null @@ -1,20 +0,0 @@ -# BOOST_OPENMETHOD_CLASSES - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_CLASSES(CLASSES...[, REGISTRY]); -``` - -## Description - -Registers `CLASSES` in REGISTRY. - -NOTE: The default value for `REGISTRY` is the value of -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` when `` is -included. Subsequently changing it has no retroactive effect. - -This macro is a wrapper around cpp:use_classes[]; see its documentation for more -details. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc deleted file mode 100644 index 3465682c..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc +++ /dev/null @@ -1,56 +0,0 @@ -# BOOST_OPENMETHOD_DECLARE_OVERRIDER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, (PARAMETERS...), RETURN_TYPE) -``` - -## Description - -Declares an overrider for a method, but does not start its definition. This -macro can be used in header files. - -`ID` is the identifier of the method to which the overrider is added. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. - -`PARAMETERS` is a comma-separated list of types, possibly followed by parameter -names, just like in a function declaration. - -The macro tries to locate a method that can be called with the same argument -list as the overrider, possibly via argument dependent lookup. - -Each `virtual_ptr` in the method's parameter list must have a corresponding -`virtual_ptr` parameter in the same position in the overrider's parameter -list, such that `U` is the same as `T`, or has `T` as an accessible unambiguous -base. - -Each `virtual_` in the method's parameter list must have a corresponding `U` -parameter in the same position in the overrider's parameter list, such that `U` -is the same as `T`, or has `T` as an accessible unambiguous base. - -## Implementation Notes - -The macro creates additional entities in the current scope. - -* A class template declaration that acts as a container for the method's -overriders in the current scope: - -```c++ -template struct BOOST_OPENMETHOD_OVERRIDERS(NAME); -``` - -* A specialization of the container for the overrider: -+ --- -```c++ -struct BOOST_OPENMETHOD_OVERRIDERS(ID) { - static auto fn(PARAMETERS...) -> RETURN_TYPE; - static auto has_next() -> bool; - template - static auto next(typename... Args) -> RETURN_TYPE; -}; -``` diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc deleted file mode 100644 index 5febb823..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc +++ /dev/null @@ -1,36 +0,0 @@ -# BOOST_OPENMETHOD_DEFAULT_REGISTRY - -Default value for Registry - -== Synopsis - -Defined in `<https://www.github.com/boostorg/openmethod/blob/develop/include/boost/openmethod/core.hpp#L27[boost/openmethod/core.hpp]>` - -```cpp -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY ::boost::openmethod::default_registry -``` - -== Description - -The name of the default registry. - -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` is the default value for the `Registry` -template parameter of cpp:method[], cpp:use_classes[], cpp:virtual_ptr[], and -all the constructs that take a registry as a template argument. - -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` can be defined by a program to change the -default registry globally, *before* including ``. After that, changing its value has no effect, even on other macros. - -To override the default registry, proceed as follows: - -1. Define a cpp:registry[] class, either from scratch, or by tuning an existing -registry. Include ``, -``, and headers under -`boost/openmethod/policies` as needed. - -2. Set `BOOST_OPENMETHOD_DEFAULT_REGISTRY` to the new registry class. - -3. Include ``. - -NOTE;; Use this feature with caution, as it will cause ODR violations if -different translation units define different default registries. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc deleted file mode 100644 index e118ace6..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc +++ /dev/null @@ -1,18 +0,0 @@ - -# BOOST_OPENMETHOD_DEFINE_OVERRIDER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, (PARAMETERS...), RETURN_TYPE) -``` - -## Description - -Defines the body of an overrider declared with -xref:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER]. -It should be called in an implementation file, and followed by a function body. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc deleted file mode 100644 index afe3134d..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc +++ /dev/null @@ -1,13 +0,0 @@ - -# BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS - -Enables runtime checks in cpp:default_registry[]. - -## Synopsis - -May be defined by a program before including -`` to enable runtime checks. - -## Description - -See cpp:default_registry[] for details. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc deleted file mode 100644 index 5578c295..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc +++ /dev/null @@ -1,46 +0,0 @@ - -# BOOST_OPENMETHOD_EXPORT_REGISTRY - -Declares a registry's state exported, in the module that owns it. - -## Synopsis - -[source,c++] ----- -BOOST_OPENMETHOD_EXPORT_REGISTRY(registry); ----- - -Used at namespace scope, after the registry's definition, in _every_ translation -unit of the module that owns the registry. Being a declaration it may be -repeated, so it belongs in the header those translation units share. - -## Description - -All of a registry's mutable state lives in a single variable (see -cpp:registry_state[]). Sharing a registry across modules means sharing that one -symbol, which takes three macros: the _owning_ module uses -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header its translation units share and -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -in exactly one of them; every _client_ module uses -xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY]. - -They exist to hide a platform incompatibility: on Windows, Cygwin and MinGW, -`__declspec(dllexport)` and `extern` are incompatible on an explicit -instantiation, while on ELF and Mach-O the visibility attribute must be on the -declaration and must not be repeated on the definition. See -xref:shared_libraries.adoc[Shared Libraries] for the full discussion, including -the required link setup. - -On ELF it emits an exported explicit instantiation _declaration_, which both -suppresses implicit instantiation and pins the symbol to default visibility. On -declspec platforms it expands to nothing, because there the export belongs on -the instantiation instead. - -WARNING: on ELF this macro is not decoration. A translation unit of the owning -module that uses neither it nor -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -instantiates the state implicitly, and under `-fvisibility=hidden` that copy is -module-local. Since ELF merges COMDATs at the _most restrictive_ visibility, the -merged symbol becomes local: the module builds, exports nothing, and clients -fail to link with an undefined reference to `registry_state<...>::st`. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ID.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ID.adoc deleted file mode 100644 index 57eb26a3..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ID.adoc +++ /dev/null @@ -1,17 +0,0 @@ - -# BOOST_OPENMETHOD_ID - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_ID(ID) /* unspecified */ -``` - -## Description - -Generates a long, obfuscated name from a short name. All the other names -generated by macros are based on this name. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc deleted file mode 100644 index 77c00034..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc +++ /dev/null @@ -1,42 +0,0 @@ - -# BOOST_OPENMETHOD_IMPORT_REGISTRY - -Imports a registry's state from the module that owns it. - -## Synopsis - -[source,c++] ----- -BOOST_OPENMETHOD_IMPORT_REGISTRY(registry); ----- - -Used at namespace scope, after the registry's definition, in every translation -unit of every module that uses the registry without owning it. Being a -declaration it may be repeated, so it belongs in the header those modules share. - -## Description - -All of a registry's mutable state lives in a single variable (see -cpp:registry_state[]). Sharing a registry across modules means sharing that one -symbol, which takes three macros: the _owning_ module uses -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header its translation units share and -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -in exactly one of them; every _client_ module uses -xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY]. - -They exist to hide a platform incompatibility: on Windows, Cygwin and MinGW, -`__declspec(dllexport)` and `extern` are incompatible on an explicit -instantiation, while on ELF and Mach-O the visibility attribute must be on the -declaration and must not be repeated on the definition. See -xref:shared_libraries.adoc[Shared Libraries] for the full discussion, including -the required link setup. - -It emits an `extern template` declaration decorated with `BOOST_SYMBOL_IMPORT` -(`__declspec(dllimport)` on Windows, nothing on ELF). The declaration suppresses -the client's own instantiation, so it references the owner's symbol instead of -creating a private copy. - -The client module must be linked so the reference resolves: on Windows and macOS -by linking against the owning module; on ELF a dynamically loaded library may -also leave it for the dynamic linker to resolve at load time. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc deleted file mode 100644 index 97f5144b..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc +++ /dev/null @@ -1,17 +0,0 @@ -# BOOST_OPENMETHOD_INLINE_OVERRIDE - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_INLINE_OVERRIDE(ID, (PARAMETERS...), RETURN_TYPE) { - // body -} -``` - -## Description - -`BOOST_OPENMETHOD_INLINE_OVERRIDE` performs the same function as -xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE], except that the -overrider is marked `inline`. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc deleted file mode 100644 index 24fe1793..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc +++ /dev/null @@ -1,40 +0,0 @@ - -# BOOST_OPENMETHOD_INSTANTIATE_REGISTRY - -Instantiates a registry's state in the module that owns it. - -## Synopsis - -[source,c++] ----- -BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(registry); ----- - -Used at namespace scope, after the registry's definition, in _exactly one_ -translation unit of the module that owns the registry. It belongs in a `.cpp` -file, never in a header. - -## Description - -All of a registry's mutable state lives in a single variable (see -cpp:registry_state[]). Sharing a registry across modules means sharing that one -symbol, which takes three macros: the _owning_ module uses -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header its translation units share and -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -in exactly one of them; every _client_ module uses -xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY]. - -They exist to hide a platform incompatibility: on Windows, Cygwin and MinGW, -`__declspec(dllexport)` and `extern` are incompatible on an explicit -instantiation, while on ELF and Mach-O the visibility attribute must be on the -declaration and must not be repeated on the definition. See -xref:shared_libraries.adoc[Shared Libraries] for the full discussion, including -the required link setup. - -It emits the explicit instantiation _definition_ of the registry state, of which -a program may contain only one. On declspec platforms the definition carries the -`dllexport`; on ELF and Mach-O it carries no attribute, that having been -supplied by -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc deleted file mode 100644 index eb64930b..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc +++ /dev/null @@ -1,87 +0,0 @@ - -# BOOST_OPENMETHOD_OVERRIDE - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_OVERRIDE(ID, (PARAMETERS...), RETURN_TYPE) { - // body -} -``` - -## Description - -`BOOST_OPENMETHOD_OVERRIDE` adds an overrider to a method. - -`ID` is the identifier of the method to which the overrider is added. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. - -`PARAMETERS` is a comma-separated list of types, possibly followed by parameter -names, just like in a function declaration. - -The macro tries to locate a method that can be called with the same argument -list as the overrider, possibly via argument dependent lookup. - -Each `virtual_ptr` in the method's parameter list must have a corresponding -`virtual_ptr` parameter in the same position in the overrider's parameter -list, such that `U` is the same as `T`, or has `T` as an accessible unambiguous -base. - -Each `virtual_` in the method's parameter list must have a corresponding `U` -parameter in the same position in the overrider's parameter list, such that `U` -is the same as `T`, or has `T` as an accessible unambiguous base. - -The following names are available inside the overrider's body: - -* `fn`: a pointer to a function, the overrider itself. Can be used for recursion. - -* `next`: a function with the same signature as the method (minus the -`virtual_<>` decorators). It forwards to the next most specialized overrider, if -it exists and it is unique. If the next overrider does not exist, or is -ambiguous, calling `next` reports a cpp:no_overrider[] or a cpp:ambiguous_call[] -and terminates the program. - -* `has_next()`: returns `true` if the next most specialized overrider exists. - -## Implementation Notes - -The macro creates additional entities in the current scope. - -* A class template declaration that acts as a container for the method's -overriders in the current scope: - -```c++ -template struct BOOST_OPENMETHOD_OVERRIDERS(NAME); -``` - -* A specialization of the container for the overrider: -+ --- -```c++ -struct BOOST_OPENMETHOD_OVERRIDERS(ID) { - static auto fn(PARAMETERS...) -> RETURN_TYPE; - static auto has_next() -> bool; - template - static auto next(typename... Args) -> RETURN_TYPE; -}; -``` - -[] - -* A xref:BOOST_OPENMETHOD_REGISTER.adoc[registrar] adding the overrider to the -method. - -* Finally, the macro starts the definition of the overrider function: --- -```c++ -auto BOOST_OPENMETHOD_OVERRIDERS(ID)::fn( - PARAMETERS...) -> RETURN_TYPE -``` --- - -{empty} - -The `{}` block following the call to the macro is the body of the function. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDER.adoc deleted file mode 100644 index fcedb6bd..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDER.adoc +++ /dev/null @@ -1,17 +0,0 @@ - -# BOOST_OPENMETHOD_OVERRIDER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_OVERRIDER(ID, (PARAMETERS...), RETURN_TYPE) -``` - -## Description - -Expands to the specialization of the class template that contains the overrider -for with the given name, parameter list and return type. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDERS.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDERS.adoc deleted file mode 100644 index 11d42c76..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDERS.adoc +++ /dev/null @@ -1,18 +0,0 @@ - -# BOOST_OPENMETHOD_OVERRIDERS - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_OVERRIDERS(ID) \ - BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _overriders) -``` - -## Description - -`BOOST_OPENMETHOD_OVERRIDERS` expands to the name of the class template that -contains the overriders for all the methods with a given name. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc deleted file mode 100644 index 0f2d5adf..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc +++ /dev/null @@ -1,17 +0,0 @@ - -# BOOST_OPENMETHOD_REGISTER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_REGISTER(TYPE); -``` - -## Description - -Creates a registrar for `TYPE`, i.e. a static `TYPE` object with a unique -generated name. At static initialization time, the object adds itself to a list: -methods and class registrations add themselves to a cpp:registry[], and -overriders add themselves to a method's overrider list. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc deleted file mode 100644 index cc2789d8..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc +++ /dev/null @@ -1,14 +0,0 @@ -# BOOST_OPENMETHOD_TYPE - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_TYPE(ID, (PARAMETERS...), RETURN_TYPE [, REGISTRY]); -``` - -## Description - -Expands to the core cpp:method[`method`] specialization created by -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] called with the same arguments. diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index b2781aee..0d50f66d 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -15,7 +15,7 @@ class that points to an instance of `Class`. `virtual_ptr` is defined in the lib `boost::openmethod`. To create an open-method that implements the `postfix` operation, we use the -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] macro: +xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] macro: ```c++ BOOST_OPENMETHOD( @@ -35,7 +35,7 @@ inline auto postfix(virtual_ptr node, std::ostream& os) -> void { ``` Before we can call the method, we need to define overriders. For that we use the -xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] macro: +xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] macro: [source,cpp] ---- @@ -70,7 +70,7 @@ There are two more things we need to do. OpenMethod is a library, not a compiler. It needs to be informed of all the classes that may be used as virtual parameters, and in method calls, and their inheritance relationships. We provide that information with the -xref:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] macro: +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] macro: [source,cpp] diff --git a/doc/modules/ROOT/pages/core_api.adoc b/doc/modules/ROOT/pages/core_api.adoc index 5da6129c..9d67461a 100644 --- a/doc/modules/ROOT/pages/core_api.adoc +++ b/doc/modules/ROOT/pages/core_api.adoc @@ -29,7 +29,7 @@ The exact name of the identifier class does not matter. The class needs not be defined, only declared. Inventing identifier class names can get tedious, so OpenMethod provides a macro -for that: xref:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID]. Let's use it: +for that: xref:reference:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID]. Let's use it: [source,c++] ---- @@ -38,7 +38,7 @@ include::{example}/core_api.cpp[tag=method] We said macro-free interface, but here is a macro again! Well, we are not forced to use the macro. There is a benefit though: it is used in the implementation of -high-level macros like xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD]. This makes +high-level macros like xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD]. This makes it possible to mix the two styles, for example to define a method using the macro, and add overriders using the core API. @@ -61,7 +61,7 @@ include::{example}/core_api.cpp[tag=variable_overrider] Once again we find ourselves inventing a name for a single use. Maybe some day C++ will get a Python-like `_` special variable. In the meantime, we can use another convenience macro: -xref:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER]. It takes a +xref:reference:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER]. It takes a class, and instantiates a static object with an obfuscated name: [source,c++] @@ -119,7 +119,7 @@ notation: include::{example}/core_api.cpp[tag=postfix_binary] ---- -Macro xref:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] takes the same +Macro xref:reference:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] takes the same parameters as `BOOST_OPENMETHOD`, and expands to the core cpp:method[method] instance. That is how we access its nested `overrider` class template: diff --git a/doc/modules/ROOT/pages/custom_rtti.adoc b/doc/modules/ROOT/pages/custom_rtti.adoc index 40a2e41c..610cf56e 100644 --- a/doc/modules/ROOT/pages/custom_rtti.adoc +++ b/doc/modules/ROOT/pages/custom_rtti.adoc @@ -124,7 +124,7 @@ include::{example}/1/custom_rtti.cpp[tag=registry] ---- Defining macro -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] sets the default registry used by all library components that need one. Next, we include the main header. diff --git a/doc/modules/ROOT/pages/headers.adoc b/doc/modules/ROOT/pages/headers.adoc index 86f50dd5..92802fff 100644 --- a/doc/modules/ROOT/pages/headers.adoc +++ b/doc/modules/ROOT/pages/headers.adoc @@ -57,14 +57,14 @@ include::{example}/2/roles.hpp[tag=content] ---- Unlike function declarations, -xref:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] +xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] cannot appear multiple times in a translation unit with the same arguments. Also, it requires the _method_ itself to be defined prior using this macro. Overriders are placed in _overrider_ _containers_. An overrider container is a class template named after the method, declared in the current namespace. It is specialized for each overrider signature. Macro -xref:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] takes the same +xref:reference:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] takes the same arguments `BOOST_OPENMETHOD_OVERRIDE`, and expands to the corresponding specialization of the overrider container. Containers have a static member function `fn` that contains the body of the overrider, provided by the user. We can @@ -81,7 +81,7 @@ OpenMethod does, it's `next`. It is almost always the right choice. The exception is: when performance is critical, we may want to inline the call to the base overrider. -xref:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] +xref:reference:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] defines the overrider as an inline function, and it can go in a header file: [source,c++] diff --git a/doc/modules/ROOT/pages/namespaces.adoc b/doc/modules/ROOT/pages/namespaces.adoc index 28a15cb1..39e6ab20 100644 --- a/doc/modules/ROOT/pages/namespaces.adoc +++ b/doc/modules/ROOT/pages/namespaces.adoc @@ -5,8 +5,8 @@ Note;; This section uses overrider containers, described in the xref:headers.adoc[Headers and Implementation Files] section. -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] defines a method in the current -namespace. xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] works +xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] defines a method in the current +namespace. xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] works _across_ namespaces. Overriders are not required to be in the same namespace as the method they override. The macro adds the overrider to a method that can be called with the same arguments as the overrider, possibly located via argument diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 0c89651a..56277304 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -77,7 +77,7 @@ Provides a `virtual_traits` specialization that makes it possible to use a The following headers can be included before `core.hpp` to define custom registries and policies, and override the default registry by defining -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. ### link:{{BASE_URL}}/include/boost/openmethod/preamble.hpp[] diff --git a/doc/modules/ROOT/pages/ref_macros.adoc b/doc/modules/ROOT/pages/ref_macros.adoc index 71852cba..90dd589c 100644 --- a/doc/modules/ROOT/pages/ref_macros.adoc +++ b/doc/modules/ROOT/pages/ref_macros.adoc @@ -8,19 +8,19 @@ uses of the library. |=== | Name | Description. -| xref:BOOST_OPENMETHOD_CLASSES.adoc[*BOOST_OPENMETHOD_CLASSES*] +| xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[*BOOST_OPENMETHOD_CLASSES*] | Registers classes. -| xref:BOOST_OPENMETHOD.adoc[*BOOST_OPENMETHOD*] +| xref:reference:BOOST_OPENMETHOD.adoc[*BOOST_OPENMETHOD*] | Declares a method. -| xref:BOOST_OPENMETHOD_OVERRIDE.adoc[*BOOST_OPENMETHOD_OVERRIDE*] +| xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[*BOOST_OPENMETHOD_OVERRIDE*] | Adds an overrider to a method. -| xref:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] +| xref:reference:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] | Adds an overrider to a method as an inline function. -| xref:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] +| xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] | Declares a method overrider. -| xref:BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc[BOOST_OPENMETHOD_DEFINE_OVERRIDER] +| xref:reference:BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc[BOOST_OPENMETHOD_DEFINE_OVERRIDER] | Defines the body of a method overrider. -| xref:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] +| xref:reference:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] | Enables runtime checks in method calls. |=== @@ -31,22 +31,22 @@ The following macros are for advanced uses of the library. |=== | Name | Description. -| xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] | Default registry. -| xref:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] +| xref:reference:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] | Returns the class template specialization containing an overrider. -| xref:BOOST_OPENMETHOD_OVERRIDERS.adoc[BOOST_OPENMETHOD_OVERRIDERS] +| xref:reference:BOOST_OPENMETHOD_OVERRIDERS.adoc[BOOST_OPENMETHOD_OVERRIDERS] | Returns the class template containing the overriders for all the methods with a given name. -| xref:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID] +| xref:reference:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID] | Generates a method id. -| xref:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] +| xref:reference:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] | Expands to core `method` specialization. -| xref:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER] +| xref:reference:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER] | Creates a registrar object. -| xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] | Imports a registry's state from the module that owns it. -| xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] | Declares a registry's state exported, in every translation unit of the owning module. -| xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] | Instantiates a registry's state, in exactly one translation unit of the owning module. |=== diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index 89101a45..f5154381 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -6,11 +6,11 @@ same registry. If a class is used as a virtual parameter in methods using different registries, it must be registered with each of them. Class templates cpp:use_classes[], cpp:method[], cpp:virtual_ptr[], and macros -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] and -xref:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], take an additional +xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] and +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], take an additional argument, a cpp:registry[] class, which defaults to cpp:default_registry[]. The default registry can be overridden by defining the macroprocessor symbol -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] _before_ including ``. The value of the symbol is used as a default template parameter for `use_classes`, `method`, `virtual_ptr`, and others. Once the `core` header has been included, changing @@ -53,7 +53,7 @@ Policies are placed in the cpp:boost::openmethod::policies[] namespace. |=== if -xref:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] +xref:reference:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] is defined, `default_registry` also contains the `runtime_checks` policy. This enables extra validations during method dispatch, which can detect missing class registrations that could not be caught by `initialize`. diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index 507581d1..a9c6ccab 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -21,13 +21,13 @@ Each takes the registry as an argument, so they can be used to manage |=== | Macro | Where -| xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] | header; every translation unit of a _client_ module -| xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] | header; every translation unit of the _owning_ module -| xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] | exactly one `.cpp` of the owning module |=== @@ -240,7 +240,7 @@ registry that contains the cpp:indirect_vptr[] policy. `` provides an cpp:indirect_registry[] that has the same policies as `default_registry`, plus `indirect_vptr`. Make it the registry the `BOOST_OPENMETHOD` macros use by defining -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] _before_ including ``. The `indirect_vptr` example does that in the header both modules share, rather diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 380e427b..97ae2cd5 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -25,6 +25,17 @@ exclude-symbols: - 'boost::openmethod::boost_openmethod_registry' - 'boost::openmethod::registry_state::st' +# Macros. Only the public macros carry a doc comment, and with +# `extract-all-macros` off (the default) MrDocs extracts only documented ones. +# The patterns below make that explicit: the library's own implementation +# macros are never documented, whatever they are called. +include-macros: + - 'BOOST_OPENMETHOD*' +exclude-macros: + - 'BOOST_OPENMETHOD_DETAIL_*' + - 'BOOST_OPENMETHOD_GENSYM' + - 'BOOST_OPENMETHOD_GUIDE' + sort-members: false # sort-namespace-members-by: location extract-friends: false diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 68f62776..8864f915 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -24,6 +24,33 @@ #include #ifndef BOOST_OPENMETHOD_DEFAULT_REGISTRY +//! Default value for `Registry`. +//! +//! The name of the default registry. +//! +//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` is the default value for the `Registry` +//! template parameter of @ref boost::openmethod::method, +//! @ref boost::openmethod::use_classes, @ref boost::openmethod::virtual_ptr, +//! and all the constructs that take a registry as a template argument. +//! +//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` can be defined by a program to change +//! the default registry globally, *before* including +//! ``. After that, changing its value has no effect, +//! even on other macros. +//! +//! To override the default registry, proceed as follows: +//! +//! @li Define a @ref boost::openmethod::registry class, either from scratch, or +//! by tuning an existing registry. Include ``, +//! ``, and headers under +//! `boost/openmethod/policies` as needed. +//! +//! @li Set `BOOST_OPENMETHOD_DEFAULT_REGISTRY` to the new registry class. +//! +//! @li Include ``. +//! +//! @note Use this feature with caution, as it will cause ODR violations if +//! different translation units define different default registries. #define BOOST_OPENMETHOD_DEFAULT_REGISTRY ::boost::openmethod::default_registry #endif @@ -661,7 +688,7 @@ inline auto final_virtual_ptr(Arg&& obj) { //! the other way around. //! //! The default value for `Registry` can be customized by defining the -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}} +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY //! preprocessor symbol. //! //! @par Requirements @@ -2093,7 +2120,7 @@ struct validate_method_parameter< //! //! The default value for `Registry` is @ref default_registry, but it can be //! overridden by defining the preprocessor symbol -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}}, *before* including +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, *before* including //! ``. Setting the symbol afterwards has no effect. //! //! Specializations of `method` have a single instance: the static member `fn`, diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 96317411..b603ece3 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -18,7 +18,7 @@ namespace boost::openmethod { //! Default registry. //! //! `default_registry` is a predefined @ref registry, and the default value of -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}}. +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. //! It contains the following policies: //! @li @ref policies::std_rtti: Use standard RTTI. //! @li @ref policies::fast_perfect_hash: Use a fast perfect hash function to @@ -27,8 +27,7 @@ namespace boost::openmethod { //! @li @ref policies::default_error_handler: Write short diagnostic messages. //! @li @ref policies::stderr_output: Write messages to @c stderr. //! -//! If -//! {{BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS}} +//! If @ref BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS //! is defined, `default_registry` also includes the @ref runtime_checks policy. //! //! @note Use `BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS` with caution, as @@ -38,8 +37,8 @@ namespace boost::openmethod { //! //! For a program and its shared libraries to contribute to the same //! `default_registry`, its state must be shared across the modules, with -//! {{BOOST_OPENMETHOD_IMPORT_REGISTRY}}, {{BOOST_OPENMETHOD_EXPORT_REGISTRY}} -//! and {{BOOST_OPENMETHOD_INSTANTIATE_REGISTRY}}: +//! @ref BOOST_OPENMETHOD_IMPORT_REGISTRY, @ref BOOST_OPENMETHOD_EXPORT_REGISTRY +//! and @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY: //! @code //! // header, every translation unit of a client module: //! BOOST_OPENMETHOD_IMPORT_REGISTRY(boost::openmethod::default_registry); @@ -80,4 +79,20 @@ struct indirect_registry : default_registry::with {}; } // namespace boost::openmethod +// The library only tests BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS, it never +// defines it - that is up to the program. MrDocs extracts macros from +// `#define` directives, so give it one to extract. It is placed after +// `default_registry`, whose definition tests the macro, so that documenting it +// cannot change what is documented. +#ifdef __MRDOCS__ +#ifndef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS +//! Enable runtime checks in @ref boost::openmethod::default_registry. +//! +//! May be defined by a program before including +//! `` to enable runtime checks. See +//! @ref boost::openmethod::default_registry for details. +#define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS +#endif +#endif + #endif diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 2890050d..0d239d77 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -1838,7 +1838,7 @@ void registry::compiler::print( //! //! Initialize the @ref registry passed as an explicit function template //! argument, or @ref default_registry if the registry is not specified. The -//! default can be changed by defining {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}}. +//! default can be changed by defining @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. //! Option objects can be passed to change the behavior of the function. //! Currently two options exist: //! @li @ref trace Enable tracing of the initialization process. diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index 0ddf5340..1245a312 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -68,7 +68,7 @@ class inplace_vptr_base_tag {}; //! //! `inplace_vptr_base` registers the class in `Registry`. It is not necessary //! to register the class with @ref use_class or -//! {{BOOST_OPENMETHOD_REGISTER}} +//! @ref BOOST_OPENMETHOD_REGISTER. //! //! The v-table pointer is obtained directly from the `Registry`\'s @ref //! static_vptr variable. No hashing is involved. If all the classes in @@ -81,7 +81,7 @@ class inplace_vptr_base_tag {}; //! to @ref initialize. //! //! The default value of `Registry` can be changed by defining -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}} +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. //! //! @tparam Class The class in which to embed the v-table pointer. //! @tparam Registry The @ref registry in which `Class` and its derived classes @@ -167,7 +167,7 @@ class inplace_vptr_base : protected detail::inplace_vptr_base_tag { //! //! `inplace_vptr_derived` registers the class and its bases in `Registry`. It //! is not necessary to register them with @ref use_class or -//! {{BOOST_OPENMETHOD_REGISTER}} +//! @ref BOOST_OPENMETHOD_REGISTER. //! //! The v-table pointer is obtained directly from the `Registry`\'s @ref //! static_vptr variable. No hashing is involved. If all the classes in diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 7417740c..926f419a 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -44,84 +44,259 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_GENSYM BOOST_PP_CAT(openmethod_gensym_, __COUNTER__) +//! Create a registrar object. +//! +//! Creates a registrar for a type, i.e. a static object of that type with a +//! unique generated name. At static initialization time, the object adds +//! itself to a list: methods and class registrations add themselves to a +//! @ref boost::openmethod::registry, and overriders add themselves to a +//! method's overrider list. +//! +//! @param ... The registrar's type. It is variadic so that it may contain +//! unparenthesized commas, as in `std::pair`. #define BOOST_OPENMETHOD_REGISTER(...) \ static __VA_ARGS__ BOOST_OPENMETHOD_GENSYM -#define BOOST_OPENMETHOD_ID(NAME) NAME##_boost_openmethod - -#define BOOST_OPENMETHOD_OVERRIDERS(NAME) \ - BOOST_PP_CAT(BOOST_OPENMETHOD_ID(NAME), _overriders) - -#define BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS> - -#define BOOST_OPENMETHOD_GUIDE(NAME) \ - BOOST_PP_CAT(BOOST_OPENMETHOD_ID(NAME), _guide) - -#define BOOST_OPENMETHOD_TYPE(NAME, ARGS, ...) \ +//! Generate a method id. +//! +//! Generates a long, obfuscated name from a short name. All the other names +//! generated by macros are based on this name. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +#define BOOST_OPENMETHOD_ID(ID) ID##_boost_openmethod + +//! Return the class template containing the overriders for all the methods +//! with a given name. +//! +//! `BOOST_OPENMETHOD_OVERRIDERS` expands to the name of the class template that +//! contains the overriders for all the methods with a given name. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +#define BOOST_OPENMETHOD_OVERRIDERS(ID) \ + BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _overriders) + +//! Return the class template specialization containing an overrider. +//! +//! Expands to the specialization of the class template that contains the +//! overrider with the given name, parameter list and return type. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +#define BOOST_OPENMETHOD_OVERRIDER(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_OVERRIDERS(ID)<__VA_ARGS__ PARAMETERS> + +#define BOOST_OPENMETHOD_GUIDE(ID) BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _guide) + +//! Expand to a core `method` specialization. +//! +//! Expands to the core @ref boost::openmethod::method specialization created by +//! @ref BOOST_OPENMETHOD called with the same arguments. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! @param PARAMETERS The method's parameter list, in parentheses. +//! @param ... The method's return type, optionally followed by the registry. +#define BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, ...) \ ::boost::openmethod::method< \ - BOOST_OPENMETHOD_ID(NAME), \ - ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type ARGS, \ + BOOST_OPENMETHOD_ID(ID), \ + ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type \ + PARAMETERS, \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::registry> -#define BOOST_OPENMETHOD(NAME, ARGS, ...) \ - struct BOOST_OPENMETHOD_ID(NAME); \ +//! Declare a method. +//! +//! Declares a method, called `ID`, with the given parameters and return type, +//! and adds it to a registry. +//! +//! `PARAMETERS` is a comma-separated list of types, possibly followed by +//! parameter names, just like in a function declaration. Parameters with a type +//! in the form `virtual_ptr` or `virtual_` are called virtual parameters. +//! The dynamic type of the arguments passed in virtual parameters determines +//! which overrider to call, following the same rules as overloaded function +//! resolution: +//! +//! @li Form the set of all applicable overriders. An overrider is applicable +//! if it can be called with the arguments passed to the method. +//! +//! @li If the set is empty, call the error handler (if present in the +//! registry), then terminate the program with `abort`. +//! +//! @li Remove the overriders that are dominated by other overriders in the +//! set. Overrider A dominates overrider B if any of its virtual formal +//! parameters is more specialized than B's, and if none of B's virtual +//! parameters is more specialized than A's. +//! +//! @li If the resulting set contains exactly one overrider, call it. +//! +//! If a single most specialized overrider does not exist, the program is +//! terminated via `abort`. If the registry contains an `error_handler` policy, +//! its `error` function is called with an object that describes the error, +//! prior to calling `abort`. `error` may prevent termination by throwing an +//! exception. +//! +//! For each virtual argument `arg`, the dispatch mechanism calls +//! `virtual_traits::peek(arg)` and deduces the v-table pointer from the +//! `result`, using the first of the following methods that applies: +//! +//! @li If `result` is a `virtual_ptr`, get the pointer to the v-table from it. +//! +//! @li If `boost_openmethod_vptr` can be called with `result` and a +//! `Registry*`, and it returns a `vptr_type`, call it. +//! +//! @li Call `Registry::vptr::dynamic_vptr(result)`. +//! +//! The macro creates an ordinary inline function in the current scope, with the +//! `virtual_` decorators removed from the parameter types. `virtual_ptr` +//! parameters are preserved. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @note The default registry is the value of +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY at the point +//! `` is included. Changing the value of this symbol +//! has no effect after that point. +//! +//! @par Implementation Notes +//! +//! The macro creates several additional constructs: +//! +//! @li A `struct` forward declaration that acts as the method's identifier: +//! @code +//! struct BOOST_OPENMETHOD_ID(ID); +//! @endcode +//! +//! @li A class template declaration that acts as a container for the method's +//! overriders in the current scope: +//! @code +//! template struct BOOST_OPENMETHOD_OVERRIDERS(ID); +//! @endcode +//! +//! @li A guide function used to match overriders with the method: +//! @code +//! auto BOOST_OPENMETHOD_ID(ID)_guide(...) +//! -> ::boost::openmethod::method< +//! BOOST_OPENMETHOD_ID(ID)(PARAMETERS...), RETURN_TYPE [, REGISTRY]>; +//! @endcode +//! +//! @li A registrar (see @ref BOOST_OPENMETHOD_REGISTER) that adds the method to +//! the registry. +//! +//! @param ID The method's name. +//! @param PARAMETERS The method's parameter list, in parentheses. +//! @param ... The method's return type, optionally followed by the registry. +#define BOOST_OPENMETHOD(ID, PARAMETERS, ...) \ + struct BOOST_OPENMETHOD_ID(ID); \ template \ typename ::boost::openmethod::detail::enable_forwarder< \ - void, BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ - typename BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ + void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ + typename BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ ForwarderParameters...>::type \ - BOOST_OPENMETHOD_GUIDE(NAME)(ForwarderParameters && ... args); \ + BOOST_OPENMETHOD_GUIDE(ID)(ForwarderParameters && ... args); \ template \ - inline auto NAME(ForwarderParameters&&... args) -> \ + inline auto ID(ForwarderParameters&&... args) -> \ typename ::boost::openmethod::detail::enable_forwarder< \ - void, BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ + void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type, \ ForwarderParameters...>::type { \ - return BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__)::fn( \ + return BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__)::fn( \ std::forward(args)...); \ } \ template \ - struct BOOST_OPENMETHOD_OVERRIDERS(NAME) + struct BOOST_OPENMETHOD_OVERRIDERS(ID) -#define BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(NAME, ARGS) \ +#define BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(ID, PARAMETERS) \ template \ struct boost_openmethod_detail_locate_method_aux { \ static_assert( \ ::boost::openmethod::detail::method_not_found, \ - "BOOST_OPENMETHOD_OVERRIDE: cannot find '" #NAME \ + "BOOST_OPENMETHOD_OVERRIDE: cannot find '" #ID \ "' method that accepts the same arguments as the overrider"); \ }; \ template \ struct boost_openmethod_detail_locate_method_aux< \ void(A...), \ - std::void_t()...))>> { \ using type = \ - decltype(BOOST_OPENMETHOD_GUIDE(NAME)(std::declval()...)); \ + decltype(BOOST_OPENMETHOD_GUIDE(ID)(std::declval()...)); \ } -#define BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, ...) \ +//! Declare a method overrider. +//! +//! Declares an overrider for a method, but does not start its definition. This +//! macro can be used in header files. +//! +//! `ID` is the identifier of the method to which the overrider is added. +//! +//! `PARAMETERS` is a comma-separated list of types, possibly followed by +//! parameter names, just like in a function declaration. +//! +//! The macro tries to locate a method that can be called with the same argument +//! list as the overrider, possibly via argument dependent lookup. +//! +//! Each `virtual_ptr` in the method's parameter list must have a +//! corresponding `virtual_ptr` parameter in the same position in the +//! overrider's parameter list, such that `U` is the same as `T`, or has `T` as +//! an accessible unambiguous base. +//! +//! Each `virtual_` in the method's parameter list must have a corresponding +//! `U` parameter in the same position in the overrider's parameter list, such +//! that `U` is the same as `T`, or has `T` as an accessible unambiguous base. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @par Implementation Notes +//! +//! The macro creates additional entities in the current scope. +//! +//! @li A class template declaration that acts as a container for the method's +//! overriders in the current scope: +//! @code +//! template struct BOOST_OPENMETHOD_OVERRIDERS(ID); +//! @endcode +//! +//! @li A specialization of the container for the overrider: +//! @code +//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { +//! static auto fn(PARAMETERS...) -> RETURN_TYPE; +//! static auto has_next() -> bool; +//! template +//! static auto next(typename... Args) -> RETURN_TYPE; +//! }; +//! @endcode +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +#define BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, ...) \ template \ - struct BOOST_OPENMETHOD_OVERRIDERS(NAME); \ + struct BOOST_OPENMETHOD_OVERRIDERS(ID); \ template<> \ - struct BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS> { \ - BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(NAME, ARGS); \ - static auto fn ARGS->__VA_ARGS__; \ + struct BOOST_OPENMETHOD_OVERRIDERS(ID)<__VA_ARGS__ PARAMETERS> { \ + BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(ID, PARAMETERS); \ + static auto fn PARAMETERS->__VA_ARGS__; \ static auto has_next() -> bool; \ template \ static auto next(Args&&... args) -> decltype(auto); \ }; \ inline auto BOOST_OPENMETHOD_OVERRIDERS( \ - NAME)<__VA_ARGS__ ARGS>::has_next() -> bool { \ + ID)<__VA_ARGS__ PARAMETERS>::has_next() -> bool { \ return boost_openmethod_detail_locate_method_aux< \ - void ARGS>::type::has_next(); \ + void PARAMETERS>::type::has_next(); \ } \ template \ - inline auto BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS>::next( \ + inline auto BOOST_OPENMETHOD_OVERRIDERS(ID)<__VA_ARGS__ PARAMETERS>::next( \ Args&&... args) -> decltype(auto) { \ return boost_openmethod_detail_locate_method_aux< \ - void ARGS>::type::next(std::forward(args)...); \ + void PARAMETERS>::type::next(std::forward(args)...); \ } // REGISTRAR selects which of method<...>::override (plain) or @@ -137,25 +312,111 @@ inline constexpr bool method_not_found = false; // overrider's return type, which may contain an unprotected top-level comma, // e.g. an un-aliased std::pair) as one argument. #define BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER_AUX( \ - NAME, ARGS, REGISTRAR, ...) \ + ID, PARAMETERS, REGISTRAR, ...) \ BOOST_OPENMETHOD_REGISTER( \ - BOOST_OPENMETHOD_OVERRIDERS(NAME) < __VA_ARGS__ ARGS > \ - ::boost_openmethod_detail_locate_method_aux::type:: \ + BOOST_OPENMETHOD_OVERRIDERS(ID) < __VA_ARGS__ PARAMETERS > \ + ::boost_openmethod_detail_locate_method_aux::type:: \ REGISTRAR< \ - BOOST_OPENMETHOD_OVERRIDERS(NAME) < __VA_ARGS__ ARGS>::fn >); + BOOST_OPENMETHOD_OVERRIDERS(ID) < \ + __VA_ARGS__ PARAMETERS>::fn >); -#define BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(NAME, ARGS, ...) \ +#define BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(ID, PARAMETERS, ...) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER_AUX( \ - NAME, ARGS, override, __VA_ARGS__) - -#define BOOST_OPENMETHOD_DEFINE_OVERRIDER(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ - auto BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, __VA_ARGS__)::fn ARGS \ + ID, PARAMETERS, override, __VA_ARGS__) + +//! Define the body of a method overrider. +//! +//! Defines the body of an overrider declared with +//! @ref BOOST_OPENMETHOD_DECLARE_OVERRIDER. It should be called in an +//! implementation file, and followed by a function body. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +#define BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ + auto BOOST_OPENMETHOD_OVERRIDER( \ + ID, PARAMETERS, __VA_ARGS__)::fn PARAMETERS \ -> boost::mp11::mp_back> -#define BOOST_OPENMETHOD_OVERRIDE(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ - BOOST_OPENMETHOD_DEFINE_OVERRIDER(NAME, ARGS, __VA_ARGS__) +//! Add an overrider to a method. +//! +//! `BOOST_OPENMETHOD_OVERRIDE` adds an overrider to a method. It is followed by +//! the overrider's body. +//! +//! `ID` is the identifier of the method to which the overrider is added. +//! +//! `PARAMETERS` is a comma-separated list of types, possibly followed by +//! parameter names, just like in a function declaration. +//! +//! The macro tries to locate a method that can be called with the same argument +//! list as the overrider, possibly via argument dependent lookup. +//! +//! Each `virtual_ptr` in the method's parameter list must have a +//! corresponding `virtual_ptr` parameter in the same position in the +//! overrider's parameter list, such that `U` is the same as `T`, or has `T` as +//! an accessible unambiguous base. +//! +//! Each `virtual_` in the method's parameter list must have a corresponding +//! `U` parameter in the same position in the overrider's parameter list, such +//! that `U` is the same as `T`, or has `T` as an accessible unambiguous base. +//! +//! The following names are available inside the overrider's body: +//! +//! @li `fn`: a pointer to a function, the overrider itself. Can be used for +//! recursion. +//! +//! @li `next`: a function with the same signature as the method (minus the +//! `virtual_<>` decorators). It forwards to the next most specialized +//! overrider, if it exists and it is unique. If the next overrider does not +//! exist, or is ambiguous, calling `next` reports a +//! @ref boost::openmethod::no_overrider or a +//! @ref boost::openmethod::ambiguous_call and terminates the program. +//! +//! @li `has_next()`: returns `true` if the next most specialized overrider +//! exists. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @par Implementation Notes +//! +//! The macro creates additional entities in the current scope. +//! +//! @li A class template declaration that acts as a container for the method's +//! overriders in the current scope: +//! @code +//! template struct BOOST_OPENMETHOD_OVERRIDERS(ID); +//! @endcode +//! +//! @li A specialization of the container for the overrider: +//! @code +//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { +//! static auto fn(PARAMETERS...) -> RETURN_TYPE; +//! static auto has_next() -> bool; +//! template +//! static auto next(typename... Args) -> RETURN_TYPE; +//! }; +//! @endcode +//! +//! @li A registrar (see @ref BOOST_OPENMETHOD_REGISTER) adding the overrider to +//! the method. +//! +//! @li Finally, the macro starts the definition of the overrider function: +//! @code +//! auto BOOST_OPENMETHOD_OVERRIDERS(ID)::fn( +//! PARAMETERS...) -> RETURN_TYPE +//! @endcode +//! +//! The `{}` block following the call to the macro is the body of the function. +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +#define BOOST_OPENMETHOD_OVERRIDE(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ + BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) // Unlike BOOST_OPENMETHOD_OVERRIDE, registers via method<...>::inline_override // instead of method<...>::override, marking the overrider_info as @@ -165,33 +426,47 @@ inline constexpr bool method_not_found = false; // identical definition appear in more than one translation unit/module in // the first place, which is why plain BOOST_OPENMETHOD_OVERRIDE never sets // this. -#define BOOST_OPENMETHOD_INLINE_OVERRIDE(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ + +//! Add an overrider to a method as an inline function. +//! +//! `BOOST_OPENMETHOD_INLINE_OVERRIDE` performs the same function as +//! @ref BOOST_OPENMETHOD_OVERRIDE, except that the overrider is marked +//! `inline`. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +#define BOOST_OPENMETHOD_INLINE_OVERRIDE(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER_AUX( \ - NAME, ARGS, inline_override, __VA_ARGS__) \ - inline auto BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, __VA_ARGS__)::fn ARGS \ + ID, PARAMETERS, inline_override, __VA_ARGS__) \ + inline auto BOOST_OPENMETHOD_OVERRIDER( \ + ID, PARAMETERS, __VA_ARGS__)::fn PARAMETERS \ -> boost::mp11::mp_back> +//! Register classes. +//! +//! Registers classes in a registry. +//! +//! This macro is a wrapper around @ref boost::openmethod::use_classes; see its +//! documentation for more details. +//! +//! @note The default registry is the value of +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when `` is +//! included. Subsequently changing it has no retroactive effect. +//! +//! @param ... The classes to register, optionally followed by the registry. #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>) -// Share a registry's state across module boundaries. All of a registry's -// mutable state lives in one variable, registry_state::st -// (see registry_state in preamble.hpp); these macros emit the explicit -// instantiations that make it a single shared symbol: -// -// BOOST_OPENMETHOD_IMPORT_REGISTRY - header; every TU of a CLIENT module -// BOOST_OPENMETHOD_EXPORT_REGISTRY - header; every TU of the OWNING module -// BOOST_OPENMETHOD_INSTANTIATE_REGISTRY - exactly one .cpp of the owning module -// -// The owning module uses both: EXPORT in the header its translation units -// share, and INSTANTIATE in exactly one of them. Use them at namespace scope, -// after the registry's definition, with a trailing `;`. REGISTRY may be any -// registry, predefined or user-defined; everything emitted is fully qualified, -// so there is no need to be inside, or to open, namespace boost::openmethod. -// -// The macros exist because no single spelling is portable - the two ABIs want -// opposite things: +// The three macros below share a registry's state - the single variable +// registry_state::st, see registry_state in preamble.hpp - +// across module boundaries, by emitting the explicit instantiations that make +// it one shared symbol. See their documentation comments for how they are +// meant to be used. They exist because no single spelling is portable: the two +// ABIs want opposite things. // // * declspec platforms (Windows, Cygwin, MinGW): MSVC rejects `extern` together // with __declspec(dllexport) on an explicit instantiation outright ("warning @@ -210,27 +485,110 @@ inline constexpr bool method_not_found = false; // instantiates the state implicitly, and under -fvisibility=hidden that copy // is module-local; since ELF merges COMDATs at the most restrictive // visibility, the whole symbol then becomes local and clients fail to link. + +//! Import a registry's state from the module that owns it. +//! +//! All of a registry's mutable state lives in a single variable (see +//! @ref boost::openmethod::registry_state). Sharing a registry across modules +//! means sharing that one symbol, which takes three macros: the owning module +//! uses @ref BOOST_OPENMETHOD_EXPORT_REGISTRY in the header its translation +//! units share, and @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY in exactly one +//! of them; every client module uses `BOOST_OPENMETHOD_IMPORT_REGISTRY`. +//! +//! They exist to hide a platform incompatibility: on Windows, Cygwin and +//! MinGW, `__declspec(dllexport)` and `extern` are incompatible on an explicit +//! instantiation, while on ELF and Mach-O the visibility attribute must be on +//! the declaration and must not be repeated on the definition. See the Shared +//! Libraries section of the documentation for the full discussion, including +//! the required link setup. +//! +//! Use at namespace scope, after the registry's definition, in every +//! translation unit of every module that uses the registry without owning it. +//! Being a declaration it may be repeated, so it belongs in the header those +//! modules share. Everything it emits is fully qualified, so there is no need +//! to be inside, or to open, namespace `boost::openmethod`. +//! +//! It emits an `extern template` declaration decorated with +//! `BOOST_SYMBOL_IMPORT` (`__declspec(dllimport)` on Windows, nothing on ELF). +//! The declaration suppresses the client's own instantiation, so it references +//! the owner's symbol instead of creating a private copy. +//! +//! The client module must be linked so the reference resolves: on Windows and +//! macOS by linking against the owning module; on ELF a dynamically loaded +//! library may also leave it for the dynamic linker to resolve at load time. +//! +//! @param REGISTRY The registry to import. May be any registry, predefined or +//! user-defined. #define BOOST_OPENMETHOD_IMPORT_REGISTRY(REGISTRY) \ extern template struct BOOST_SYMBOL_IMPORT ::boost::openmethod:: \ registry_state #ifdef BOOST_HAS_DECLSPEC -#define BOOST_OPENMETHOD_EXPORT_REGISTRY(REGISTRY) static_assert(true) +#define BOOST_OPENMETHOD_DETAIL_EXPORT_REGISTRY(REGISTRY) static_assert(true) -#define BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(REGISTRY) \ +#define BOOST_OPENMETHOD_DETAIL_INSTANTIATE_REGISTRY(REGISTRY) \ template struct BOOST_SYMBOL_EXPORT ::boost::openmethod::registry_state< \ REGISTRY::registry_type> #else -#define BOOST_OPENMETHOD_EXPORT_REGISTRY(REGISTRY) \ +#define BOOST_OPENMETHOD_DETAIL_EXPORT_REGISTRY(REGISTRY) \ extern template struct BOOST_SYMBOL_EXPORT ::boost::openmethod:: \ registry_state -#define BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(REGISTRY) \ +#define BOOST_OPENMETHOD_DETAIL_INSTANTIATE_REGISTRY(REGISTRY) \ template struct ::boost::openmethod::registry_state #endif +//! Declare a registry's state exported, in every translation unit of the +//! owning module. +//! +//! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing +//! macros fit together. +//! +//! Use at namespace scope, after the registry's definition, in *every* +//! translation unit of the module that owns the registry. Being a declaration +//! it may be repeated, so it belongs in the header those translation units +//! share. +//! +//! On ELF it emits an exported explicit instantiation *declaration*, which +//! both suppresses implicit instantiation and pins the symbol to default +//! visibility. On declspec platforms it expands to nothing, because there the +//! export belongs on the instantiation instead. +//! +//! @warning On ELF this macro is not decoration. A translation unit of the +//! owning module that uses neither it nor +//! @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY instantiates the state +//! implicitly, and under `-fvisibility=hidden` that copy is module-local. Since +//! ELF merges COMDATs at the *most restrictive* visibility, the merged symbol +//! becomes local: the module builds, exports nothing, and clients fail to link +//! with an undefined reference to `registry_state<...>::st`. +//! +//! @param REGISTRY The registry to export. May be any registry, predefined or +//! user-defined. +#define BOOST_OPENMETHOD_EXPORT_REGISTRY(REGISTRY) \ + BOOST_OPENMETHOD_DETAIL_EXPORT_REGISTRY(REGISTRY) + +//! Instantiate a registry's state, in exactly one translation unit of the +//! owning module. +//! +//! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing +//! macros fit together. +//! +//! Use at namespace scope, after the registry's definition, in *exactly one* +//! translation unit of the module that owns the registry. It belongs in a +//! `.cpp` file, never in a header. +//! +//! It emits the explicit instantiation *definition* of the registry state, of +//! which a program may contain only one. On declspec platforms the definition +//! carries the `dllexport`; on ELF and Mach-O it carries no attribute, that +//! having been supplied by @ref BOOST_OPENMETHOD_EXPORT_REGISTRY in the header. +//! +//! @param REGISTRY The registry to instantiate. May be any registry, predefined +//! or user-defined. +#define BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(REGISTRY) \ + BOOST_OPENMETHOD_DETAIL_INSTANTIATE_REGISTRY(REGISTRY) + #endif diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 5dddbe50..27c756fe 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -96,7 +96,7 @@ struct openmethod_error {}; //! //! This error is raised if the definition of @ref default_registry is //! inconsistent across translation units, due to misuse of -//! {{BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS}}. +//! @ref BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS. struct odr_violation : openmethod_error { //! Write a description of the error to a stream. //! @tparam Registry The registry containing this policy. @@ -1053,8 +1053,8 @@ struct initialize_aux; //! whole and import via `extern template`. //! //! To share the state across modules, use -//! {{BOOST_OPENMETHOD_IMPORT_REGISTRY}}, {{BOOST_OPENMETHOD_EXPORT_REGISTRY}} -//! and {{BOOST_OPENMETHOD_INSTANTIATE_REGISTRY}}. They hide a platform +//! @ref BOOST_OPENMETHOD_IMPORT_REGISTRY, @ref BOOST_OPENMETHOD_EXPORT_REGISTRY +//! and @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY. They hide a platform //! incompatibility: the export goes on the declaration on ELF and Mach-O, but //! on the instantiation on declspec platforms, where `extern` and //! `__declspec(dllexport)` cannot be combined. From 607b462e115432d8b2ec3c4da8ef82dd07f637cd Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Wed, 29 Jul 2026 20:46:03 -0400 Subject: [PATCH 05/85] doc: link the registry macro pages back to the guide MrDocs escapes prose punctuation but emits markdown-link targets verbatim, so a literal Antora resource ID survives a doc comment: the link [Shared Libraries](xref:ROOT:shared_libraries.adoc) comes out as `xref:ROOT:shared_libraries.adoc[Shared Libraries]`, which Antora resolves from the reference module to the component's ROOT module. Use it on all three registry-sharing macro pages, in place of the plain-text "see the Shared Libraries section of the documentation" that assumed the xref could not survive. The link must be on one line: MrDocs parses it after the comment has been split into lines, and a link broken across two `//!` lines falls through as escaped literal text. That is what happened to the two [CRTP mixin] links in inplace_vptr.hpp, which render today as [CRTP] mixin](https://en.wikipedia...) Shorten their text to [CRTP] so the link fits on one line inside the column limit, and move `mixin` into the surrounding prose. Co-Authored-By: Claude Opus 5 (1M context) --- include/boost/openmethod/inplace_vptr.hpp | 20 ++++++++++---------- include/boost/openmethod/macros.hpp | 14 +++++++++----- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index 1245a312..ebb52a87 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -60,11 +60,11 @@ class inplace_vptr_base_tag {}; //! Embed a v-table pointer in a class. //! -//! `inplace_vptr_base` is a [CRTP -//! mixin](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) -//! that embeds a v-table pointer at the root of a class hierarchy. It also -//! declares a @ref boost_openmethod_vptr free function that returns the v-table -//! pointer stored in the object. +//! `inplace_vptr_base` is a +//! [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +//! mixin that embeds a v-table pointer at the root of a class hierarchy. It +//! also declares a @ref boost_openmethod_vptr free function that returns the +//! v-table pointer stored in the object. //! //! `inplace_vptr_base` registers the class in `Registry`. It is not necessary //! to register the class with @ref use_class or @@ -159,11 +159,11 @@ class inplace_vptr_base : protected detail::inplace_vptr_base_tag { #ifdef __MRDOCS__ //! Adjust the v-table pointer embedded in a class. //! -//! `inplace_vptr_derived` is a [CRTP -//! mixin](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) -//! that adjusts the v-table pointer in a @ref inplace_vptr_base. It can be used -//! only with classes that have @ref inplace_vptr_base as a direct or indirect -//! base class. +//! `inplace_vptr_derived` is a +//! [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +//! mixin that adjusts the v-table pointer in a @ref inplace_vptr_base. It can +//! be used only with classes that have @ref inplace_vptr_base as a direct or +//! indirect base class. //! //! `inplace_vptr_derived` registers the class and its bases in `Registry`. It //! is not necessary to register them with @ref use_class or diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 926f419a..923a77c6 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -498,9 +498,9 @@ inline constexpr bool method_not_found = false; //! They exist to hide a platform incompatibility: on Windows, Cygwin and //! MinGW, `__declspec(dllexport)` and `extern` are incompatible on an explicit //! instantiation, while on ELF and Mach-O the visibility attribute must be on -//! the declaration and must not be repeated on the definition. See the Shared -//! Libraries section of the documentation for the full discussion, including -//! the required link setup. +//! the declaration and must not be repeated on the definition. See +//! [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full +//! discussion, including the required link setup. //! //! Use at namespace scope, after the registry's definition, in every //! translation unit of every module that uses the registry without owning it. @@ -546,7 +546,9 @@ inline constexpr bool method_not_found = false; //! owning module. //! //! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing -//! macros fit together. +//! macros fit together, and +//! [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full discussion, +//! including the required link setup. //! //! Use at namespace scope, after the registry's definition, in *every* //! translation unit of the module that owns the registry. Being a declaration @@ -575,7 +577,9 @@ inline constexpr bool method_not_found = false; //! owning module. //! //! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing -//! macros fit together. +//! macros fit together, and +//! [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full discussion, +//! including the required link setup. //! //! Use at namespace scope, after the registry's definition, in *exactly one* //! translation unit of the module that owns the registry. It belongs in a From b2dcdb807fc5b6fa93accd742d290b6cfab058c5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Wed, 29 Jul 2026 23:11:04 -0400 Subject: [PATCH 06/85] doc: add See Also guide links to the macro reference pages Every guide page links into the reference; nothing linked back out. Give each macro page a See Also section pointing at the guide that covers it, using the markdown-link-to-Antora-xref form established in the previous commit. `@see` is the right vehicle: MrDocs renders `symbol.doc.sees` under a "See Also" heading, each entry through the same inline path as description text, so a markdown link works there. Link text matches the nav labels, and a symbol is linked to a guide page only where that page actually discusses it - so BOOST_OPENMETHOD_OVERRIDERS points at Header and Implementation Files (overrider containers) while BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS points at Registries and Policies. The three registry-sharing macros move their inline guide link into See Also, so all the macro pages have the same shape. Only macro pages get these links. MrDocs writes `:relfileprefix: ../../` into its nested reference pages - by design, its template says so - and Asciidoctor prepends that to the xref target Antora resolves, so `xref:ROOT:basics.adoc` arrives as `../../ROOT:basics.adoc` and does not resolve. Macro pages sit at the reference module root, get no prefix, and work. Clearing the attribute fixes the nested pages but breaks ~1250 breadcrumb links, because the `boost::openmethod::` xrefs in the document title bypass Antora's resolver and genuinely need it. To be reported upstream: the title partial should inline the prefix itself rather than rely on a document attribute that corrupts module-qualified xrefs. Also turn three dead `@see` entries into real references: `@see indirect_vptr.` rendered as escaped plain text, and two `@see The main template for documentation.` had nothing to click. Co-Authored-By: Claude Opus 5 (1M context) --- include/boost/openmethod/core.hpp | 2 + include/boost/openmethod/default_registry.hpp | 4 +- include/boost/openmethod/inplace_vptr.hpp | 4 +- include/boost/openmethod/macros.hpp | 47 +++++++++++++++---- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 8864f915..3dda139b 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -51,6 +51,8 @@ //! //! @note Use this feature with caution, as it will cause ODR violations if //! different translation units define different default registries. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) #define BOOST_OPENMETHOD_DEFAULT_REGISTRY ::boost::openmethod::default_registry #endif diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index b603ece3..c8915231 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -74,7 +74,7 @@ static odr_check default_registry_odr_check_instance; //! Share it across shared libraries exactly as for @ref default_registry, //! naming `indirect_registry` in the macros. //! -//! @see indirect_vptr. +//! @see @ref policies::indirect_vptr struct indirect_registry : default_registry::with {}; } // namespace boost::openmethod @@ -91,6 +91,8 @@ struct indirect_registry : default_registry::with {}; //! May be defined by a program before including //! `` to enable runtime checks. See //! @ref boost::openmethod::default_registry for details. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) #define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS #endif #endif diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index ebb52a87..e2363e58 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -200,7 +200,7 @@ class inplace_vptr_derived; //! Specialization for a single base class. //! //! -//! @see The main template for documentation. +//! @see @ref inplace_vptr_derived for documentation. template class inplace_vptr_derived { static_assert( @@ -233,7 +233,7 @@ class inplace_vptr_derived { //! Specialization for multiple base classes. //! -//! @see The main template for documentation. +//! @see @ref inplace_vptr_derived for documentation. template class inplace_vptr_derived { static_assert( diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 923a77c6..81c9e8b1 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -54,6 +54,8 @@ inline constexpr bool method_not_found = false; //! //! @param ... The registrar's type. It is variadic so that it may contain //! unparenthesized commas, as in `std::pair`. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) #define BOOST_OPENMETHOD_REGISTER(...) \ static __VA_ARGS__ BOOST_OPENMETHOD_GENSYM @@ -65,6 +67,8 @@ inline constexpr bool method_not_found = false; //! @note `ID` must be an *identifier*. Qualified names are not allowed. //! //! @param ID The method's name. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) #define BOOST_OPENMETHOD_ID(ID) ID##_boost_openmethod //! Return the class template containing the overriders for all the methods @@ -76,6 +80,8 @@ inline constexpr bool method_not_found = false; //! @note `ID` must be an *identifier*. Qualified names are not allowed. //! //! @param ID The method's name. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) #define BOOST_OPENMETHOD_OVERRIDERS(ID) \ BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _overriders) @@ -89,6 +95,8 @@ inline constexpr bool method_not_found = false; //! @param ID The method's name. //! @param PARAMETERS The overrider's parameter list, in parentheses. //! @param ... The overrider's return type. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) #define BOOST_OPENMETHOD_OVERRIDER(ID, PARAMETERS, ...) \ BOOST_OPENMETHOD_OVERRIDERS(ID)<__VA_ARGS__ PARAMETERS> @@ -104,6 +112,8 @@ inline constexpr bool method_not_found = false; //! @param ID The method's name. //! @param PARAMETERS The method's parameter list, in parentheses. //! @param ... The method's return type, optionally followed by the registry. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) #define BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, ...) \ ::boost::openmethod::method< \ BOOST_OPENMETHOD_ID(ID), \ @@ -192,6 +202,9 @@ inline constexpr bool method_not_found = false; //! @param ID The method's name. //! @param PARAMETERS The method's parameter list, in parentheses. //! @param ... The method's return type, optionally followed by the registry. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) #define BOOST_OPENMETHOD(ID, PARAMETERS, ...) \ struct BOOST_OPENMETHOD_ID(ID); \ template \ @@ -276,6 +289,8 @@ inline constexpr bool method_not_found = false; //! @param ID The method's name. //! @param PARAMETERS The overrider's parameter list, in parentheses. //! @param ... The overrider's return type. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) #define BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, ...) \ template \ struct BOOST_OPENMETHOD_OVERRIDERS(ID); \ @@ -335,6 +350,8 @@ inline constexpr bool method_not_found = false; //! @param ID The method's name. //! @param PARAMETERS The overrider's parameter list, in parentheses. //! @param ... The overrider's return type. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) #define BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, PARAMETERS, ...) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ auto BOOST_OPENMETHOD_OVERRIDER( \ @@ -414,6 +431,11 @@ inline constexpr bool method_not_found = false; //! @param ID The method's name. //! @param PARAMETERS The overrider's parameter list, in parentheses. //! @param ... The overrider's return type. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) +//! @see [Namespaces](xref:ROOT:namespaces.adoc) +//! @see [Friends](xref:ROOT:friends.adoc) #define BOOST_OPENMETHOD_OVERRIDE(ID, PARAMETERS, ...) \ BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) @@ -438,6 +460,8 @@ inline constexpr bool method_not_found = false; //! @param ID The method's name. //! @param PARAMETERS The overrider's parameter list, in parentheses. //! @param ... The overrider's return type. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) #define BOOST_OPENMETHOD_INLINE_OVERRIDE(ID, PARAMETERS, ...) \ BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER_AUX( \ @@ -458,6 +482,8 @@ inline constexpr bool method_not_found = false; //! included. Subsequently changing it has no retroactive effect. //! //! @param ... The classes to register, optionally followed by the registry. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>) @@ -498,9 +524,7 @@ inline constexpr bool method_not_found = false; //! They exist to hide a platform incompatibility: on Windows, Cygwin and //! MinGW, `__declspec(dllexport)` and `extern` are incompatible on an explicit //! instantiation, while on ELF and Mach-O the visibility attribute must be on -//! the declaration and must not be repeated on the definition. See -//! [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full -//! discussion, including the required link setup. +//! the declaration and must not be repeated on the definition. //! //! Use at namespace scope, after the registry's definition, in every //! translation unit of every module that uses the registry without owning it. @@ -519,6 +543,9 @@ inline constexpr bool method_not_found = false; //! //! @param REGISTRY The registry to import. May be any registry, predefined or //! user-defined. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full +//! discussion, including the required link setup. #define BOOST_OPENMETHOD_IMPORT_REGISTRY(REGISTRY) \ extern template struct BOOST_SYMBOL_IMPORT ::boost::openmethod:: \ registry_state @@ -546,9 +573,7 @@ inline constexpr bool method_not_found = false; //! owning module. //! //! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing -//! macros fit together, and -//! [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full discussion, -//! including the required link setup. +//! macros fit together. //! //! Use at namespace scope, after the registry's definition, in *every* //! translation unit of the module that owns the registry. Being a declaration @@ -570,6 +595,9 @@ inline constexpr bool method_not_found = false; //! //! @param REGISTRY The registry to export. May be any registry, predefined or //! user-defined. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full +//! discussion, including the required link setup. #define BOOST_OPENMETHOD_EXPORT_REGISTRY(REGISTRY) \ BOOST_OPENMETHOD_DETAIL_EXPORT_REGISTRY(REGISTRY) @@ -577,9 +605,7 @@ inline constexpr bool method_not_found = false; //! owning module. //! //! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing -//! macros fit together, and -//! [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full discussion, -//! including the required link setup. +//! macros fit together. //! //! Use at namespace scope, after the registry's definition, in *exactly one* //! translation unit of the module that owns the registry. It belongs in a @@ -592,6 +618,9 @@ inline constexpr bool method_not_found = false; //! //! @param REGISTRY The registry to instantiate. May be any registry, predefined //! or user-defined. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full +//! discussion, including the required link setup. #define BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(REGISTRY) \ BOOST_OPENMETHOD_DETAIL_INSTANTIATE_REGISTRY(REGISTRY) From a1746d30f426c7b889a5d9b3e4dd71ba34232cfa Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Thu, 30 Jul 2026 23:00:00 -0400 Subject: [PATCH 07/85] doc: make base-url a real Antora attribute ref_headers.adoc linked each public header to its source through `{{BASE_URL}}`, which is not an AsciiDoc construct. Asciidoctor saw the inner `{BASE_URL}`, found no such attribute and - under Antora's default `attribute-missing: skip` - left the text alone, so the literal `{{BASE_URL}}` reached the HTML and build_antora.sh rewrote it with perl after the site was built. That cost 17 "skipping reference to missing attribute" warnings on every build, indistinguishable from real ones, and left local builds with 17 broken links: BASE_URL is only computed when a CI environment is detected, so outside CI the perl step never ran. Use `link:{base-url}/...` instead, give antora.yml a fallback pointing at master - matching the one already in mrdocs.yml, which serves the same purpose for the generated reference - and have build_antora.sh pass `--attribute base-url=...` when it can determine the commit. A command-line attribute outranks the component descriptor, so the exact commit still wins in CI. The perl rewrite is gone. The mrdocs.yml patch-and-restore stays: that base-url is MrDocs configuration rather than an AsciiDoc attribute, and the reference extension builds a fixed MrDocs argument list with no hook to inject one. Co-Authored-By: Claude Opus 5 (1M context) --- doc/antora.yml | 4 +++ doc/build_antora.sh | 12 +++++++-- doc/modules/ROOT/pages/ref_headers.adoc | 34 ++++++++++++------------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/doc/antora.yml b/doc/antora.yml index dfcfac86..f26c5d02 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -15,6 +15,10 @@ asciidoc: attributes: source-language: asciidoc@ table-caption: false + # Base of the links to header sources in ref_headers.adoc. build_antora.sh + # overrides this with the exact commit when it can determine one; this is + # the fallback for builds that cannot, such as a local preview. + base-url: https://github.com/boostorg/openmethod/blob/master nav: - modules/ROOT/nav.adoc ext: diff --git a/doc/build_antora.sh b/doc/build_antora.sh index ce838990..d6dc4de3 100755 --- a/doc/build_antora.sh +++ b/doc/build_antora.sh @@ -78,7 +78,16 @@ npm ci echo "Building docs in custom dir..." PATH="$(pwd)/node_modules/.bin:${PATH}" export PATH -npx antora --clean --fetch "$PLAYBOOK" --stacktrace # --log-level all + +# ref_headers.adoc links each header to its source with `link:{base-url}/...`. +# Point that at the exact commit when we know it; otherwise antora.yml's +# fallback applies. A command-line attribute outranks the one in antora.yml. +ANTORA_ATTRS=() +if [ -n "${BASE_URL:-}" ]; then + ANTORA_ATTRS+=(--attribute "base-url=$BASE_URL") +fi + +npx antora --clean --fetch "$PLAYBOOK" "${ANTORA_ATTRS[@]}" --stacktrace # --log-level all echo "Fixing links to non-mrdocs URIs..." echo "BRANCH='${BRANCH:-}'" @@ -95,7 +104,6 @@ if [ -n "${BASE_URL:-}" ]; then else echo "mrdocs.yml.bak not found; skipping restore" fi - perl -i -pe "s[{{BASE_URL}}][$BASE_URL]g" html/openmethod/ref_headers.html fi echo "Done" diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 56277304..4bddf4cc 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -25,14 +25,14 @@ parameters: ## High-level Headers [#core] -### link:{{BASE_URL}}/include/boost/openmethod/core.hpp[] +### link:{base-url}/include/boost/openmethod/core.hpp[] Defines the main constructs of the library: methods, overriders and virtual pointers, and mechanisms to implement them. Does not define any public macros apart from `BOOST_OPENMETHOD_DEFAULT_REGISTRY`, if it is not defined already. [#macros] -### link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[] +### link:{base-url}/include/boost/openmethod/macros.hpp[] Defines the public macros of the library, such as `BOOST_OPENMETHOD`, `BOOST_OPENMETHOD_CLASSES`, etc. @@ -41,12 +41,12 @@ There is little point in including this header directly, as this has the same effect as including `boost/openmethod.hpp`, which is shorter. [#openmethod] -### link:{{BASE_URL}}/include/boost/openmethod.hpp[] +### link:{base-url}/include/boost/openmethod.hpp[] Includes `core.hpp` and `macros.hpp`. [#initialize] -### link:{{BASE_URL}}/include/boost/openmethod/initialize.hpp[] +### link:{base-url}/include/boost/openmethod/initialize.hpp[] Provides the cpp:initialize[] and cpp:finalize[] functions. This header is typically included in the translation unit containing `main`. Translation units @@ -54,19 +54,19 @@ that dynamically load or unload shared libraries may also need to call those functions. [#std_shared_ptr] -### link:{{BASE_URL}}/include/boost/openmethod/interop/std_shared_ptr.hpp[] +### link:{base-url}/include/boost/openmethod/interop/std_shared_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::shared_ptr` in place of a raw pointer or reference in virtual parameters. [#std_unique_ptr] -### link:{{BASE_URL}}/include/boost/openmethod/interop/std_unique_ptr.hpp[] +### link:{base-url}/include/boost/openmethod/interop/std_unique_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::unique_ptr` in place of a raw pointer or reference in virtual parameters. [#boost_intrusive_ptr] -### link:{{BASE_URL}}/include/boost/openmethod/interop/boost_intrusive_ptr.hpp[] +### link:{base-url}/include/boost/openmethod/interop/boost_intrusive_ptr.hpp[] 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. @@ -79,52 +79,52 @@ The following headers can be included before `core.hpp` to define custom registries and policies, and override the default registry by defining xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. -### link:{{BASE_URL}}/include/boost/openmethod/preamble.hpp[] +### link:{base-url}/include/boost/openmethod/preamble.hpp[] Defines `registry` and stock policy categories. Also defines all types and functions necessary for the definition of `registry`. -### link:{{BASE_URL}}/include/boost/openmethod/policies/std_rtti.hpp[] +### link:{base-url}/include/boost/openmethod/policies/std_rtti.hpp[] Provides an implementation of the `rtti` policy using standard RTTI. -### link:{{BASE_URL}}/include/boost/openmethod/policies/fast_perfect_hash.hpp[] +### link:{base-url}/include/boost/openmethod/policies/fast_perfect_hash.hpp[] Provides an implementation of the `hash` policy using a fast perfect hash function. -### link:{{BASE_URL}}/include/boost/openmethod/policies/vptr_vector.hpp[] +### link:{base-url}/include/boost/openmethod/policies/vptr_vector.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a `std::vector` indexed by type ids, possibly hashed. -### link:{{BASE_URL}}/include/boost/openmethod/policies/default_error_handler.hpp[] +### link:{base-url}/include/boost/openmethod/policies/default_error_handler.hpp[] Provides an implementation of the `error_handler` policy that calls a `std::function` when an error is encountered, and before the library aborts the program. -### link:{{BASE_URL}}/include/boost/openmethod/policies/stderr_output.hpp[] +### link:{base-url}/include/boost/openmethod/policies/stderr_output.hpp[] Provides an implementation of the `output` policy that writes diagnostics to the C standard error stream (not using iostreams). -### link:{{BASE_URL}}/include/boost/openmethod/default_registry.hpp[] +### link:{base-url}/include/boost/openmethod/default_registry.hpp[] Defines the default registry, which contains all the stock policies listed above. Includes all the headers listed in this section so far. -### link:{{BASE_URL}}/include/boost/openmethod/policies/static_rtti.hpp[] +### link:{base-url}/include/boost/openmethod/policies/static_rtti.hpp[] Provides a minimal implementation of the `rtti` policy that does not depend on standard RTTI. -### link:{{BASE_URL}}/include/boost/openmethod/policies/throw_error_handler.hpp[] +### link:{base-url}/include/boost/openmethod/policies/throw_error_handler.hpp[] Provides an implementation of the `error_handler` policy that throws errors as exceptions. -### link:{{BASE_URL}}/include/boost/openmethod/policies/vptr_map.hpp[] +### link:{base-url}/include/boost/openmethod/policies/vptr_map.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a map (by default a `std::map`) indexed by type ids. From 2a8e394fd9f60fdf6d940519d243dd425f39a3fc Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Thu, 30 Jul 2026 23:22:00 -0400 Subject: [PATCH 08/85] doc: tidy the registries and shared libraries pages List the stock policies in the order a registry declares them, so the table reads the same way as `default_registry`: type_hash before vptr. Drop the "This section discusses" preamble from the shared libraries page and state the fact directly. Co-Authored-By: Claude Opus 5 (1M context) --- doc/modules/ROOT/pages/registries_and_policies.adoc | 8 ++++---- doc/modules/ROOT/pages/shared_libraries.adoc | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index f5154381..0ca7414f 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -34,14 +34,14 @@ Policies are placed in the cpp:boost::openmethod::policies[] namespace. | std_rtti | provides type information for classes and objects -| vptr -| vptr_vector -| stores vptrs in an indexed collection - | type_hash | fast_perfect_hash | hashes type id to an index in a vector +| vptr +| vptr_vector +| stores vptrs in an indexed collection + | error_handler | default_error_handler | calls an overridable handler function diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index a9c6ccab..97da9bbf 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -2,8 +2,8 @@ [#shared_libraries] -This section discusses how OpenMethod interoperates with shared libraries on -Linux, other POSIX-like platforms, and Windows. +OpenMethod interoperates with shared libraries on Linux, other POSIX-like +platforms, and Windows. OpenMethod uses global data to keep track of methods, overriders and classes, all managed by static constructors and destructors. cpp:initialize[] uses that From 92a87e31a8c833eacfe43dd1b83ac6bbd297b52d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Thu, 30 Jul 2026 23:52:00 -0400 Subject: [PATCH 09/85] doc: guide links on every reference page, via a template override The See Also sections added in b2dcdb8 stopped at the macro pages. The other 52 links, on the C++ symbol pages, had to be dropped: MrDocs sets `:relfileprefix: ../../` on nested pages, Asciidoctor folds it into the xref target Antora resolves, and `../../ROOT:basics.adoc` is not a valid resource id. Clearing the attribute is not an option - it breaks ~1250 breadcrumb links, because the xrefs in the document title bypass Antora's resolver and need it (cppalliance/mrdocs#1245). Override `markup/a.adoc.hbs` instead, through `addons-supplemental` - the documented way to replace a few templates while falling back to the built-ins for the rest. The new branch recognises an `xref:ROOT:` href and, on a nested page, emits a `link:` rather than an `xref:`. A link macro is not an inter-document xref, so relfileprefix never touches it; `relfileprefix` reaches the reference module root and the guide sits one level above it, hence the extra `../`. At the root the href is passed through unchanged, so the macro pages keep emitting real xrefs and Antora still validates them. Everything else in the file is upstream verbatim, so it diffs cleanly against a newer MrDocs, and the header comment says when to delete it. With that in place, restore the 52 links: the error types and policy categories in preamble.hpp, the smart-pointer traits and aliases in interop, the stock policies, `method`, `use_classes` and `virtual_ptr` in core.hpp, `initialize` and `finalize`, and the two inplace_vptr mixins. 73 guide links now render across 65 reference pages, at depths 0, 2 and 3. The whole-site link check is unchanged at 19 broken links, all of them the `file://` edit-page links a local build always produces - in particular the breadcrumbs are intact, which is what the earlier attempt at clearing relfileprefix broke. Co-Authored-By: Claude Opus 5 (1M context) --- .../generator/adoc/partials/markup/a.adoc.hbs | 46 +++++++++++++++++++ doc/mrdocs.yml | 5 ++ include/boost/openmethod/core.hpp | 11 +++++ include/boost/openmethod/initialize.hpp | 5 ++ include/boost/openmethod/inplace_vptr.hpp | 4 ++ .../interop/boost_intrusive_ptr.hpp | 8 ++++ .../openmethod/interop/std_shared_ptr.hpp | 8 ++++ .../openmethod/interop/std_unique_ptr.hpp | 6 +++ .../policies/default_error_handler.hpp | 2 + .../openmethod/policies/fast_perfect_hash.hpp | 2 + .../boost/openmethod/policies/static_rtti.hpp | 2 + .../boost/openmethod/policies/std_rtti.hpp | 2 + .../openmethod/policies/stderr_output.hpp | 2 + .../policies/throw_error_handler.hpp | 2 + .../boost/openmethod/policies/vptr_map.hpp | 2 + .../boost/openmethod/policies/vptr_vector.hpp | 2 + include/boost/openmethod/preamble.hpp | 38 +++++++++++++++ 17 files changed, 147 insertions(+) create mode 100644 doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs diff --git a/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs b/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs new file mode 100644 index 00000000..536b98c6 --- /dev/null +++ b/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs @@ -0,0 +1,46 @@ +{{! + Overrides the built-in markup/a partial, adding the `xref:ROOT:` branch + below. Everything else is the upstream template verbatim; keep it that way + so the file is easy to diff against a newer MrDocs. + + Why the extra branch: a doc comment reaches a hand-written guide page with a + markdown link whose target is an Antora resource ID, which the final `else` + emits verbatim: + + //! @see [Methods and Overriders](xref:ROOT:basics.adoc) + + That works only at the output root. MrDocs sets `:relfileprefix: ../../` on + nested pages, Asciidoctor folds it into the xref target Antora resolves, and + `../../ROOT:basics.adoc` is not a valid resource ID. Clearing the attribute + is not an option - the breadcrumbs in the document title are converted by + plain Asciidoctor and need it. See cppalliance/mrdocs#1245. + + So on a nested page emit a `link:` instead: a link macro is not an + inter-document xref, so relfileprefix never touches it. `relfileprefix` + reaches the reference module root and the ROOT module sits one level above + it, hence the extra `../`. At the root the href is passed through unchanged, + so those links stay real xrefs and Antora still validates them. + + Delete this file once #1245 is resolved upstream. + + Do not relativize links as asciidoc does not support it. + + https://gitlab.com/antora/antora/-/issues/428 +}} +{{#if (eq href @root.symbol.url)~}} + {{{> @partial-block }}} +{{~else if (starts_with href "#")~}} + link:{{{ href }}}[{{> @partial-block }}] +{{~else if (starts_with href "xref:ROOT:")~}} +{{~#if @root.page.relfileprefix~}} + link:{{{@root.page.relfileprefix}}}../{{{replace (remove_prefix href "xref:ROOT:") ".adoc" ".html"}}}[{{> @partial-block }}] +{{~else~}} + {{{href}}}[{{> @partial-block }}] +{{~/if~}} +{{~else if (starts_with href "/")~}} + xref:{{{remove_prefix href "/"}}}[{{> @partial-block }}] +{{~else if (starts_with href ".")~}} + xref:{{{href}}}[{{> @partial-block }}] +{{~else~}} + {{{href}}}[{{> @partial-block }}{{#if blank}}^{{/if}}] +{{~/if~}} diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 97ae2cd5..c77a80ab 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -45,6 +45,11 @@ inherit-base-members: never private-bases: false auto-function-metadata: false +# Template overrides, layered on top of the built-in addons. See the header +# comment in the overridden file for what it changes and why. +addons-supplemental: + - mrdocs-addons + # Generator generate: adoc base-url: https://www.github.com/boostorg/openmethod/blob/master/ diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 3dda139b..48b6627d 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -441,6 +441,9 @@ using use_classes_tuple_type = boost::mp11::mp_apply< //! //! Virtual and multiple inheritance are supported, with the exclusion of //! repeated inheritance. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template class use_classes { detail::use_classes_tuple_type tuple; @@ -702,6 +705,10 @@ inline auto final_virtual_ptr(Arg&& obj) { //! @tparam Class The class of the object, possibly cv-qualified //! @tparam Registry The registry in which `Class` is registered //! @tparam unnamed Implementation defined, use default +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) +//! @see [Performance](xref:ROOT:performance.adoc) template class virtual_ptr { @@ -1189,6 +1196,8 @@ class virtual_ptr { //! //! @tparam SmartPtr A smart pointer type //! @tparam Registry The registry in which the underlying class is registered +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template class virtual_ptr< SmartPtr, Registry, @@ -2177,6 +2186,8 @@ struct validate_method_parameter< //! @tparam Id A type //! @tparam Fn A function type //! @tparam Registry The registry in which the method is defined +//! +//! @see [Core API](xref:ROOT:core_api.adoc) template< typename Id, typename Fn, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY> diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 0d239d77..c1c5f4ea 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -1907,6 +1907,9 @@ void registry::compiler::print( //! // ... //! } //! @endcode +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) template inline auto initialize(Options&&... options) { if (detail::odr_check::count > 1) { @@ -1987,6 +1990,8 @@ auto registry::finalize(Options... opts) -> void { //! @tparam Options... Zero or more option types, deduced from the function //! arguments. //! @param options Zero or more option objects. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) template inline auto finalize(Options&&... opts) -> void { Registry::finalize(std::forward(opts)...); diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index e2363e58..171ca009 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -124,6 +124,8 @@ class inplace_vptr_base_tag {}; //! return 0; //! } //! @endcode +//! +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) template class inplace_vptr_base : protected detail::inplace_vptr_base_tag { template @@ -180,6 +182,8 @@ class inplace_vptr_base : protected detail::inplace_vptr_base_tag { //! @tparam Class The class in which to embed the v-table pointer. //! @tparam Base A direct base class of `Class`. //! @tparam MoreBases More direct base classes of `Class`. +//! +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) template class inplace_vptr_derived { protected: diff --git a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp index 5ab68294..63b0cb76 100644 --- a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp +++ b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp @@ -16,6 +16,8 @@ namespace boost::openmethod { //! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits, Registry> { //! Rebind to a different element type. @@ -62,6 +64,8 @@ struct virtual_traits, Registry> { //! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits&, Registry> { public: @@ -114,6 +118,8 @@ struct virtual_traits&, Registry> { }; //! Alias for a `virtual_ptr>`. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using boost_intrusive_virtual_ptr = virtual_ptr, Registry>; @@ -132,6 +138,8 @@ using boost_intrusive_virtual_ptr = //! @param args Arguments to pass to the constructor of `Class`. //! @return A `boost_intrusive_virtual_ptr` pointing to a newly //! created object of type `Class`. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index d0ac4f9e..d859ec45 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -55,6 +55,8 @@ struct validate_method_parameter< //! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits, Registry> { //! Rebind to a different element type. @@ -138,6 +140,8 @@ struct virtual_traits, Registry> { //! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits&, Registry> { public: @@ -186,6 +190,8 @@ struct virtual_traits&, Registry> { }; //! Alias for a `virtual_ptr>`. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using shared_virtual_ptr = virtual_ptr, Registry>; @@ -203,6 +209,8 @@ using shared_virtual_ptr = virtual_ptr, Registry>; //! @param args Arguments to pass to the constructor of `Class`. //! @return A `shared_virtual_ptr` pointing to a newly //! created object of type `Class`. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/std_unique_ptr.hpp b/include/boost/openmethod/interop/std_unique_ptr.hpp index bcda19fa..b2190957 100644 --- a/include/boost/openmethod/interop/std_unique_ptr.hpp +++ b/include/boost/openmethod/interop/std_unique_ptr.hpp @@ -16,6 +16,8 @@ namespace boost::openmethod { //! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits, Registry> { //! `Class`, stripped from cv-qualifiers. @@ -62,6 +64,8 @@ struct virtual_traits, Registry> { }; //! Alias for a `virtual_ptr>`. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using unique_virtual_ptr = virtual_ptr, Registry>; @@ -79,6 +83,8 @@ using unique_virtual_ptr = virtual_ptr, Registry>; //! @param args Arguments to pass to the constructor of `Class`. //! @return A `unique_virtual_ptr` pointing to a newly //! created object of type `Class`. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index 02de74fe..b7e19e96 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -32,6 +32,8 @@ namespace policies { //! handler with a function that throws an exception, possibly preventing //! program termination. The @ref throw_error_handler policy can also be used to //! enable exception throwing on a registry basis. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct default_error_handler : error_handler { //! A ErrorHandlerFn metafunction. diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index 331dc112..204f9281 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -60,6 +60,8 @@ namespace policies { //! corresponds to a value in the domain, or even that the codomain is a dense //! range of integers. In other words, a lot of space may be wasted in presence //! of large sets of type_ids. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct fast_perfect_hash : type_hash { //! Cannot find hash factors diff --git a/include/boost/openmethod/policies/static_rtti.hpp b/include/boost/openmethod/policies/static_rtti.hpp index a197a36f..a8750376 100644 --- a/include/boost/openmethod/policies/static_rtti.hpp +++ b/include/boost/openmethod/policies/static_rtti.hpp @@ -22,6 +22,8 @@ namespace boost::openmethod::policies { //! @par Example //! TODO //! include::example$static_rtti.cpp[tag=all] +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct static_rtti : rtti { //! A RttiFn metafunction. //! diff --git a/include/boost/openmethod/policies/std_rtti.hpp b/include/boost/openmethod/policies/std_rtti.hpp index 88857c56..e2f9986b 100644 --- a/include/boost/openmethod/policies/std_rtti.hpp +++ b/include/boost/openmethod/policies/std_rtti.hpp @@ -20,6 +20,8 @@ namespace boost::openmethod::policies { //! //! `std_rtti` implements the `rtti` policy using the standard C++ RTTI system. //! It is the default RTTI policy. +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct std_rtti : rtti { //! A RttiFn metafunction. //! diff --git a/include/boost/openmethod/policies/stderr_output.hpp b/include/boost/openmethod/policies/stderr_output.hpp index 595de98a..85c4eacc 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -16,6 +16,8 @@ namespace policies { //! @ref Writes to the C standard error stream. //! //! `stderr_output` writes to standard error using the C API. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct stderr_output : output { //! An OutputFn metafunction. template diff --git a/include/boost/openmethod/policies/throw_error_handler.hpp b/include/boost/openmethod/policies/throw_error_handler.hpp index 066f17d4..edc95ee5 100644 --- a/include/boost/openmethod/policies/throw_error_handler.hpp +++ b/include/boost/openmethod/policies/throw_error_handler.hpp @@ -16,6 +16,8 @@ namespace boost::openmethod::policies { //! Throws error as an exception. //! +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct throw_error_handler : error_handler { //! A ErrorHandlerFn metafunction. //! diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index 54be921c..da8e7970 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -24,6 +24,8 @@ namespace policies { //! //! @tparam MapFn A mp11 quoted metafunction that takes a key type and a //! value type, and returns an @ref AssociativeContainer. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template> class vptr_map : public vptr { public: diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index ae3f7ac5..c7ec0ea0 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -24,6 +24,8 @@ namespace policies { //! //! If the registry contains the @ref indirect_vptr policy, stores pointers to //! pointers to v-tables in the vector. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct vptr_vector : vptr { public: //! A VptrFn metafunction. diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 27c756fe..71920f47 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -80,6 +80,8 @@ using type_id = const void*; //! - @ref virtual_traits must be specialized for `T`. //! //! @tparam T A class. +//! +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) template struct virtual_; @@ -90,6 +92,8 @@ struct virtual_traits; // Error handling //! Base class for all OpenMethod errors. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct openmethod_error {}; //! One Definition Rule violation. @@ -97,6 +101,8 @@ struct openmethod_error {}; //! This error is raised if the definition of @ref default_registry is //! inconsistent across translation units, due to misuse of //! @ref BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct odr_violation : openmethod_error { //! Write a description of the error to a stream. //! @tparam Registry The registry containing this policy. @@ -132,6 +138,8 @@ std::size_t odr_check::inc = count++; } // namespace detail //! Registry not initialized +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct not_initialized : openmethod_error { //! Write a short description to an output stream //! @param os The output stream @@ -186,6 +194,8 @@ struct not_initialized : openmethod_error { //! Bulldog hector; //! poke(hector); // throws missing_class; //! @endcode +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_class : openmethod_error { //! The type_id of the unknown class. type_id type; @@ -225,6 +235,8 @@ struct missing_class : openmethod_error { //! @code //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); //! @endcode +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_base : openmethod_error { //! The type_id of the base class. type_id base; @@ -240,6 +252,8 @@ struct missing_base : openmethod_error { }; //! No valid overrider +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct bad_call : openmethod_error { //! The type_id of method that was called type_id method; @@ -254,6 +268,7 @@ struct bad_call : openmethod_error { //! No overrider for virtual tuple //! //! @see @ref bad_call for data members. +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct no_overrider : bad_call { //! Write a short description to an output stream //! @param os The output stream @@ -268,6 +283,7 @@ struct no_overrider : bad_call { //! Ambiguous call //! //! @see @ref bad_call for data members. +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct ambiguous_call : bad_call { //! Write a short description to an output stream //! @param os The output stream @@ -287,6 +303,8 @@ struct ambiguous_call : bad_call { //! policy, its @ref error function is called with a `final_error` object, then //! the program is terminated with //! @ref abort. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct final_error : openmethod_error { type_id static_type, dynamic_type; @@ -484,6 +502,7 @@ inline trace trace::from_env() { //! that conforms to the blueprint's requirements. //! //! @see @ref registry for a complete explanation of registries and policies. +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) namespace policies { @@ -600,6 +619,8 @@ struct RttiFn { //! @li derive from @c rtti. //! @li provide a @c fn metafunction that conforms to the @ref RttiFn //! blueprint. +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct rtti { // Policy category. using category = rtti; @@ -638,6 +659,8 @@ struct rtti { //! and overriders. This creates order-of-initialization issues. Deriving a @e //! rtti policy from this class - instead of just `rtti` - causes the collection //! of type ids to be deferred until the first call to @ref update. +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct deferred_static_rtti : rtti {}; // ----------------------------------------------------------------------------- @@ -667,6 +690,8 @@ struct ErrorHandlerFn { //! @li derive from @c error_handler. //! @li provide a @c fn metafunction that conforms to the @ref //! ErrorHandlerFn blueprint. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct error_handler { // Policy category. using category = error_handler; @@ -726,6 +751,8 @@ struct VptrFn { //! @li derive from @c vptr. //! @li provide a @c fn metafunction that conforms to the @ref //! VptrFn blueprint. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct vptr { // Policy category. using category = vptr; @@ -738,6 +765,8 @@ struct vptr { //! These indirect pointers remain valid after a call to @ref initialize, after //! dynamically loading a library that adds classes, methods and overriders to //! the registry. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) struct indirect_vptr final { // Policy category. using category = indirect_vptr; @@ -802,6 +831,8 @@ struct TypeHashFn { //! @li derive from @c type_hash. //! @li provide a @c fn metafunction that conforms to the @ref //! TypeHashFn blueprint. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct type_hash { // Policy category. using category = type_hash; @@ -835,6 +866,8 @@ struct OutputFn { //! @li derive from @c output. //! @li provide a @c fn metafunction that conforms to the @ref //! OutputFn blueprint. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct output { // Policy category. using category = output; @@ -849,6 +882,8 @@ struct output { //! @li Classes of virtual arguments have been registered. //! @li Dynamic and static types match in "final" constructs (@ref //! final_virtual_ptr and related functions). +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct runtime_checks final { // Policy category. using category = runtime_checks; @@ -1066,6 +1101,8 @@ struct initialize_aux; //! // exactly one .cpp of the owning module: //! BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(boost::openmethod::default_registry); //! @endcode +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) template struct registry_state { static detail::registry_state_type st; @@ -1123,6 +1160,7 @@ detail::registry_state_type registry_state::st; //! @li @c Policy must contain a @c fn metafunction. //! //! @see @ref policies +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template class registry : public detail::registry_base { From 8a691965531e704ec1c6ae55a747629b33491611 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Fri, 31 Jul 2026 00:06:25 -0400 Subject: [PATCH 10/85] doc: render See Also as one comma-separated list The built-in see-also partial renders each @see entry through `doc/block/see`, i.e. as a block, and MrDocs separates blocks with a blank line - which AsciiDoc reads as a paragraph break. A symbol with several @see entries therefore got a paragraph each, one link per line. Override the partial to render the entries inline instead. `doc/inline-container` is what `doc/block/see` reaches through `doc/block/paragraph`, so going straight to it drops the block separation; the entries are then joined with ", " and the whole list is one paragraph. The blank line before the section closes keeps the page footer out of it. Four @see entries were sentences rather than bare references, which a comma join would have read as "... for data members., Error Handling". Move the clause into the description, where it is more visible anyway, and leave the reference bare: no_overrider and ambiguous_call pointing at bad_call, the policies namespace pointing at registry, and inplace_vptr_derived pointing at inplace_vptr_base. Co-Authored-By: Claude Opus 5 (1M context) --- .../partials/symbol/section/see-also.hbs | 26 +++++++++++++++++++ include/boost/openmethod/inplace_vptr.hpp | 4 ++- include/boost/openmethod/preamble.hpp | 12 ++++++--- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 doc/mrdocs-addons/generator/common/partials/symbol/section/see-also.hbs diff --git a/doc/mrdocs-addons/generator/common/partials/symbol/section/see-also.hbs b/doc/mrdocs-addons/generator/common/partials/symbol/section/see-also.hbs new file mode 100644 index 00000000..0e0ac365 --- /dev/null +++ b/doc/mrdocs-addons/generator/common/partials/symbol/section/see-also.hbs @@ -0,0 +1,26 @@ +{{! + Overrides the built-in symbol/section/see-also partial. + + The built-in renders each @see entry through `doc/block/see`, i.e. as a + block, and MrDocs separates blocks with a blank line - which AsciiDoc reads + as a paragraph break, so a symbol with several @see entries gets a paragraph + each. This renders them inline instead, comma-separated on one line, the + conventional shape for a See Also list. + + `doc/inline-container` is what `doc/block/see` reaches through + `doc/block/paragraph`; going straight to it is what drops the block + separation. The blank line before the section closes keeps whatever follows + out of the same paragraph. + + Entries are joined with ", ", so each @see should be a bare reference rather + than a sentence. +}} +{{#if symbol.doc.sees}} +{{#> markup/section name="see-also"}} +{{#> markup/dynamic-level-h }}See Also{{/markup/dynamic-level-h~}} +{{#each symbol.doc.sees~}} +{{> doc/inline-container .}}{{#unless @last}}, {{/unless}} +{{~/each}} + +{{/markup/section}} +{{/if}} diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index 171ca009..2eac8c62 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -177,7 +177,9 @@ class inplace_vptr_base : protected detail::inplace_vptr_base_tag { //! @ref policies::vptr policy, nor any policy it depends on (like @ref //! policies::type_hash). //! -//! @see @ref inplace_vptr_base for an example. +//! @ref inplace_vptr_base carries an example. +//! +//! @see @ref inplace_vptr_base //! //! @tparam Class The class in which to embed the v-table pointer. //! @tparam Base A direct base class of `Class`. diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 71920f47..775cd703 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -267,7 +267,9 @@ struct bad_call : openmethod_error { //! No overrider for virtual tuple //! -//! @see @ref bad_call for data members. +//! The data members are documented on @ref bad_call. +//! +//! @see @ref bad_call //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct no_overrider : bad_call { //! Write a short description to an output stream @@ -282,7 +284,9 @@ struct no_overrider : bad_call { //! Ambiguous call //! -//! @see @ref bad_call for data members. +//! The data members are documented on @ref bad_call. +//! +//! @see @ref bad_call //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct ambiguous_call : bad_call { //! Write a short description to an output stream @@ -501,7 +505,9 @@ inline trace trace::from_env() { //! implementing these blueprints must provide a `fn` metafunction //! that conforms to the blueprint's requirements. //! -//! @see @ref registry for a complete explanation of registries and policies. +//! @ref registry carries a complete explanation of registries and policies. +//! +//! @see @ref registry //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) namespace policies { From 2fd58ddc8ebf6386e1b96e0cf91f65f182ac7284 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Fri, 31 Jul 2026 00:16:17 -0400 Subject: [PATCH 11/85] doc: restore mrdocs.yml from a trap, not at the end of the build The base-url MrDocs puts behind every "Declared in
" link comes from mrdocs.yml, and the Antora extension invokes MrDocs with a fixed argument list, so pointing it at the commit means editing the file in place. The restore ran at the end of the script, which `set -e` skips: a build that failed anywhere after the edit left mrdocs.yml patched, and the next run then copied the patched file to mrdocs.yml.bak and restored that - losing the original base-url for good. Move the restore into an EXIT trap armed right after the backup is taken, so it runs whether the build succeeds or aborts. Paths are absolute so the trap does not depend on the working directory at exit. Co-Authored-By: Claude Opus 5 (1M context) --- doc/build_antora.sh | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/doc/build_antora.sh b/doc/build_antora.sh index d6dc4de3..0b6f680a 100755 --- a/doc/build_antora.sh +++ b/doc/build_antora.sh @@ -62,10 +62,24 @@ fi cd "$SCRIPT_DIR" +# MrDocs takes its own base-url - the one behind the "Declared in
" link +# on every reference page - from mrdocs.yml, and the Antora extension invokes it +# with a fixed argument list, so there is no way to pass the commit other than +# editing the file. Restore it from an EXIT trap rather than at the end of the +# script: without one, a failed build leaves mrdocs.yml patched, and the next +# run backs up the patched file and loses the original. +restore_mrdocs_yml() { + if [ -f "$SCRIPT_DIR/mrdocs.yml.bak" ]; then + mv -f "$SCRIPT_DIR/mrdocs.yml.bak" "$SCRIPT_DIR/mrdocs.yml" + echo "Restored original mrdocs.yml" + fi +} + if [ -n "${REPOSITORY}" ] && [ -n "${SHA}" ]; then BASE_URL="https://github.com/${REPOSITORY}/blob/${SHA}" echo "Setting base-url to $BASE_URL" cp mrdocs.yml mrdocs.yml.bak + trap restore_mrdocs_yml EXIT perl -i -pe 's{^\s*base-url:.*$}{base-url: '"$BASE_URL/"'}' mrdocs.yml else echo "REPOSITORY or SHA not set; skipping base-url modification" @@ -97,13 +111,4 @@ for f in $(find html -name '*.html'); do perl -i -pe "s{Boost.OpenMethod}{Boost.OpenMethod}g" "$f" done -if [ -n "${BASE_URL:-}" ]; then - if [ -f mrdocs.yml.bak ]; then - mv -f mrdocs.yml.bak mrdocs.yml - echo "Restored original mrdocs.yml" - else - echo "mrdocs.yml.bak not found; skipping restore" - fi -fi - echo "Done" From c09d995a5178d642faeef7aaa6e546e1add8a947 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Fri, 31 Jul 2026 03:37:24 -0400 Subject: [PATCH 12/85] doc: make the Reference nav entry a link It was the only bare label in the navigation with somewhere obvious to point: the reference module's index page, which lists the namespaces and the macros. "Basic Features" and "Advanced Features" stay labels, having no page of their own. Co-Authored-By: Claude Opus 5 (1M context) --- doc/modules/ROOT/nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index edaebaa3..b6b8b6c2 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -14,7 +14,7 @@ ** xref:error_handling.adoc[Error Handling] ** xref:virtual_ptr_alt.adoc[Virtual Pointer Alternatives] ** xref:shared_libraries.adoc[Shared Libraries] -* Reference +* xref:reference:index.adoc[Reference] ** xref:ref_headers.adoc[Headers] ** xref:ref_macros.adoc[Macros] ** xref:reference:boost/openmethod.adoc[Namespace boost::openmethod] From 491405325a41fc3585ae9825913402e08efa8f84 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Fri, 31 Jul 2026 04:35:43 -0400 Subject: [PATCH 13/85] support std::any by mutable and xvalue reference Add virtual_traits, and test dispatch on a std::any passed by mutable lvalue reference and by xvalue reference. virtual_ silently bound the generic virtual_traits, whose cast goes through optimal_cast - a static_cast/dynamic_cast that cannot compile against an overrider taking a reference to the contained type. Add a specialization with the full member set. virtual_traits::cast passed its parameter to std::any_cast as an lvalue, selecting the any_cast(any&) overload, which asserts is_constructible_v - false for an rvalue reference U. Forward it as an rvalue so any_cast(any&&) is selected. Also fix dynamic_vptr in that same specialization: it named the rtti policy, which has no type_vptr, and passed a type_info by value where a type_id is wanted. It compiles today only because acquire_vptr normalizes every reference category to const& before looking dynamic_vptr up, so the body is never instantiated. The mutable reference overriders cannot use BOOST_OPENMETHOD_OVERRIDE: the macro locates the method by checking that the overrider's parameter types can be passed to the method's forwarder, and nothing converts to a mutable lvalue reference to std::any. Register them via method<...>::override instead. Co-Authored-By: Claude Opus 5 (1M context) --- include/boost/openmethod/interop/std_any.hpp | 56 ++++----- test/test_dispatch_std_any.cpp | 119 +++++++++++++------ 2 files changed, 110 insertions(+), 65 deletions(-) diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 15267772..b02df1a5 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -12,9 +12,6 @@ namespace boost::openmethod { namespace detail { -template -struct validate_method_parameter, Registry, void> - : std::true_type {}; template struct validate_method_parameter, Registry, void> @@ -30,15 +27,14 @@ struct validate_method_parameter, Registry, void> } // namespace detail -//! Specialize virtual_traits for std::any by value. +//! 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()`. Requires the registry to use a @ref -//! rtti policy that provides `dynamic_type` (e.g. @ref std_rtti). +//! obtained via `std::any::type()`. //! //! @tparam Registry A @ref registry. template -struct virtual_traits { +struct virtual_traits { //! The type used for dispatch. using virtual_type = std::any; @@ -64,29 +60,33 @@ struct virtual_traits { //! terminates the program with @ref abort. //! //! @param arg A reference to a const `any`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for the stored value. static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { - return Registry::rtti::type_vptr(arg.type()); - }; + return Registry::vptr::type_vptr(&arg.type()); + } //! Cast to a type. //! - //! Extracts the stored value using `std::any_cast`. + //! 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. `Dog&`, `const Dog&`, `Dog`). - //! @param arg An rvalue reference to the `std::any` method argument. + //! @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) { + static auto cast(const std::any& arg) -> decltype(auto) { 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 { +struct virtual_traits { //! The type used for dispatch. using virtual_type = std::any; @@ -99,8 +99,8 @@ struct virtual_traits { //! 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. + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::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. @@ -111,31 +111,31 @@ struct virtual_traits { //! 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 a the v-table pointer for `Class`. + //! @param arg A reference to a `std::any`. + //! @return A reference to the v-table pointer for the stored value. static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { return Registry::vptr::type_vptr(&arg.type()); - }; + } //! Cast to a type. //! //! Extracts the stored value using `std::any_cast`. Supports mutable - //! references (e.g. `Dog&`) because the `any` argument is non-const. + //! 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(const std::any& arg) -> decltype(auto) { + static auto cast(std::any& arg) -> decltype(auto) { return std::any_cast(arg); } }; -//! Specialize virtual_traits for std::any by value. +//! 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()`. Requires the registry to use a @ref -//! rtti policy that provides `dynamic_type` (e.g. @ref std_rtti). +//! obtained via `std::any::type()`. //! //! @tparam Registry A @ref registry. template @@ -167,8 +167,8 @@ struct virtual_traits { //! @param arg A reference to a const `any`. //! @return A reference to a the v-table pointer for `Class`. static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { - return Registry::rtti::type_vptr(arg.type()); - }; + return Registry::vptr::type_vptr(&arg.type()); + } //! Cast to a type. //! @@ -179,7 +179,7 @@ struct virtual_traits { //! @return The value stored in `arg`, cast to `U`. template static auto cast(std::any&& arg) -> decltype(auto) { - return std::any_cast(arg); + return std::any_cast(std::move(arg)); } }; diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index b0b7667b..f746dbe1 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -22,33 +22,34 @@ using namespace boost::openmethod; \ use_any_types BOOST_OPENMETHOD_GENSYM; -#if 1 - namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- -// pass virtual args as std::any by value +// pass virtual args as const std::any& (const ref) + +static_assert(detail::has_dynamic_vptr< + virtual_traits, type_id>); MAKE_CLASSES(); -BOOST_OPENMETHOD(name, (virtual_), std::string); +BOOST_OPENMETHOD(name, (virtual_), std::string); -BOOST_OPENMETHOD_OVERRIDE(name, (Dog dog), std::string) { +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { return dog.name + " the dog"; } -BOOST_OPENMETHOD_OVERRIDE(name, (std::string name), std::string) { +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { return name; } -BOOST_OPENMETHOD_OVERRIDE(name, (int value), std::string) { +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_value) { - initialize(); +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"}); @@ -59,71 +60,115 @@ BOOST_AUTO_TEST_CASE(std_any_by_value) { BOOST_TEST(name(answer) == "42 the integer"); } } // namespace BOOST_OPENMETHOD_GENSYM -#endif + namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- -// pass virtual args as const std::any& (const ref) +// pass virtual args as std::any& (mutable ref) static_assert(detail::has_dynamic_vptr< - virtual_traits, type_id>); + virtual_traits, type_id>); MAKE_CLASSES(); -BOOST_OPENMETHOD(name, (virtual_), std::string); +BOOST_OPENMETHOD(bump, (virtual_), std::string); -BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), 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"; } -BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { +auto bump_string(std::string& name) -> std::string { + name += "!"; return name; } -BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) { +auto bump_int(int& value) -> std::string { + ++value; std::ostringstream os; os << value << " the integer"; return os.str(); } -BOOST_AUTO_TEST_CASE(std_any_by_const_ref) { +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()); - const std::any spot(Dog{"Spot"}); - const std::any felix(std::string{"Felix the cat"}); - const std::any answer(42); + std::any spot(Dog{"Spot"}); + std::any felix(std::string{"Felix the cat"}); + std::any answer(41); - BOOST_TEST(name(spot) == "Spot the dog"); - BOOST_TEST(name(felix) == "Felix the cat"); - BOOST_TEST(name(answer) == "42 the integer"); + 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 -#if 0 + namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- -// pass virtual args as std::any&& (rvalue ref, move semantics) +// pass virtual args as std::any&& (xvalue ref) + +static_assert(detail::has_dynamic_vptr< + virtual_traits, type_id>); MAKE_CLASSES(); -BOOST_OPENMETHOD(name, (virtual_), std::string); +BOOST_OPENMETHOD(steal, (virtual_), std::string); -BOOST_OPENMETHOD_OVERRIDE(name, (Dog dog), std::string) { - return dog.name + " the dog"; +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(name, (Cat cat), std::string) { - return cat.name + " the cat"; +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_rvalue_ref) { - initialize(); +BOOST_AUTO_TEST_CASE(std_any_by_xvalue_ref) { + initialize(trace()); std::any spot(Dog{"Spot"}); - std::any felix(Cat{"Felix"}); - - BOOST_TEST(name(std::move(spot)) == "Spot the dog"); - BOOST_TEST(name(std::move(felix)) == "Felix the cat"); + 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 -#endif \ No newline at end of file From 810a9816734d80344a6b0b9f7169b4ea9f592f54 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Fri, 31 Jul 2026 05:57:44 -0400 Subject: [PATCH 14/85] support boost::any Add interop/boost_any.hpp, mirroring interop/std_any.hpp: virtual_traits specializations for const boost::any&, boost::any& and boost::any&&, and a use_boost_any_types registrar. Dispatch is on the type of the contained value, obtained from boost::any::type(), which yields the same std::type_info object std_rtti keys on. boost::any_cast is looser than std::any_cast. Its any& overload is unconstrained, so it binds an rvalue reference to the value held in an lvalue any - letting an overrider move out of an any the caller still owns - and its const any& overload fails inside Boost.Any rather than at the trait. Constrain cast with SFINAE in all three specializations, so the bad instantiations are removed from the overload set instead. Two compile_fail tests cover them; the diagnostic is the compiler's own overload resolution failure, whose wording varies, hence the loose fail_regex. Rename use_any_types to use_std_any_types, for symmetry with use_boost_any_types. One registrar cannot serve both: it names the any type twice, as the root class and as the synthetic base of the contained types, and that root must be the class the method registers for its virtual parameter. Boost.Any is not in the transitive closure of the library's declared dependencies, so declare it in the test Jamfile, and in CMakeLists.txt alongside Boost::smart_ptr - the mrdocs build compiles every header. Also document both any headers in ref_headers.adoc; std_any.hpp was missed when it landed. Co-Authored-By: Claude Opus 5 (1M context) --- CMakeLists.txt | 1 + doc/modules/ROOT/pages/ref_headers.adoc | 16 ++ .../boost/openmethod/interop/boost_any.hpp | 243 ++++++++++++++++++ include/boost/openmethod/interop/std_any.hpp | 22 +- test/CMakeLists.txt | 8 + test/Jamfile | 1 + ...ail_boost_any_const_ref_to_mutable_ref.cpp | 31 +++ ...il_boost_any_mutable_ref_to_rvalue_ref.cpp | 41 +++ test/test_dispatch_boost_any.cpp | 174 +++++++++++++ test/test_dispatch_std_any.cpp | 2 +- 10 files changed, 532 insertions(+), 7 deletions(-) create mode 100644 include/boost/openmethod/interop/boost_any.hpp create mode 100644 test/compile_fail_boost_any_const_ref_to_mutable_ref.cpp create mode 100644 test/compile_fail_boost_any_mutable_ref_to_rvalue_ref.cpp create mode 100644 test/test_dispatch_boost_any.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e246ba..193b3939 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/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 0c89651a..4089b03e 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -71,6 +71,22 @@ 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. +[#std_any] +### link:{{BASE_URL}}/include/boost/openmethod/interop/std_any.hpp[] + +Provides `virtual_traits` specializations that make it possible to use a `std::any` - +by const reference, by mutable reference, or by rvalue reference - in virtual +parameters. Dispatch is on the type of the contained value. Also provides +`use_std_any_types`, which registers the types that may be contained. + +[#boost_any] +### link:{{BASE_URL}}/include/boost/openmethod/interop/boost_any.hpp[] + +Provides `virtual_traits` specializations that make it possible to use a `boost::any` - +by const reference, by mutable reference, or by rvalue reference - in virtual +parameters. Dispatch is on the type of the contained value. Also provides +`use_boost_any_types`, which registers the types that may be contained. + *The headers below are for advanced use*. ## Pre-Core Headers diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp new file mode 100644 index 00000000..75f834c4 --- /dev/null +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -0,0 +1,243 @@ +// 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 + +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 {}; + +} // 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 + //! identify classes by `&typeid(T)`, as @ref std_rtti does; + //! `boost::any::type()` yields the same `std::type_info` object, provided + //! Boost.TypeIndex uses `stl_type_index`. + //! + //! 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 dynamic_vptr(const boost::any& arg) -> const vptr_type& { + return Registry::vptr::type_vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! 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) { + 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 + //! identify classes by `&typeid(T)`, as @ref std_rtti does; + //! `boost::any::type()` yields the same `std::type_info` object, provided + //! Boost.TypeIndex uses `stl_type_index`. + //! + //! 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 dynamic_vptr(const boost::any& arg) -> const vptr_type& { + return Registry::vptr::type_vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! 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) { + 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 + //! identify classes by `&typeid(T)`, as @ref std_rtti does; + //! `boost::any::type()` yields the same `std::type_info` object, provided + //! Boost.TypeIndex uses `stl_type_index`. + //! + //! 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 dynamic_vptr(const boost::any& arg) -> const vptr_type& { + return Registry::vptr::type_vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! 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) { + 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. +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>... {}; + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index b02df1a5..923e56c1 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -183,13 +183,23 @@ struct virtual_traits { } }; +//! 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. template -struct use_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>... {}; +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>... {}; } // namespace boost::openmethod diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f9f4524b..ed1f9f77 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -155,6 +155,14 @@ 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") if (TARGET Boost::dll) add_subdirectory(dynamic_loading) diff --git a/test/Jamfile b/test/Jamfile index a1c69c4e..10ab8c56 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -20,6 +20,7 @@ project cxx17_structured_bindings ] /boost/openmethod//boost_openmethod + /boost/any//boost_any extra 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/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp new file mode 100644 index 00000000..ede5d029 --- /dev/null +++ b/test/test_dispatch_boost_any.cpp @@ -0,0 +1,174 @@ +// 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_dynamic_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_dynamic_vptr< + virtual_traits, 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_dynamic_vptr< + virtual_traits, 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 diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index f746dbe1..0bc1c12d 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -20,7 +20,7 @@ using namespace boost::openmethod; std::string name; \ }; \ \ - use_any_types BOOST_OPENMETHOD_GENSYM; + use_std_any_types BOOST_OPENMETHOD_GENSYM; namespace BOOST_OPENMETHOD_GENSYM { From b28438451d47584a11117cbf3f136740bc7c3d05 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 11:35:23 -0400 Subject: [PATCH 15/85] doc: include compiled snippets in reference doc comments A paragraph of a doc comment reading include:[#[;...]] is now replaced by the file, or its `// tag::name[]` regions, rendered as a code block. The point is that the example on a reference page is a region of a file the build compiles and runs, so it cannot drift from the library without a build failing. Implemented as a MrDocs corpus transform in Lua, per Alan de Freitas' suggestion. Regions are selected in file order the way Asciidoctor's `tags=` attribute selects them, and each contiguous run is dedented on its own before the runs are joined, so a snippet can draw its setup from namespace scope and its body from inside a test case and still render flush. A missing file or unknown tag aborts the build naming both. The transform rebuilds a comment's whole block list rather than patching the marker in place, because three gaps in the 0.8.0 extension API leave no alternative: array proxies expose no indexed write to Lua, proxies read out of the corpus are rejected as setter input, and `level` is refused by the generic setter. The header comment records all three; if they are fixed upstream the script collapses to a few lines. Snippets live in doc/modules/ROOT/snippets, which Antora ignores as an unrecognised family, and are built and run with the examples -- Boost keeps tests under test/. Converts two `virtual_ptr` examples, both of which were broken: - `operator=(std::nullptr_t)` had a mangled opening fence written after the body, so the example rendered as escaped prose run onto one line, trailed by a stray `//!` and an empty code block. - `cast()` had an empty `@code`/`@endcode` pair. Generating the reference over the whole corpus produces byte-identical output apart from those two pages, so the block-list rebuild is lossless. --- CMakeLists.txt | 2 + doc/modules/ROOT/snippets/CMakeLists.txt | 29 +++ doc/modules/ROOT/snippets/virtual_ptr.cpp | 112 ++++++++++ doc/mrdocs-addons/extensions/include.lua | 237 ++++++++++++++++++++++ doc/mrdocs.yml | 12 +- include/boost/openmethod/core.hpp | 21 +- 6 files changed, 395 insertions(+), 18 deletions(-) create mode 100644 doc/modules/ROOT/snippets/CMakeLists.txt create mode 100644 doc/modules/ROOT/snippets/virtual_ptr.cpp create mode 100644 doc/mrdocs-addons/extensions/include.lua diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e246ba..cdd278df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,5 +219,7 @@ if (BOOST_OPENMETHOD_BUILD_TESTS) # Examples if (BOOST_OPENMETHOD_BUILD_EXAMPLES) add_subdirectory(doc/modules/ROOT/examples) + # Sources behind the `include:` markers in the reference doc comments. + add_subdirectory(doc/modules/ROOT/snippets) endif () endif () diff --git a/doc/modules/ROOT/snippets/CMakeLists.txt b/doc/modules/ROOT/snippets/CMakeLists.txt new file mode 100644 index 00000000..f3558474 --- /dev/null +++ b/doc/modules/ROOT/snippets/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) 2018-2025 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) + +# Sources for the `include:` markers in the reference doc comments; see +# doc/mrdocs-addons/extensions/include.lua. Built and run alongside the +# examples, which is the whole point: a reference example cannot drift from the +# library without this failing. +# +# Targets carry a `snippet_` prefix because a snippet and an example may share +# a stem -- both directories have a virtual_ptr.cpp. + +message(STATUS "Boost.OpenMethod: building documentation snippets") + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions(BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS) +endif() + +file(GLOB cpp_files "*.cpp") + +foreach (cpp ${cpp_files}) + get_filename_component(stem ${cpp} NAME_WE) + set(test_target "boost_openmethod-snippet_${stem}") + add_executable(${test_target} ${cpp}) + target_link_libraries(${test_target} PRIVATE Boost::openmethod Boost::unit_test_framework) + add_test(NAME ${test_target} COMMAND ${test_target}) + add_dependencies(tests ${test_target}) +endforeach() diff --git a/doc/modules/ROOT/snippets/virtual_ptr.cpp b/doc/modules/ROOT/snippets/virtual_ptr.cpp new file mode 100644 index 00000000..827a784c --- /dev/null +++ b/doc/modules/ROOT/snippets/virtual_ptr.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +namespace polymorphic_classes { + +// tag::polymorphic_classes[] +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +// end::polymorphic_classes[] + +BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace polymorphic_classes + +namespace non_polymorphic_classes { + +// tag::non_polymorphic_classes[] +// polymorphism not required +struct Animal {}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); +// end::non_polymorphic_classes[] + +BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace non_polymorphic_classes + +BOOST_AUTO_TEST_CASE(virtual_ptr_examples) { + // tag::initialize[] + initialize(); + // end::initialize[] + + { + using namespace non_polymorphic_classes; + poke(make_unique_virtual()); // for coverage + } + + { + using namespace polymorphic_classes; + // tag::assign_nullptr[] + Dog snoopy; + virtual_ptr p(snoopy); + + p = nullptr; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::assign_nullptr[] + } + + { + using namespace polymorphic_classes; + + // tag::cast[] + Dog snoopy; + virtual_ptr animal(snoopy); + + auto dog = animal.cast(); + + BOOST_TEST(dog.get() == &snoopy); + BOOST_TEST(dog.vptr() == animal.vptr()); + // end::cast[] + } + + { + using namespace non_polymorphic_classes; + + // tag::final_virtual_ptr[] + Dog snoopy; + virtual_ptr animal = final_virtual_ptr(snoopy); + BOOST_TEST(poke(animal) == "bark"); + + Cat felix; + animal = final_virtual_ptr(felix); + BOOST_TEST(poke(animal) == "hiss"); + // end::final_virtual_ptr[] + } +} diff --git a/doc/mrdocs-addons/extensions/include.lua b/doc/mrdocs-addons/extensions/include.lua new file mode 100644 index 00000000..dea625e2 --- /dev/null +++ b/doc/mrdocs-addons/extensions/include.lua @@ -0,0 +1,237 @@ +-- Substitute a marker in a doc comment with the contents of a file. +-- +-- A paragraph whose entire text is +-- +-- include:[#[;...]] +-- +-- is replaced by a code block holding , or the named `// tag::name[]` +-- regions of it. The path is relative to `transform-options.include.root`, +-- itself relative to the directory holding this mrdocs.yml. Typical use: +-- +-- //! @par Example +-- //! include:virtual_ptr.cpp#setup;assign_nullptr +-- +-- The point is that the rendered snippet is a region of a file the build +-- compiles and runs, so a reference example cannot drift from the library. +-- +-- Regions are selected in file order, the way Asciidoctor's `tags=` attribute +-- selects them, and each contiguous run is dedented on its own before the runs +-- are joined by a blank line. Per-run dedent is what lets a snippet draw its +-- setup from namespace scope and its body from inside a test case and still +-- render flush. +-- +-- Why the whole block list is rebuilt rather than the marker patched in place: +-- three gaps in the 0.8.0 extension API, reported at +-- https://cpplang.slack.com/archives/C0508A7LWUV/p1785455605224149 +-- +-- * `doc.document[i] = block` fails with "attempt to index a userdata value" +-- -- the Lua binding exposes no __newindex for array proxies, so +-- DescribedArrayProxy::set is unreachable from a script. +-- * A proxy read out of the corpus is rejected as setter input ("expects an +-- object describing a polymorphic value"), so blocks cannot be handed back +-- verbatim; they have to be deep-copied into plain tables. +-- * `level` is refused by the generic setter, hence UNWRITABLE below. +-- +-- If those are fixed upstream this whole file collapses to a few lines. + +-- Fields the generic setter cannot write. `level` is a heading's depth; MrDocs +-- does not parse markdown `##` headings in doc comments, so heading blocks only +-- ever come from `@par` at level 1 -- which is the default -- and dropping it +-- round-trips. +local UNWRITABLE = { level = true } + +local function dirname(path) + return path:match("^(.*)/[^/]*$") or "." +end + +local function is_array(value) + local ok, n = pcall(function() + return #value + end) + return ok and n and n > 0 +end + +local function copy(value) + if type(value) ~= "userdata" then + return value + end + + if is_array(value) then + local out = {} + for i = 1, #value do + out[i] = copy(value[i]) + end + return out + end + + local out, any = {}, false + for key, field in pairs(value) do + any = true + if not UNWRITABLE[key] then + out[key] = copy(field) + end + end + + -- An empty proxy is an absent optional, not an empty object. + if not any then + return nil + end + + return out +end + +-- Drop the common indentation of `lines`, then join them. +local function dedent(lines) + local indent + + for _, line in ipairs(lines) do + local lead = line:match("^([ \t]*)%S") + if lead and (not indent or #lead < #indent) then + indent = lead + end + end + + if indent and #indent > 0 then + for i, line in ipairs(lines) do + lines[i] = line:sub(#indent + 1) + end + end + + return (table.concat(lines, "\n"):gsub("%s+$", "")) +end + +-- Return the regions of `text` covered by `tags`, in file order, or the whole +-- text when `tags` is nil. The second result lists the tags that never opened. +local function select_regions(text, tags) + if not tags then + return (text:gsub("%s+$", "")), {} + end + + local wanted, found = {}, {} + for _, tag in ipairs(tags) do + wanted[tag] = true + end + + local regions, current, depth = {}, nil, 0 + + local function flush() + if current then + regions[#regions + 1] = dedent(current) + current = nil + end + end + + for line in (text .. "\n"):gmatch("([^\n]*)\n") do + local opens = line:match("tag::([%w_%-%.]+)%[%]") + local closes = line:match("end::([%w_%-%.]+)%[%]") + + if opens then + if wanted[opens] then + found[opens] = true + depth = depth + 1 + end + elseif closes then + if wanted[closes] then + depth = depth - 1 + if depth == 0 then + flush() + end + end + elseif depth > 0 then + current = current or {} + current[#current + 1] = line + end + end + + flush() + + local missing = {} + for _, tag in ipairs(tags) do + if not found[tag] then + missing[#missing + 1] = tag + end + end + + return table.concat(regions, "\n\n"), missing +end + +local function read_file(path) + local file = io.open(path, "r") + if not file then + return nil + end + local text = file:read("*a") + file:close() + return text +end + +-- `include:` or `include:#[;...]`, alone in a paragraph. +local function parse_marker(block) + if block.kind ~= "paragraph" then + return nil + end + + local inlines = block.children + if not inlines or #inlines ~= 1 or inlines[1].kind ~= "text" then + return nil + end + + local spec = inlines[1].literal:match("^include:(%S+)$") + if not spec then + return nil + end + + local path, tail = spec:match("^([^#]+)#(.+)$") + if not path then + return spec, nil + end + + local tags = {} + for tag in tail:gmatch("[^;]+") do + tags[#tags + 1] = tag + end + + return path, tags +end + +mrdocs.register_transform("include", function(ctx) + local root = dirname(ctx.config.config) .. "/" .. (ctx.params.root or ".") + local lang = ctx.params.lang or "cpp" + + for _, symbol in ipairs(ctx.corpus.symbols) do + local document = symbol.doc and symbol.doc.document + + if document and #document > 0 then + local blocks, substituted = {}, false + + for i = 1, #document do + local block = document[i] + local path, tags = parse_marker(block) + + if path then + local full = root .. "/" .. path + local text = read_file(full) + if not text then + error("include: cannot read " .. full) + end + + local body, missing = select_regions(text, tags) + if #missing > 0 then + error( + "include: no tag " .. table.concat(missing, ", ") + .. " in " .. full) + end + + blocks[i] = { kind = "code", literal = body, info = lang } + substituted = true + else + blocks[i] = copy(block) + end + end + + if substituted then + symbol.doc.document = blocks + end + end + end +end) diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index c77a80ab..afaf61f9 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -45,11 +45,19 @@ inherit-base-members: never private-bases: false auto-function-metadata: false -# Template overrides, layered on top of the built-in addons. See the header -# comment in the overridden file for what it changes and why. +# Template overrides and extension scripts, layered on top of the built-in +# addons. See the header comment in each file for what it does and why. addons-supplemental: - mrdocs-addons +# `include:[#[;...]]` alone in a paragraph of a doc comment is +# replaced by the file, or its `// tag::name[]` regions, as a code block. See +# mrdocs-addons/extensions/include.lua. `root` is relative to this file. +transform-options: + include: + root: modules/ROOT/snippets + lang: cpp + # Generator generate: adoc base-url: https://www.github.com/boostorg/openmethod/blob/master/ diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 48b6627d..88da80b6 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -608,6 +608,9 @@ inline vptr_type null_vptr = nullptr; //! @li @ref final_error The static and dynamic types of the object are //! different. //! +//! @par Example +//! include:virtual_ptr.cpp#non_polymorphic_classes;final_virtual_ptr +//! //! @tparam Registry A @ref registry. //! @tparam Arg The type of the argument. //! @param obj A reference to an object. @@ -1101,20 +1104,7 @@ class virtual_ptr { //! Set both object and v-table pointers to `nullptr`. //! //! @par Example - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! virtual_ptr p = final_virtual_ptr(snoopy); - //! - //! p = nullptr; - //! - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! //! @code - //! @endcode + //! include:virtual_ptr.cpp#assign_nullptr virtual_ptr& operator=(std::nullptr_t) { obj = nullptr; vp = detail::box_vptr(detail::null_vptr); @@ -1152,8 +1142,7 @@ class virtual_ptr { //! Cast to another `virtual_ptr` type //! //! @par Example - //! @code - //! @endcode + //! include:virtual_ptr.cpp#cast //! //! @tparam Other The target class of the cast //! @return A `virtual_ptr` pointing to the same object From 80e8157cc193878ffbcea6857a05617a005dd259 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 13:23:03 -0400 Subject: [PATCH 16/85] doc: convert the virtual_ptr examples to include: markers Replaces the 24 remaining `@code` blocks in core.hpp with markers into doc/modules/ROOT/snippets/virtual_ptr.cpp, which the build compiles and runs. core.hpp now has no `@code` left. A marker names a class-setup tag only where the example deviates from the norm -- that is, where it relies on non-polymorphic classes, which is the point of those examples. The other markers render the body alone, since repeating four lines of Animal/Dog on every page is noise. Deletes test/test_virtual_ptr_doc.cpp. Its only purpose was to compile these examples a second time, by hand, with nothing keeping the two copies in step; the snippets do that job now. Both build systems glob, so no build file changes. Note that b2 builds only test/, so it no longer compiles these examples at all -- adding a Jamfile for the snippets would route them back through the test build we just moved them out of. Two examples were attached to the wrong overload: the assignment operators briefed "from a (const) smart pointer" and "move-assign from a smart pointer" both showed assigning from a *virtual* pointer, which is what the following two overloads document, with near-identical bodies. They get bodies that assign from a bare std::shared_ptr, matching their briefs. Being compiled from now on, they cannot drift again. A third malformed block is fixed by the conversion: the smart-pointer default constructor had a stray `@par Example` inside its `@code` fence. Generating the reference produces exactly 20 changed pages -- 24 blocks less the four that share a doc comment with another -- and no other difference across the 353 generated files. --- doc/modules/ROOT/snippets/virtual_ptr.cpp | 311 ++++++++++++++++++++ include/boost/openmethod/core.hpp | 330 ++-------------------- test/test_virtual_ptr_doc.cpp | 254 ----------------- 3 files changed, 335 insertions(+), 560 deletions(-) delete mode 100644 test/test_virtual_ptr_doc.cpp diff --git a/doc/modules/ROOT/snippets/virtual_ptr.cpp b/doc/modules/ROOT/snippets/virtual_ptr.cpp index 827a784c..4f03a3d1 100644 --- a/doc/modules/ROOT/snippets/virtual_ptr.cpp +++ b/doc/modules/ROOT/snippets/virtual_ptr.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #define BOOST_TEST_MODULE openmethod @@ -69,6 +70,141 @@ BOOST_AUTO_TEST_CASE(virtual_ptr_examples) { poke(make_unique_virtual()); // for coverage } + { + using namespace polymorphic_classes; + // tag::ctor_nullptr[] + virtual_ptr p{nullptr}; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::ctor_nullptr[] + } + + { + using namespace polymorphic_classes; + // tag::ctor_ref[] + Dog snoopy; + Animal& animal = snoopy; + + virtual_ptr p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_ref[] + } + + { + using namespace polymorphic_classes; + // tag::ctor_pointer[] + Dog snoopy; + Animal* animal = &snoopy; + + virtual_ptr p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_pointer[] + } + + { + using namespace non_polymorphic_classes; + // tag::ctor_vptr[] + Dog snoopy; + virtual_ptr dog = final_virtual_ptr(snoopy); + + virtual_ptr p = dog; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::ctor_shared_vptr[] + virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_shared_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::ctor_shared_from_plain_rejected[] + static_assert( + std::is_constructible_v< + shared_virtual_ptr, virtual_ptr> == false); + // end::ctor_shared_from_plain_rejected[] + } + + { + using namespace polymorphic_classes; + // tag::assign_ref[] + virtual_ptr p{nullptr}; + Dog snoopy; + Animal& animal = snoopy; + + p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_ref[] + } + + { + using namespace polymorphic_classes; + // tag::assign_pointer[] + virtual_ptr p{nullptr}; + Dog snoopy; + Animal* animal = &snoopy; + + p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_pointer[] + } + + { + using namespace non_polymorphic_classes; + // tag::assign_vptr[] + Dog snoopy; + virtual_ptr dog = final_virtual_ptr(snoopy); + virtual_ptr p{nullptr}; + + p = dog; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::assign_shared_vptr[] + virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr p; + + p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_shared_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::assign_shared_from_plain_rejected[] + static_assert( + std::is_assignable_v< + shared_virtual_ptr&, virtual_ptr> == false); + // end::assign_shared_from_plain_rejected[] + } + { using namespace polymorphic_classes; // tag::assign_nullptr[] @@ -110,3 +246,178 @@ BOOST_AUTO_TEST_CASE(virtual_ptr_examples) { // end::final_virtual_ptr[] } } + +BOOST_AUTO_TEST_CASE(shared_virtual_ptr_examples) { + initialize(); + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_default[] + virtual_ptr> p; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::shared_ctor_default[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_nullptr[] + virtual_ptr> p{nullptr}; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::shared_ctor_nullptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_ctor_const_smart_ptr[] + const std::shared_ptr snoopy = std::make_shared(); + virtual_ptr> p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_ctor_const_smart_ptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_ctor_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + virtual_ptr> p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_ctor_smart_ptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_ctor_move_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + Dog* moving = snoopy.get(); + + virtual_ptr> p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::shared_ctor_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_const_vptr[] + const virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr> p = snoopy; + + BOOST_TEST(snoopy.get() != nullptr); + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_ctor_const_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_move_vptr[] + virtual_ptr> snoopy = make_shared_virtual(); + Dog* dog = snoopy.get(); + + virtual_ptr> p = std::move(snoopy); + + BOOST_TEST(p.get() == dog); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::shared_ctor_move_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_nullptr[] + virtual_ptr> p = make_shared_virtual(); + + p = nullptr; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + BOOST_TEST((p == virtual_ptr>())); + // end::shared_assign_nullptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_assign_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + virtual_ptr> p; + + p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_assign_smart_ptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_assign_move_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + Dog* moving = snoopy.get(); + virtual_ptr> p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::shared_assign_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_vptr[] + virtual_ptr> snoopy = make_shared_virtual(); + virtual_ptr> p; + + p = snoopy; + + BOOST_TEST(p.get() != nullptr); + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); + // end::shared_assign_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_const_vptr[] + const virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr> p; + + p = snoopy; + + BOOST_TEST(p.get() != nullptr); + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); + // end::shared_assign_const_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_move_vptr[] + virtual_ptr> snoopy = make_shared_virtual(); + Dog* moving = snoopy.get(); + virtual_ptr> p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + BOOST_TEST(snoopy.vptr() == nullptr); + // end::shared_assign_move_vptr[] + } +} diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 88da80b6..2d3a8b23 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -756,16 +756,7 @@ class virtual_ptr { //! //! @par Example //! - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr p{nullptr}; - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#ctor_nullptr //! //! @param value A `nullptr`. explicit virtual_ptr(std::nullptr_t) @@ -783,20 +774,7 @@ class virtual_ptr { //! @param other A reference to a polymorphic object //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! Animal& animal = snoopy; - //! - //! virtual_ptr p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#ctor_ref //! //! @par Requirements //! @li @c Other must be a polymorphic class, according to the @c rtti @@ -829,20 +807,7 @@ class virtual_ptr { //! `vptr` policy otherwise. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! Animal* animal = &snoopy; - //! - //! virtual_ptr p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#ctor_pointer //! //! @param other A pointer to a polymorphic object //! @@ -881,44 +846,15 @@ class virtual_ptr { //! //! Assigning from a plain virtual_ptr: //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! virtual_ptr dog = final_virtual_ptr(snoopy); - //! virtual_ptr p{nullptr}; - //! - //! p = dog; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;ctor_vptr //! //! Assigning from a smart virtual_ptr: //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr p = snoopy; - //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;ctor_shared_vptr //! //! No construction of a smart `virtual_ptr` from a plain `virtual_ptr`: //! - //! @code - //! static_assert( - //! std::is_constructible_v< - //! shared_virtual_ptr, virtual_ptr> == false); - //! @endcode + //! include:virtual_ptr.cpp#ctor_shared_from_plain_rejected //! //! @param other A virtual_ptr to a type-compatible object //! @@ -940,21 +876,7 @@ class virtual_ptr { //! `vptr` policy otherwise. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr p{nullptr}; - //! Dog snoopy; - //! Animal& animal = snoopy; - //! - //! p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#assign_ref //! //! @param other A reference to a polymorphic object //! @@ -992,21 +914,7 @@ class virtual_ptr { //! `vptr` policy otherwise. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr p{nullptr}; - //! Dog snoopy; - //! Animal* animal = &snoopy; - //! - //! p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#assign_pointer //! //! @param other A pointer to a polymorphic object //! @@ -1044,46 +952,15 @@ class virtual_ptr { //! //! Assigning from a plain virtual_ptr: //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! virtual_ptr dog = final_virtual_ptr(snoopy); - //! virtual_ptr p{nullptr}; - //! - //! p = dog; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;assign_vptr //! //! Assigning from a smart virtual_ptr: //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr p; - //! - //! p = snoopy; - //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;assign_shared_vptr //! //! No assignment from a plain `virtual_ptr` to a smart `virtual_ptr`: //! - //! @code - //! static_assert( - //! std::is_assignable_v< - //! shared_virtual_ptr&, virtual_ptr> == false); - //! @endcode + //! include:virtual_ptr.cpp#assign_shared_from_plain_rejected //! //! @param other A virtual_ptr to a type-compatible object //! @@ -1229,16 +1106,7 @@ class virtual_ptr< //! v-table pointer to `nullptr`. //! //! @par Example - //! @code - //! struct Dog {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Dog); - //! initialize(); - //! - //! virtual_ptr> p; - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! @par Example - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_default virtual_ptr() : vp(detail::box_vptr(detail::null_vptr)) { } @@ -1249,15 +1117,7 @@ class virtual_ptr< //! v-table pointer to `nullptr`. //! //! @par Example - //! @code - //! struct Dog {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Dog); - //! initialize(); - //! - //! virtual_ptr> p{nullptr}; - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_nullptr //! //! @param value A `nullptr`. explicit virtual_ptr(std::nullptr_t) @@ -1279,18 +1139,7 @@ class virtual_ptr< //! according to the dynamic type of `*other`. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! const std::shared_ptr snoopy = std::make_shared(); - //! virtual_ptr> p = snoopy; - //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_ctor_const_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1326,18 +1175,7 @@ class virtual_ptr< //! according to the dynamic type of `*other`. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! std::shared_ptr snoopy = std::make_shared(); - //! virtual_ptr> p = snoopy; - //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_ctor_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1373,21 +1211,7 @@ class virtual_ptr< //! according to the dynamic type of `*other`. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! std::shared_ptr snoopy = std::make_shared(); - //! Dog* moving = snoopy.get(); - //! - //! virtual_ptr> p = std::move(snoopy); - //! - //! BOOST_TEST(p.get() == moving); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_ctor_move_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1423,19 +1247,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! const virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p = snoopy; - //! - //! BOOST_TEST(snoopy.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_const_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1461,21 +1273,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! Dog* dog = snoopy.get(); - //! - //! virtual_ptr> p = std::move(snoopy); - //! - //! BOOST_TEST(p.get() == dog); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_move_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1502,19 +1300,7 @@ class virtual_ptr< //! v-table pointer to `nullptr`. //! //! @par Example - //! @code - //! struct Dog {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Dog); - //! initialize(); - //! - //! virtual_ptr> p = make_shared_virtual(); - //! - //! p = nullptr; - //! - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! BOOST_TEST((p == virtual_ptr>())); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_nullptr //! //! @param value A `nullptr`. virtual_ptr& operator=(std::nullptr_t) { @@ -1529,17 +1315,7 @@ class virtual_ptr< //! according to the dynamic type of `*other`. //! //! @par Example - //! @code - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p; - //! - //! p = snoopy; - //! - //! BOOST_TEST(p.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_assign_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1568,18 +1344,7 @@ class virtual_ptr< //! according to the dynamic type of `*other`. //! //! @par Example - //! @code - //! virtual_ptr> snoopy = make_shared_virtual(); - //! Dog* moving = snoopy.get(); - //! virtual_ptr> p; - //! - //! p = std::move(snoopy); - //! - //! BOOST_TEST(p.get() == moving); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! BOOST_TEST(snoopy.vptr() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_assign_move_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1609,22 +1374,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p; - //! - //! p = snoopy; - //! - //! BOOST_TEST(p.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1653,22 +1403,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! const virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p; - //! - //! p = snoopy; - //! - //! BOOST_TEST(p.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_const_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1696,24 +1431,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr> snoopy = - //! make_shared_virtual(); - //! Dog* moving = snoopy.get(); - //! virtual_ptr> p; - //! - //! p = std::move(snoopy); - //! - //! BOOST_TEST(p.get() == moving); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! BOOST_TEST(snoopy.vptr() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_move_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - diff --git a/test/test_virtual_ptr_doc.cpp b/test/test_virtual_ptr_doc.cpp deleted file mode 100644 index dbd95fb0..00000000 --- a/test/test_virtual_ptr_doc.cpp +++ /dev/null @@ -1,254 +0,0 @@ -// qright (c) 2018-2025 Jean-Louis Leroy -// Distributed under the Boost Software License, Version 1.0. -// See accompanying file LICENSE_1_0.txt -// or q at http://www.boost.org/LICENSE_1_0.txt) - -#include -#include -#include - -#define BOOST_TEST_MODULE openmethod -#include - -using namespace boost::openmethod; - -namespace polymorphic { - -struct Animal { - virtual ~Animal() { - } -}; -struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); -BOOST_OPENMETHOD(poke, (virtual_ptr), void); - -void instiantiate_poke(virtual_ptr snoopy) { - poke(snoopy); -} - -BOOST_AUTO_TEST_CASE(virtual_ptr_examples_polymorphic) { - { - initialize(trace()); - - { - virtual_ptr p{nullptr}; - - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - - { - Dog snoopy; - Animal& animal = snoopy; - - virtual_ptr p = animal; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - Dog snoopy; - Animal* animal = &snoopy; - - virtual_ptr p = animal; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr p{nullptr}; - Dog snoopy; - Animal* animal = &snoopy; - - p = animal; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - Dog snoopy; - virtual_ptr p = final_virtual_ptr(snoopy); - - p = nullptr; - - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - } -} - -BOOST_AUTO_TEST_CASE(smart_virtual_ptr_examples) { - initialize(); - - { - virtual_ptr> p; - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - - { - virtual_ptr> p{nullptr}; - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - - { - const std::shared_ptr snoopy = std::make_shared(); - virtual_ptr> p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - std::shared_ptr snoopy = std::make_shared(); - virtual_ptr> p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - std::shared_ptr snoopy = std::make_shared(); - Dog* moving = snoopy.get(); - - virtual_ptr> p = std::move(snoopy); - - // coverity[use_after_move] - BOOST_TEST(p.get() == moving); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.get() == nullptr); - } - { - const virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr> p = std::move(snoopy); - - // coverity[use_after_move] - BOOST_TEST(snoopy.get() != nullptr); - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr> snoopy = make_shared_virtual(); - Dog* moving = snoopy.get(); - - virtual_ptr> p = std::move(snoopy); - - BOOST_TEST(p.get() == moving); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.get() == nullptr); - BOOST_TEST(snoopy.vptr() == nullptr); - } - - { - virtual_ptr> p = make_shared_virtual(); - - p = nullptr; - - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - BOOST_TEST((p == virtual_ptr>())); - } - - { - const virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr> p; - - p = snoopy; - - BOOST_TEST(p.get() != nullptr); - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr> snoopy = make_shared_virtual(); - Dog* moving = snoopy.get(); - virtual_ptr> p; - - p = std::move(snoopy); - - BOOST_TEST(p.get() == moving); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.get() == nullptr); - BOOST_TEST(snoopy.vptr() == nullptr); - } -} -} // namespace polymorphic - -namespace non_polymorphic { - -struct Animal {}; // polymorphic not required -struct Dog : Animal {}; // polymorphic not required -BOOST_OPENMETHOD_CLASSES(Animal, Dog); - -// codecov:ignore:start -BOOST_OPENMETHOD(poke, (virtual_ptr), void); - -void instantiate_poke(virtual_ptr snoopy) { - poke(snoopy); -} -// codecov:ignore:end - -BOOST_AUTO_TEST_CASE(virtual_ptr_examples_non_polymorphic) { - { - initialize(); - - { - Dog snoopy; - virtual_ptr dog = final_virtual_ptr(snoopy); - - virtual_ptr p(dog); - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - Dog snoopy; - virtual_ptr dog = final_virtual_ptr(snoopy); - virtual_ptr p{nullptr}; - - p = dog; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - static_assert( - std::is_constructible_v< - shared_virtual_ptr, virtual_ptr> == false); - - { - virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr p; - - p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - static_assert( - std::is_assignable_v< - shared_virtual_ptr&, virtual_ptr> == false); - } -} -} // namespace non_polymorphic From 2efb550676c60aab065fc08b2c1265d967182fc7 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 13:25:39 -0400 Subject: [PATCH 17/85] doc: fix the prose around the virtual_ptr multi-example blocks The constructor from another `virtual_ptr` introduced its examples with "Assigning from...", though it documents a constructor. Both that comment and the assignment one prepended the non-polymorphic class setup to two consecutive examples, so the same five lines rendered twice on one page. Only the first names the setup tag now. Also backticks `virtual_ptr` in those lines: unformatted, it rendered as escaped text beside the correctly formatted mention in the third paragraph of the same section. --- include/boost/openmethod/core.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 2d3a8b23..edf31d9d 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -844,13 +844,13 @@ class virtual_ptr { //! //! @par Examples //! - //! Assigning from a plain virtual_ptr: + //! Constructing from a plain `virtual_ptr`: //! //! include:virtual_ptr.cpp#non_polymorphic_classes;ctor_vptr //! - //! Assigning from a smart virtual_ptr: + //! Constructing from a smart `virtual_ptr`: //! - //! include:virtual_ptr.cpp#non_polymorphic_classes;ctor_shared_vptr + //! include:virtual_ptr.cpp#ctor_shared_vptr //! //! No construction of a smart `virtual_ptr` from a plain `virtual_ptr`: //! @@ -950,13 +950,13 @@ class virtual_ptr { //! //! @par Examples //! - //! Assigning from a plain virtual_ptr: + //! Assigning from a plain `virtual_ptr`: //! //! include:virtual_ptr.cpp#non_polymorphic_classes;assign_vptr //! - //! Assigning from a smart virtual_ptr: + //! Assigning from a smart `virtual_ptr`: //! - //! include:virtual_ptr.cpp#non_polymorphic_classes;assign_shared_vptr + //! include:virtual_ptr.cpp#assign_shared_vptr //! //! No assignment from a plain `virtual_ptr` to a smart `virtual_ptr`: //! From bfcb4de7189ad731bf6529cc07b507cfcc03225f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 13:36:27 -0400 Subject: [PATCH 18/85] doc: examples for unique_ptr and for the smart-pointer interop headers Two gaps: the 13 examples on the `virtual_ptr` specialization were all `std::shared_ptr`, and the three interop headers had no examples at all -- `unique_virtual_ptr`, `make_unique_virtual` and the `virtual_traits` specializations were documented in prose only. Adds unique_ptr examples to the four move overloads of `virtual_ptr`, alongside the shared_ptr ones, plus a static_assert on the copy constructor recording that a move-only smart pointer cannot be copied from. Those five are where the two pointer flavours actually diverge; copying an example that only differs in the pointer type would not earn its place on the page. Adds snippets/smart_pointers.cpp and snippets/intrusive_ptr.cpp, and markers on all three interop headers: each `virtual_traits` specialization now shows a method declared with that smart pointer as a virtual parameter, and each alias and factory shows a use. The by-reference specializations show what the by-value ones cannot -- that passing by const reference does not bump the reference count. Grouped two files rather than three: shared_ptr and unique_ptr share a class hierarchy, while intrusive_ptr needs an intrusive_ref_counter base. 16 reference pages change, and no others. --- doc/modules/ROOT/snippets/intrusive_ptr.cpp | 104 +++++++++++++ doc/modules/ROOT/snippets/smart_pointers.cpp | 143 ++++++++++++++++++ doc/modules/ROOT/snippets/virtual_ptr.cpp | 73 +++++++++ include/boost/openmethod/core.hpp | 46 +++++- .../interop/boost_intrusive_ptr.hpp | 12 ++ .../openmethod/interop/std_shared_ptr.hpp | 12 ++ .../openmethod/interop/std_unique_ptr.hpp | 9 ++ 7 files changed, 394 insertions(+), 5 deletions(-) create mode 100644 doc/modules/ROOT/snippets/intrusive_ptr.cpp create mode 100644 doc/modules/ROOT/snippets/smart_pointers.cpp diff --git a/doc/modules/ROOT/snippets/intrusive_ptr.cpp b/doc/modules/ROOT/snippets/intrusive_ptr.cpp new file mode 100644 index 00000000..f35adc6b --- /dev/null +++ b/doc/modules/ROOT/snippets/intrusive_ptr.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2018-2025 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; + +// tag::classes[] +struct Animal : boost::intrusive_ref_counter { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +// end::classes[] + +namespace by_value { + +// tag::by_value[] +BOOST_OPENMETHOD(poke, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost::intrusive_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost::intrusive_ptr animal), std::string) { + return "hiss"; +} +// end::by_value[] + +} // namespace by_value + +namespace by_reference { + +// tag::by_reference[] +BOOST_OPENMETHOD( + poke, (virtual_&>), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const boost::intrusive_ptr& animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const boost::intrusive_ptr& animal), std::string) { + return "hiss"; +} +// end::by_reference[] + +} // namespace by_reference + +BOOST_AUTO_TEST_CASE(intrusive_ptr_examples) { + initialize(); + + { + // tag::make_boost_intrusive_virtual[] + boost_intrusive_virtual_ptr animal = + make_boost_intrusive_virtual(); + + BOOST_TEST(animal.vptr() == default_registry::static_vptr); + // end::make_boost_intrusive_virtual[] + } + + { + // tag::boost_intrusive_virtual_ptr_alias[] + boost_intrusive_virtual_ptr animal = + make_boost_intrusive_virtual(); + boost::intrusive_ptr owner = animal.pointer(); + + BOOST_TEST(owner->use_count() == 2); + // end::boost_intrusive_virtual_ptr_alias[] + } + + { + using namespace by_value; + // tag::by_value_call[] + BOOST_TEST(poke(boost::intrusive_ptr(new Dog)) == "bark"); + BOOST_TEST(poke(boost::intrusive_ptr(new Cat)) == "hiss"); + // end::by_value_call[] + } + + { + using namespace by_reference; + // tag::by_reference_call[] + const boost::intrusive_ptr snoopy(new Dog); + + BOOST_TEST(poke(snoopy) == "bark"); + BOOST_TEST(snoopy->use_count() == 1); + // end::by_reference_call[] + } +} diff --git a/doc/modules/ROOT/snippets/smart_pointers.cpp b/doc/modules/ROOT/snippets/smart_pointers.cpp new file mode 100644 index 00000000..86041dd2 --- /dev/null +++ b/doc/modules/ROOT/snippets/smart_pointers.cpp @@ -0,0 +1,143 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +// tag::classes[] +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +// end::classes[] + +namespace by_value { + +// tag::shared_by_value[] +BOOST_OPENMETHOD(poke, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::shared_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::shared_ptr animal), std::string) { + return "hiss"; +} +// end::shared_by_value[] + +} // namespace by_value + +namespace by_reference { + +// tag::shared_by_reference[] +BOOST_OPENMETHOD(poke, (virtual_&>), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const std::shared_ptr& animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const std::shared_ptr& animal), std::string) { + return "hiss"; +} +// end::shared_by_reference[] + +} // namespace by_reference + +namespace unique { + +// tag::unique_by_value[] +BOOST_OPENMETHOD(poke, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::unique_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::unique_ptr animal), std::string) { + return "hiss"; +} +// end::unique_by_value[] + +} // namespace unique + +BOOST_AUTO_TEST_CASE(shared_ptr_examples) { + initialize(); + + { + // tag::make_shared_virtual[] + shared_virtual_ptr animal = make_shared_virtual(); + + BOOST_TEST(animal.vptr() == default_registry::static_vptr); + // end::make_shared_virtual[] + } + + { + // tag::shared_virtual_ptr_alias[] + shared_virtual_ptr animal = make_shared_virtual(); + std::shared_ptr owner = animal.pointer(); + + BOOST_TEST(owner.use_count() == 2); + // end::shared_virtual_ptr_alias[] + } + + { + using namespace by_value; + // tag::shared_by_value_call[] + BOOST_TEST(poke(std::make_shared()) == "bark"); + BOOST_TEST(poke(std::make_shared()) == "hiss"); + // end::shared_by_value_call[] + } + + { + using namespace by_reference; + // tag::shared_by_reference_call[] + const std::shared_ptr snoopy = std::make_shared(); + + BOOST_TEST(poke(snoopy) == "bark"); + BOOST_TEST(snoopy.use_count() == 1); + // end::shared_by_reference_call[] + } +} + +BOOST_AUTO_TEST_CASE(unique_ptr_examples) { + initialize(); + + { + // tag::make_unique_virtual[] + unique_virtual_ptr animal = make_unique_virtual(); + + BOOST_TEST(animal.vptr() == default_registry::static_vptr); + // end::make_unique_virtual[] + } + + { + // tag::unique_virtual_ptr_alias[] + unique_virtual_ptr animal = make_unique_virtual(); + unique_virtual_ptr owner = std::move(animal); + + BOOST_TEST(owner.get() != nullptr); + BOOST_TEST(animal.get() == nullptr); + // end::unique_virtual_ptr_alias[] + } + + { + using namespace unique; + // tag::unique_by_value_call[] + BOOST_TEST(poke(std::make_unique()) == "bark"); + BOOST_TEST(poke(std::make_unique()) == "hiss"); + // end::unique_by_value_call[] + } +} diff --git a/doc/modules/ROOT/snippets/virtual_ptr.cpp b/doc/modules/ROOT/snippets/virtual_ptr.cpp index 4f03a3d1..002878de 100644 --- a/doc/modules/ROOT/snippets/virtual_ptr.cpp +++ b/doc/modules/ROOT/snippets/virtual_ptr.cpp @@ -421,3 +421,76 @@ BOOST_AUTO_TEST_CASE(shared_virtual_ptr_examples) { // end::shared_assign_move_vptr[] } } + +BOOST_AUTO_TEST_CASE(unique_virtual_ptr_examples) { + initialize(); + + { + using namespace polymorphic_classes; + // tag::unique_copy_rejected[] + static_assert( + std::is_constructible_v< + unique_virtual_ptr, const std::unique_ptr&> == + false); + // end::unique_copy_rejected[] + } + + { + using namespace polymorphic_classes; + // tag::unique_ctor_move_smart_ptr[] + std::unique_ptr snoopy = std::make_unique(); + Dog* moving = snoopy.get(); + + unique_virtual_ptr p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::unique_ctor_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::unique_ctor_move_vptr[] + unique_virtual_ptr snoopy = make_unique_virtual(); + Dog* moving = snoopy.get(); + + unique_virtual_ptr p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::unique_ctor_move_vptr[] + } + + { + using namespace polymorphic_classes; + // tag::unique_assign_move_smart_ptr[] + std::unique_ptr snoopy = std::make_unique(); + Dog* moving = snoopy.get(); + unique_virtual_ptr p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::unique_assign_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::unique_assign_move_vptr[] + unique_virtual_ptr snoopy = make_unique_virtual(); + Dog* moving = snoopy.get(); + unique_virtual_ptr p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + BOOST_TEST(snoopy.vptr() == nullptr); + // end::unique_assign_move_vptr[] + } +} diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index edf31d9d..95203a65 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -1138,9 +1138,17 @@ class virtual_ptr< //! Set the object pointer with a copy of `other`. Set the v-table pointer //! according to the dynamic type of `*other`. //! - //! @par Example + //! @par Examples + //! + //! Constructing from a `std::shared_ptr`: + //! //! include:virtual_ptr.cpp#shared_ctor_const_smart_ptr //! + //! A move-only smart pointer cannot be copied from. Use the move + //! constructor instead: + //! + //! include:virtual_ptr.cpp#unique_copy_rejected + //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. @@ -1210,9 +1218,16 @@ class virtual_ptr< //! Move object pointer from `other` to `this`. Set the v-table pointer //! according to the dynamic type of `*other`. //! - //! @par Example + //! @par Examples + //! + //! Move-constructing from a `std::shared_ptr`: + //! //! include:virtual_ptr.cpp#shared_ctor_move_smart_ptr //! + //! Move-constructing from a `std::unique_ptr`: + //! + //! include:virtual_ptr.cpp#unique_ctor_move_smart_ptr + //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. @@ -1272,9 +1287,16 @@ class virtual_ptr< //! //! `Other` is _not_ required to be a pointer to a polymorphic class. //! - //! @par Example + //! @par Examples + //! + //! Move-constructing from a shared `virtual_ptr`: + //! //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_move_vptr //! + //! Move-constructing from a unique `virtual_ptr`: + //! + //! include:virtual_ptr.cpp#unique_ctor_move_vptr + //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. @@ -1343,9 +1365,16 @@ class virtual_ptr< //! Move object pointer from `other` to `this`. Set the v-table pointer //! according to the dynamic type of `*other`. //! - //! @par Example + //! @par Examples + //! + //! Move-assigning from a `std::shared_ptr`: + //! //! include:virtual_ptr.cpp#shared_assign_move_smart_ptr //! + //! Move-assigning from a `std::unique_ptr`: + //! + //! include:virtual_ptr.cpp#unique_assign_move_smart_ptr + //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. @@ -1430,9 +1459,16 @@ class virtual_ptr< //! //! `Other` is _not_ required to be a pointer to a polymorphic class. //! - //! @par Example + //! @par Examples + //! + //! Move-assigning from a shared `virtual_ptr`: + //! //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_move_vptr //! + //! Move-assigning from a unique `virtual_ptr`: + //! + //! include:virtual_ptr.cpp#unique_assign_move_vptr + //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. diff --git a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp index 63b0cb76..a7ee68f2 100644 --- a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp +++ b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp @@ -14,6 +14,9 @@ namespace boost::openmethod { //! Specialize virtual_traits for boost::intrusive_ptr. //! +//! @par Example +//! include:intrusive_ptr.cpp#classes;by_value +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. //! @@ -62,6 +65,9 @@ struct virtual_traits, Registry> { //! Specialize virtual_traits for const boost::intrusive_ptr&. //! +//! @par Example +//! include:intrusive_ptr.cpp#classes;by_reference +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. //! @@ -119,6 +125,9 @@ struct virtual_traits&, Registry> { //! Alias for a `virtual_ptr>`. //! +//! @par Example +//! include:intrusive_ptr.cpp#boost_intrusive_virtual_ptr_alias +//! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using boost_intrusive_virtual_ptr = @@ -139,6 +148,9 @@ using boost_intrusive_virtual_ptr = //! @return A `boost_intrusive_virtual_ptr` pointing to a newly //! created object of type `Class`. //! +//! @par Example +//! include:intrusive_ptr.cpp#make_boost_intrusive_virtual +//! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index d859ec45..1a804f57 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -53,6 +53,9 @@ struct validate_method_parameter< //! Specialize virtual_traits for std::shared_ptr by value. //! +//! @par Example +//! include:smart_pointers.cpp#classes;shared_by_value +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. //! @@ -133,6 +136,9 @@ struct virtual_traits, Registry> { //! Specialize virtual_traits for std::shared_ptr by reference. //! +//! @par Example +//! include:smart_pointers.cpp#classes;shared_by_reference +//! //! @note Passing a `std::shared_ptr` in a method call by const reference //! creates a temporary `std::shared_ptr` and passes it by const reference to //! the overrider. This is necessary because virtual arguments need to be cast @@ -191,6 +197,9 @@ struct virtual_traits&, Registry> { //! Alias for a `virtual_ptr>`. //! +//! @par Example +//! include:smart_pointers.cpp#shared_virtual_ptr_alias +//! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using shared_virtual_ptr = virtual_ptr, Registry>; @@ -210,6 +219,9 @@ using shared_virtual_ptr = virtual_ptr, Registry>; //! @return A `shared_virtual_ptr` pointing to a newly //! created object of type `Class`. //! +//! @par Example +//! include:smart_pointers.cpp#make_shared_virtual +//! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, diff --git a/include/boost/openmethod/interop/std_unique_ptr.hpp b/include/boost/openmethod/interop/std_unique_ptr.hpp index b2190957..c136943f 100644 --- a/include/boost/openmethod/interop/std_unique_ptr.hpp +++ b/include/boost/openmethod/interop/std_unique_ptr.hpp @@ -14,6 +14,9 @@ namespace boost::openmethod { //! Specialize virtual_traits for std::unique_ptr by value. //! +//! @par Example +//! include:smart_pointers.cpp#classes;unique_by_value +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. //! @@ -65,6 +68,9 @@ struct virtual_traits, Registry> { //! Alias for a `virtual_ptr>`. //! +//! @par Example +//! include:smart_pointers.cpp#unique_virtual_ptr_alias +//! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using unique_virtual_ptr = virtual_ptr, Registry>; @@ -84,6 +90,9 @@ using unique_virtual_ptr = virtual_ptr, Registry>; //! @return A `unique_virtual_ptr` pointing to a newly //! created object of type `Class`. //! +//! @par Example +//! include:smart_pointers.cpp#make_unique_virtual +//! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, From bf7656ca89648e8530f708848e64d39d66df593d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 13:40:00 -0400 Subject: [PATCH 19/85] doc: make the factory examples call a method `make_shared_virtual`, `make_unique_virtual` and `make_boost_intrusive_virtual` showed the returned pointer's vptr rather than what the pointer is for. Each now dispatches a call. Each needs a method taking a `virtual_ptr` to the smart pointer, which is a different signature from the by-value and by-reference ones already in the files, hence the extra namespaces. The unique version reads `poke(std::move(animal))`: the pointer is move-only, so passing it to a method consumes it. That is worth showing on the page rather than hiding behind a temporary. --- doc/modules/ROOT/snippets/intrusive_ptr.cpp | 19 ++++++++++- doc/modules/ROOT/snippets/smart_pointers.cpp | 34 ++++++++++++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/doc/modules/ROOT/snippets/intrusive_ptr.cpp b/doc/modules/ROOT/snippets/intrusive_ptr.cpp index f35adc6b..f65a14eb 100644 --- a/doc/modules/ROOT/snippets/intrusive_ptr.cpp +++ b/doc/modules/ROOT/snippets/intrusive_ptr.cpp @@ -62,15 +62,32 @@ BOOST_OPENMETHOD_OVERRIDE( } // namespace by_reference +namespace vptr { + +BOOST_OPENMETHOD(poke, (boost_intrusive_virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost_intrusive_virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost_intrusive_virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace vptr + BOOST_AUTO_TEST_CASE(intrusive_ptr_examples) { initialize(); { + using namespace vptr; // tag::make_boost_intrusive_virtual[] boost_intrusive_virtual_ptr animal = make_boost_intrusive_virtual(); - BOOST_TEST(animal.vptr() == default_registry::static_vptr); + BOOST_TEST(poke(animal) == "bark"); // end::make_boost_intrusive_virtual[] } diff --git a/doc/modules/ROOT/snippets/smart_pointers.cpp b/doc/modules/ROOT/snippets/smart_pointers.cpp index 86041dd2..4ac0eba8 100644 --- a/doc/modules/ROOT/snippets/smart_pointers.cpp +++ b/doc/modules/ROOT/snippets/smart_pointers.cpp @@ -73,14 +73,43 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (std::unique_ptr animal), std::string) { } // namespace unique +namespace shared_vptr { + +BOOST_OPENMETHOD(poke, (shared_virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (shared_virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (shared_virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace shared_vptr + +namespace unique_vptr { + +BOOST_OPENMETHOD(poke, (unique_virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (unique_virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (unique_virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace unique_vptr + BOOST_AUTO_TEST_CASE(shared_ptr_examples) { initialize(); { + using namespace shared_vptr; // tag::make_shared_virtual[] shared_virtual_ptr animal = make_shared_virtual(); - BOOST_TEST(animal.vptr() == default_registry::static_vptr); + BOOST_TEST(poke(animal) == "bark"); // end::make_shared_virtual[] } @@ -116,10 +145,11 @@ BOOST_AUTO_TEST_CASE(unique_ptr_examples) { initialize(); { + using namespace unique_vptr; // tag::make_unique_virtual[] unique_virtual_ptr animal = make_unique_virtual(); - BOOST_TEST(animal.vptr() == default_registry::static_vptr); + BOOST_TEST(poke(std::move(animal)) == "bark"); // end::make_unique_virtual[] } From b8fc9446a210c2604cdeac4855f63d03533a5a3a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 14:02:32 -0400 Subject: [PATCH 20/85] doc: examples for the policies headers The eight policies headers documented registry composition entirely in prose. policies/static_rtti.hpp was worse than empty: its example read `TODO` followed by `include::example$static_rtti.cpp[tag=all]`, a tag that does not exist -- examples/static_rtti.cpp uses Quickbook `//[ all` markers, not Antora ones -- so the page shipped a broken instruction. Adds snippets/policies.cpp, one registry per policy since policies are registry-level and several are mutually exclusive, and snippets/static_rtti.cpp, which needs its own translation unit because the policy has to be selected before is included. The tagged region is usually the registry declaration, because that is the line a user writes; the classes, method and assertions around it are compiled but untagged, so the rendered snippet stays short while the whole thing is verified. Writing them turned up a constraint the reference understates. Composing `std_rtti` with `vptr_vector` and no `type_hash` throws `std::bad_alloc` on the first `initialize()`: the vector is indexed by the type id, and `std_rtti` makes a type id a pointer, so it is sized to the address space. The `fast_perfect_hash` example now says so. Also drops a stray `@ref` from stderr_output.hpp's brief, which was rendering "Writes" as a broken code reference on that page and on the three listing pages that repeat the brief. --- doc/modules/ROOT/snippets/policies.cpp | 264 ++++++++++++++++++ doc/modules/ROOT/snippets/static_rtti.cpp | 58 ++++ .../policies/default_error_handler.hpp | 3 + .../openmethod/policies/fast_perfect_hash.hpp | 3 + .../boost/openmethod/policies/static_rtti.hpp | 15 +- .../boost/openmethod/policies/std_rtti.hpp | 3 + .../openmethod/policies/stderr_output.hpp | 5 +- .../policies/throw_error_handler.hpp | 3 + .../boost/openmethod/policies/vptr_map.hpp | 3 + .../boost/openmethod/policies/vptr_vector.hpp | 3 + 10 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 doc/modules/ROOT/snippets/policies.cpp create mode 100644 doc/modules/ROOT/snippets/static_rtti.cpp diff --git a/doc/modules/ROOT/snippets/policies.cpp b/doc/modules/ROOT/snippets/policies.cpp new file mode 100644 index 00000000..73281b0d --- /dev/null +++ b/doc/modules/ROOT/snippets/policies.cpp @@ -0,0 +1,264 @@ +// Copyright (c) 2018-2025 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; + +struct Animal { + virtual ~Animal() = default; +}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +// The registries below each get their own copy of the classes and of `trick`. +// Only `Dog` has an overrider, so calling `trick` on a `Cat` reaches the +// registry's error handler. + +namespace std_rtti_demo { + +// tag::std_rtti[] +struct dynamic_registry : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector> {}; +// end::std_rtti[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, dynamic_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + dynamic_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace std_rtti_demo + +namespace vptr_vector_demo { + +// tag::vptr_vector[] +// `fast_perfect_hash` turns the type ids into small indices; without it the +// vector is indexed by the type id itself, which `std_rtti` makes a pointer +struct vector_registry : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector> {}; +// end::vptr_vector[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, vector_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + vector_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace vptr_vector_demo + +namespace vptr_map_demo { + +// tag::vptr_map[] +struct map_registry : registry> {}; +// end::vptr_map[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, map_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, map_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace vptr_map_demo + +namespace fast_perfect_hash_demo { + +// tag::fast_perfect_hash[] +// `vptr_vector` indexes by the type id unless a `type_hash` policy maps it to +// a small integer first. With `std_rtti`, where a type id is a pointer, that +// makes the difference between a vector of a few entries and one that cannot +// be allocated at all. +struct hashed_registry : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector> {}; +// end::fast_perfect_hash[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, hashed_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + hashed_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace fast_perfect_hash_demo + +namespace stderr_output_demo { + +// tag::stderr_output[] +struct noisy_registry + : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::default_error_handler, + policies::stderr_output> {}; +// end::stderr_output[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, noisy_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, noisy_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace stderr_output_demo + +namespace default_error_handler_demo { + +// tag::default_error_handler_registry[] +struct handled_registry + : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::default_error_handler, + policies::stderr_output> {}; +// end::default_error_handler_registry[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, handled_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + handled_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace default_error_handler_demo + +namespace throw_error_handler_demo { + +// tag::throw_error_handler_registry[] +struct throwing_registry + : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::throw_error_handler> {}; +// end::throw_error_handler_registry[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, throwing_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + throwing_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace throw_error_handler_demo + +BOOST_AUTO_TEST_CASE(rtti_and_storage) { + { + using namespace std_rtti_demo; + initialize(); + + // tag::std_rtti_dispatch[] + Dog snoopy; + Animal& animal = snoopy; + + BOOST_TEST( + trick(virtual_ptr(animal)) == "spin"); + // end::std_rtti_dispatch[] + } + + { + using namespace vptr_vector_demo; + initialize(); + + Dog snoopy; + BOOST_TEST( + trick(virtual_ptr(snoopy)) == "spin"); + } + + { + using namespace vptr_map_demo; + initialize(); + + Dog snoopy; + BOOST_TEST(trick(virtual_ptr(snoopy)) == "spin"); + } + + { + using namespace fast_perfect_hash_demo; + initialize(); + + Dog snoopy; + BOOST_TEST( + trick(virtual_ptr(snoopy)) == "spin"); + } + + { + using namespace stderr_output_demo; + initialize(); + + Dog snoopy; + BOOST_TEST( + trick(virtual_ptr(snoopy)) == "spin"); + } +} + +BOOST_AUTO_TEST_CASE(error_handlers) { + { + using namespace default_error_handler_demo; + initialize(); + + // tag::default_error_handler_set[] + handled_registry::error_handler::set([](const auto& error) { + if (std::holds_alternative(error)) { + throw std::runtime_error("not implemented"); + } + }); + + Cat felix; + + BOOST_CHECK_THROW( + trick(virtual_ptr(felix)), + std::runtime_error); + // end::default_error_handler_set[] + } + + { + using namespace throw_error_handler_demo; + initialize(); + + // tag::throw_error_handler_catch[] + Cat felix; + + BOOST_CHECK_THROW( + trick(virtual_ptr(felix)), no_overrider); + // end::throw_error_handler_catch[] + } +} diff --git a/doc/modules/ROOT/snippets/static_rtti.cpp b/doc/modules/ROOT/snippets/static_rtti.cpp new file mode 100644 index 00000000..e3d537d3 --- /dev/null +++ b/doc/modules/ROOT/snippets/static_rtti.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2018-2025 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) + +// `static_rtti` has to be selected before is included, +// so this example needs a translation unit of its own. + +// tag::registry[] +#include +#include + +struct static_registry + : boost::openmethod::registry {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry +// end::registry[] + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod::aliases; + +// tag::classes[] +// polymorphism not required: there is no RTTI to consult +struct Animal {}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +BOOST_OPENMETHOD(trick, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(trick, (virtual_ptr), std::string) { + return "spin"; +} + +BOOST_OPENMETHOD_OVERRIDE(trick, (virtual_ptr), std::string) { + return "sulk"; +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(static_rtti_examples) { + boost::openmethod::initialize(); + + // tag::dispatch[] + // the exact class must be known where the pointer is created + unique_virtual_ptr a = make_unique_virtual(); + unique_virtual_ptr b = make_unique_virtual(); + + BOOST_TEST(trick(a) == "sulk"); + BOOST_TEST(trick(b) == "spin"); + // end::dispatch[] +} diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index b7e19e96..2aca30b7 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -33,6 +33,9 @@ namespace policies { //! program termination. The @ref throw_error_handler policy can also be used to //! enable exception throwing on a registry basis. //! +//! @par Example +//! include:policies.cpp#default_error_handler_registry;default_error_handler_set +//! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct default_error_handler : error_handler { diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index 204f9281..a5d8be56 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -61,6 +61,9 @@ namespace policies { //! range of integers. In other words, a lot of space may be wasted in presence //! of large sets of type_ids. //! +//! @par Example +//! include:policies.cpp#fast_perfect_hash +//! //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct fast_perfect_hash : type_hash { diff --git a/include/boost/openmethod/policies/static_rtti.hpp b/include/boost/openmethod/policies/static_rtti.hpp index a8750376..9cb452b0 100644 --- a/include/boost/openmethod/policies/static_rtti.hpp +++ b/include/boost/openmethod/policies/static_rtti.hpp @@ -20,8 +20,19 @@ namespace boost::openmethod::policies { //! its equivalents for smart pointers). //! //! @par Example -//! TODO -//! include::example$static_rtti.cpp[tag=all] +//! +//! Selecting the policy, which has to happen before `` +//! is included: +//! +//! include:static_rtti.cpp#registry +//! +//! The classes and the method need no RTTI, and need not be polymorphic: +//! +//! include:static_rtti.cpp#classes +//! +//! Every `virtual_ptr` has to be created where the exact class is known: +//! +//! include:static_rtti.cpp#dispatch //! //! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct static_rtti : rtti { diff --git a/include/boost/openmethod/policies/std_rtti.hpp b/include/boost/openmethod/policies/std_rtti.hpp index e2f9986b..7426263e 100644 --- a/include/boost/openmethod/policies/std_rtti.hpp +++ b/include/boost/openmethod/policies/std_rtti.hpp @@ -21,6 +21,9 @@ namespace boost::openmethod::policies { //! `std_rtti` implements the `rtti` policy using the standard C++ RTTI system. //! It is the default RTTI policy. //! +//! @par Example +//! include:policies.cpp#std_rtti;std_rtti_dispatch +//! //! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct std_rtti : rtti { //! A RttiFn metafunction. diff --git a/include/boost/openmethod/policies/stderr_output.hpp b/include/boost/openmethod/policies/stderr_output.hpp index 85c4eacc..0258c182 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -13,10 +13,13 @@ namespace boost::openmethod { namespace policies { -//! @ref Writes to the C standard error stream. +//! Writes to the C standard error stream. //! //! `stderr_output` writes to standard error using the C API. //! +//! @par Example +//! include:policies.cpp#stderr_output +//! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct stderr_output : output { //! An OutputFn metafunction. diff --git a/include/boost/openmethod/policies/throw_error_handler.hpp b/include/boost/openmethod/policies/throw_error_handler.hpp index edc95ee5..08d1fa24 100644 --- a/include/boost/openmethod/policies/throw_error_handler.hpp +++ b/include/boost/openmethod/policies/throw_error_handler.hpp @@ -17,6 +17,9 @@ namespace boost::openmethod::policies { //! Throws error as an exception. //! //! +//! @par Example +//! include:policies.cpp#throw_error_handler_registry;throw_error_handler_catch +//! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct throw_error_handler : error_handler { //! A ErrorHandlerFn metafunction. diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index da8e7970..61927e2b 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -25,6 +25,9 @@ namespace policies { //! @tparam MapFn A mp11 quoted metafunction that takes a key type and a //! value type, and returns an @ref AssociativeContainer. //! +//! @par Example +//! include:policies.cpp#vptr_map +//! //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template> class vptr_map : public vptr { diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index c7ec0ea0..3c4ec999 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -25,6 +25,9 @@ namespace policies { //! If the registry contains the @ref indirect_vptr policy, stores pointers to //! pointers to v-tables in the vector. //! +//! @par Example +//! include:policies.cpp#vptr_vector +//! //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct vptr_vector : vptr { public: From b6512b765ad2e211ffa9d043602155064417ed5e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 14:09:43 -0400 Subject: [PATCH 21/85] doc: convert the last convertible @code blocks preamble.hpp's four error examples and the accompanying fix, plus the programs in initialize.hpp and inplace_vptr.hpp, now come from compiled snippets. What is left is pseudo-code and stays: macros.hpp's eight synopses, and the DLL incantation in preamble.hpp and default_registry.hpp, which shows IMPORT and EXPORT of one registry together and so cannot compile in a single translation unit. The four error examples live in one snippets/errors.cpp, each in its own registry so that one deliberate mistake does not mask another. That needs a marker policy carrying an integer: registries deriving from the same `registry<...>` specialization share one state, so a single alias would have pooled the registrations -- the same device, and the same reason, as `test_registry_` in test/test_util.hpp. Compiling them showed the first example did not demonstrate its own error. It registered `Animal`, the method's virtual parameter, and left an unused `Dog` unregistered; that raises nothing. What raises `missing_class` is the reverse -- registering `Dog` and leaving `Animal` unregistered -- so the example now does that. The markers also needed a blank `//!` line above them. The `@code` fence used to end the preceding paragraph; without it the marker would have been swallowed into the prose line above and never matched. --- doc/modules/ROOT/snippets/errors.cpp | 176 +++++++++++++++++++++ doc/modules/ROOT/snippets/initialize.cpp | 42 +++++ doc/modules/ROOT/snippets/inplace_vptr.cpp | 45 ++++++ include/boost/openmethod/initialize.hpp | 19 +-- include/boost/openmethod/inplace_vptr.hpp | 37 +---- include/boost/openmethod/preamble.hpp | 46 +----- 6 files changed, 270 insertions(+), 95 deletions(-) create mode 100644 doc/modules/ROOT/snippets/errors.cpp create mode 100644 doc/modules/ROOT/snippets/initialize.cpp create mode 100644 doc/modules/ROOT/snippets/inplace_vptr.cpp diff --git a/doc/modules/ROOT/snippets/errors.cpp b/doc/modules/ROOT/snippets/errors.cpp new file mode 100644 index 00000000..ae5f03fd --- /dev/null +++ b/doc/modules/ROOT/snippets/errors.cpp @@ -0,0 +1,176 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +// Each example below registers its classes in a registry of its own, so that +// one deliberate mistake does not affect the others. They all throw rather +// than abort, which is what `throw_error_handler` is for. +// +// Registries that derive from the same `registry<...>` specialization share +// one state, so deriving all of them from a single alias would pool the +// registrations and mask the mistakes. A marker policy carrying an integer +// gives each a distinct base -- the same device as `test_registry_` in +// test/test_util.hpp. + +struct marker_category { + using category = marker_category; +}; + +template +struct marker final : marker_category { + template + struct fn {}; +}; + +template +using throwing = + default_registry::with, policies::throw_error_handler>; + +namespace missing_parameter_class { + +struct missing_parameter : throwing<1> {}; + +// tag::missing_class_in_method[] +BOOST_OPENMETHOD_CLASSES(Dog, missing_parameter); // Animal is missing + +BOOST_OPENMETHOD( + poke, (virtual_ptr), void, missing_parameter); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr), void) { /* ... */ +} +// end::missing_class_in_method[] + +} // namespace missing_parameter_class + +namespace missing_overrider_class { + +struct missing_overrider : throwing<2> {}; + +// tag::missing_class_in_overrider[] +BOOST_OPENMETHOD_CLASSES(Animal, missing_overrider); // Dog is missing + +BOOST_OPENMETHOD( + poke, (virtual_ptr), void, missing_overrider); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr), void) { /* ... */ +} +// end::missing_class_in_overrider[] + +} // namespace missing_overrider_class + +namespace missing_call_class { + +struct missing_argument : throwing<3> {}; + +// tag::missing_class_in_call[] +BOOST_OPENMETHOD_CLASSES(Animal, Dog, missing_argument); // Bulldog is missing + +BOOST_OPENMETHOD( + poke, (virtual_ptr), void, missing_argument); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr), void) { /* ... */ +} +// end::missing_class_in_call[] + +} // namespace missing_call_class + +namespace unrelated_registration { + +struct unrelated_classes : throwing<4> {}; + +// tag::missing_base[] +// registered separately, so the inheritance is never seen +BOOST_OPENMETHOD_CLASSES(Animal, unrelated_classes); +BOOST_OPENMETHOD_CLASSES(Dog, unrelated_classes); + +BOOST_OPENMETHOD( + poke, (virtual_ptr), void, unrelated_classes); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr), void) { /* ... */ +} +// end::missing_base[] + +} // namespace unrelated_registration + +namespace related_registration { + +struct related_classes : throwing<5> {}; + +// tag::missing_base_fix[] +BOOST_OPENMETHOD_CLASSES(Animal, Dog, related_classes); +// end::missing_base_fix[] + +BOOST_OPENMETHOD( + poke, (virtual_ptr), void, related_classes); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr), void) { /* ... */ +} + +} // namespace related_registration + +BOOST_AUTO_TEST_CASE(missing_class_errors) { + { + using namespace missing_parameter_class; + // tag::missing_class_in_method_init[] + BOOST_CHECK_THROW(initialize(), missing_class); + // end::missing_class_in_method_init[] + } + + { + using namespace missing_overrider_class; + // tag::missing_class_in_overrider_init[] + BOOST_CHECK_THROW(initialize(), missing_class); + // end::missing_class_in_overrider_init[] + } + + { + using namespace missing_call_class; + initialize(); + + // tag::missing_class_in_call_use[] + Bulldog hector; + + BOOST_CHECK_THROW( + poke(virtual_ptr(hector)), missing_class); + // end::missing_class_in_call_use[] + } +} + +BOOST_AUTO_TEST_CASE(missing_base_errors) { + { + using namespace unrelated_registration; + // tag::missing_base_init[] + BOOST_CHECK_THROW(initialize(), missing_base); + // end::missing_base_init[] + } + + { + using namespace related_registration; + initialize(); + + Dog snoopy; + poke(virtual_ptr(snoopy)); + } +} diff --git a/doc/modules/ROOT/snippets/initialize.cpp b/doc/modules/ROOT/snippets/initialize.cpp new file mode 100644 index 00000000..53ffb09d --- /dev/null +++ b/doc/modules/ROOT/snippets/initialize.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +namespace bom = boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +BOOST_OPENMETHOD(trick, (bom::virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(trick, (bom::virtual_ptr), std::string) { + return "stare"; +} + +BOOST_OPENMETHOD_OVERRIDE(trick, (bom::virtual_ptr), std::string) { + return "spin"; +} + +BOOST_AUTO_TEST_CASE(initialize_report) { + // tag::report[] + auto report = bom::initialize(bom::trace::from_env()).report; + + BOOST_TEST(report.not_implemented == 0); + BOOST_TEST(report.ambiguous == 0); + // end::report[] + + Dog snoopy; + BOOST_TEST(trick(bom::virtual_ptr(snoopy)) == "spin"); +} diff --git a/doc/modules/ROOT/snippets/inplace_vptr.cpp b/doc/modules/ROOT/snippets/inplace_vptr.cpp new file mode 100644 index 00000000..61d79693 --- /dev/null +++ b/doc/modules/ROOT/snippets/inplace_vptr.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +// tag::classes[] +struct Animal : inplace_vptr_base {}; + +struct Cat : Animal, inplace_vptr_derived {}; + +struct Dog : Animal, inplace_vptr_derived {}; + +BOOST_OPENMETHOD(trick, (virtual_ animal), std::string); + +BOOST_OPENMETHOD_OVERRIDE(trick, (Cat&), std::string) { + return "sulk"; +} + +BOOST_OPENMETHOD_OVERRIDE(trick, (Dog&), std::string) { + return "spin"; +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(inplace_vptr_examples) { + // tag::dispatch[] + initialize(); + + std::unique_ptr a = std::make_unique(); + std::unique_ptr b = std::make_unique(); + + BOOST_TEST(trick(*a) == "sulk"); + BOOST_TEST(trick(*b) == "spin"); + // end::dispatch[] +} diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index c1c5f4ea..1feb155d 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -1889,24 +1889,7 @@ void registry::compiler::print( //! the program again after setting environment variable //! `BOOST_OPENMETHOD_TRACE` to `1` to troubleshoot. //! -//! @code -//! #include -//! -//! #include -//! #include -//! -//! int main() { -//! namespace bom = boost::openmethod; -//! auto report = bom::initialize(bom::trace::from_env()).report; -//! -//! if (report.not_implemented != 0 || report.ambiguous != 0) { -//! std::cerr << "missing overriders or ambiguous methods\n"; -//! return 1; -//! } -//! -//! // ... -//! } -//! @endcode +//! include:initialize.cpp#report //! //! @see [Methods and Overriders](xref:ROOT:basics.adoc) //! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index 2eac8c62..7505bf32 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -88,42 +88,7 @@ class inplace_vptr_base_tag {}; //! are registered. //! //! @par Example -//! @code -//! #include -//! #include -//! #include -//! -//! using namespace boost::openmethod; -//! -//! struct Animal : inplace_vptr_base {}; -//! -//! struct Cat : Animal, inplace_vptr_derived {}; -//! -//! struct Dog : Animal, inplace_vptr_derived {}; -//! -//! BOOST_OPENMETHOD( -//! poke, (virtual_ animal, std::ostream& os), void); -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (Cat&, std::ostream& os), void) { -//! os << "hiss\n"; -//! } -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&, std::ostream& os), void) { -//! os << "bark\n"; -//! } -//! -//! int main() { -//! initialize(); -//! -//! std::unique_ptr a = std::make_unique(); -//! std::unique_ptr b = std::make_unique(); -//! -//! poke(*a, std::cout); // hiss -//! poke(*b, std::cout); // bark -//! -//! return 0; -//! } -//! @endcode +//! include:inplace_vptr.cpp#classes;dispatch //! //! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) template diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 775cd703..3d316689 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -159,41 +159,16 @@ struct not_initialized : openmethod_error { //! @par Examples //! //! Missing registration of a class used as a virtual parameter in a method: -//! @code -//! struct Animal { virtual ~Animal() {} }; -//! struct Dog : Animal {}; -//! -//! BOOST_OPENMETHOD_CLASSES(Animal); //! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); -//! -//! initialize(); // throws missing_class; -//! @endcode +//! include:errors.cpp#missing_class_in_method;missing_class_in_method_init //! //! Missing registration of a class used as a virtual parameter in an overrider: -//! @code -//! BOOST_OPENMETHOD_CLASSES(Animal); //! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ } -//! -//! initialize(); // throws missing_class; -//! @endcode +//! include:errors.cpp#missing_class_in_overrider;missing_class_in_overrider_init //! //! Missing registration of a class used as a virtual parameter in a call: -//! @code -//! struct Bulldog : Dog {}; -//! -//! BOOST_OPENMETHOD_CLASSES(Animal, Dog); //! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ } -//! -//! Bulldog hector; -//! poke(hector); // throws missing_class; -//! @endcode +//! include:errors.cpp#missing_class_in_call;missing_class_in_call_use //! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_class : openmethod_error { @@ -219,22 +194,11 @@ struct missing_class : openmethod_error { //! `Animal`, because they are not registered in a same call to @ref //! BOOST_OPENMETHOD_CLASSES. //! -//! @code -//! BOOST_OPENMETHOD_CLASSES(Animal); -//! BOOST_OPENMETHOD_CLASSES(Dog); -//! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ } -//! -//! initialize(); // throws missing_base; -//! @endcode +//! include:errors.cpp#missing_base;missing_base_init //! //! Fix: //! -//! @code -//! BOOST_OPENMETHOD_CLASSES(Animal, Dog); -//! @endcode +//! include:errors.cpp#missing_base_fix //! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_base : openmethod_error { From c3d9def762b63a49f6fdfa3b9e1d06318f049ed5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 14:36:51 -0400 Subject: [PATCH 22/85] doc: document that a registry is its policy list, not its class A registry's identity is the `registry` specialization: state, class and method lists, dispatch tables and `static_vptr` are all keyed on it. Two structs deriving from the same specialization are therefore one registry, sharing everything. Nothing said so. The guide said only to derive a class rather than typedef, which invites exactly the wrong conclusion -- that the class is the registry. Writing snippets/errors.cpp ran straight into it: four registries derived from one alias pooled their registrations, so three of the four error examples silently stopped raising their error. The trap is worst in a case the library recommends, isolating one set of methods from another: two such registries would naturally carry the same policies and so would silently be one. The way out is to give each a policy of its own, which is what test_util.hpp's `test_registry_` does and what snippets/errors.cpp had to reinvent. Both are now documented. The example is a `static_assert` on `registry_type`, which states the rule exactly and cannot go stale. It lives under examples/ rather than snippets/ because the guide reaches it with `include::example$`, which cannot see snippets/ -- Antora ignores the directory as an unrecognised family. The reference markers point at the same file, so there is one copy. --- .../ROOT/examples/registry_identity.cpp | 55 +++++++++++++++++++ .../ROOT/pages/registries_and_policies.adoc | 22 ++++++++ include/boost/openmethod/preamble.hpp | 21 ++++++- 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 doc/modules/ROOT/examples/registry_identity.cpp diff --git a/doc/modules/ROOT/examples/registry_identity.cpp b/doc/modules/ROOT/examples/registry_identity.cpp new file mode 100644 index 00000000..5d91db6a --- /dev/null +++ b/doc/modules/ROOT/examples/registry_identity.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2018-2025 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; + +namespace same_policies { + +// tag::shared[] +struct animals : registry> {}; +struct vehicles : registry> {}; + +// the policy lists are identical, so this is one registry, not two +static_assert(std::is_same_v); +// end::shared[] + +} // namespace same_policies + +namespace distinct_policies { + +// tag::distinct[] +// a policy in a category of its own, carrying nothing but a number +struct marker_category { + using category = marker_category; +}; + +template +struct marker final : marker_category { + template + struct fn {}; +}; + +struct animals : default_registry::with> {}; +struct vehicles : default_registry::with> {}; + +static_assert(!std::is_same_v); +// end::distinct[] + +} // namespace distinct_policies + +auto main() -> int { + // the shared pair reach one state, the distinct pair two + assert(same_policies::animals::id() == same_policies::vehicles::id()); + assert( + distinct_policies::animals::id() != distinct_policies::vehicles::id()); + + return 0; +} diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index 0ca7414f..928f5088 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -87,6 +87,28 @@ When defining a new registry, it is recommended to define a new class, derived from `registry<...>`, rather than via a typedef, which would create excessively long symbol names and make debugging harder. +That class is a convenience, not the registry's identity. Everything a registry +owns - the class and method lists, the dispatch tables, the state of every +stateful policy - is keyed on the `registry<...>` specialization the class +derives from, which is what its `registry_type` member aliases. Two classes +built from the same policies, in the same order, are therefore the _same_ +registry, and share everything: + +[source,c++] +---- +include::example$registry_identity.cpp[tag=shared] +---- + +This is worth watching for when the purpose of a second registry is to isolate a +set of methods from another, since such a registry would naturally be given the +same policies as the first. Registering a class or a method in either would then +register it in both. To keep them apart, give each one a policy of its own: + +[source,c++] +---- +include::example$registry_identity.cpp[tag=distinct] +---- + The order of the policies matters. When cpp:initialize[] runs, it calls each policy's `initialize` in the order the policies appear in the registry, from left to right; cpp:finalize[] calls each policy's `finalize` in the reverse diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 3d316689..5ab51054 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -1120,6 +1120,19 @@ detail::registry_state_type registry_state::st; //! contains the `runtime_checks` policy. If an error is detected, it invokes //! the @ref error_handler policy if there is one. //! +//! A registry is identified by its policy list, not by the class that derives +//! from it. Everything a registry owns is keyed on the `registry` +//! specialization, which is what @ref registry_type aliases. Two classes built +//! from the same policies, in the same order, are therefore the same registry: +//! +//! include:../examples/registry_identity.cpp#shared +//! +//! This matters when a second registry exists to isolate a set of methods from +//! another, since it would naturally be given the same policies. Give each one +//! a policy of its own to keep them apart: +//! +//! include:../examples/registry_identity.cpp#distinct +//! //! @tparam Policy The policies used in the registry. //! //! @par Requirements @@ -1168,9 +1181,11 @@ class registry : public detail::registry_base { //! `registry_type` is the `registry` specialization itself - for a //! registry defined as a struct deriving from `registry` (like @ref //! default_registry), the base class, not the struct. It is the type on - //! which the registry's state is keyed. It appears in the explicit - //! instantiation / `extern template` declaration pair that shares a - //! custom registry's state across shared libraries: + //! which the registry's state is keyed. Two structs that derive from the + //! same specialization therefore share one state, and are one registry; + //! see @ref registry for how to keep two of them apart. It also appears in + //! the explicit instantiation / `extern template` declaration pair that + //! shares a custom registry's state across shared libraries: //! `registry_state` (see @ref //! registry_state). using registry_type = registry; From d48939790cac9096742a15dc4de52031c1d6e556 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 14:48:11 -0400 Subject: [PATCH 23/85] doc: a usage example for BOOST_OPENMETHOD macros.hpp had eight @code blocks and not one of them showed how to use a macro: they are all synopses of what the macro expands to, under Implementation Notes. The page explained dispatch semantics precisely and never declared a method. Adds snippets/macros.cpp, one file for the macro family so the rest can add tags to it, and a marker on BOOST_OPENMETHOD placed above Implementation Notes so usage comes before internals. The rendered example is the declaration and two calls, nothing else. The classes, the registration, the overriders and initialize() are in the file but outside the tags -- overriders belong on the BOOST_OPENMETHOD_OVERRIDE page, and the `// hiss` and `// bark` comments already tell the reader they exist. The calls write to std::cout, which on its own proves nothing, so the snippet redirects std::cout to an ostringstream and checks what came out. Both the redirect and the BOOST_TEST sit outside the tagged region, so the page shows the idiomatic printing form while the build verifies that dispatch really picks the two overriders. --- doc/modules/ROOT/snippets/macros.cpp | 58 ++++++++++++++++++++++++++++ include/boost/openmethod/macros.hpp | 4 ++ 2 files changed, 62 insertions(+) create mode 100644 doc/modules/ROOT/snippets/macros.cpp diff --git a/doc/modules/ROOT/snippets/macros.cpp b/doc/modules/ROOT/snippets/macros.cpp new file mode 100644 index 00000000..bf869261 --- /dev/null +++ b/doc/modules/ROOT/snippets/macros.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +// tag::declare[] +BOOST_OPENMETHOD(poke, (virtual_ptr animal, std::ostream& os), void); +// end::declare[] + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << "hiss"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(macro_examples) { + initialize(); + + std::ostringstream captured; + auto* previous = std::cout.rdbuf(captured.rdbuf()); + + // tag::call[] + Cat felix; + Animal& a = felix; + Dog snoopy; + Animal& b = snoopy; + + poke(a, std::cout); // hiss + poke(b, std::cout); // bark + // end::call[] + + std::cout.rdbuf(previous); + + BOOST_TEST(captured.str() == "hissbark"); +} diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 81c9e8b1..baa3da71 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -174,6 +174,10 @@ inline constexpr bool method_not_found = false; //! `` is included. Changing the value of this symbol //! has no effect after that point. //! +//! @par Example +//! +//! include:macros.cpp#declare;call +//! //! @par Implementation Notes //! //! The macro creates several additional constructs: From 33f2bbf8df439b3d14fe0cb79519b88b4b195328 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 14:51:46 -0400 Subject: [PATCH 24/85] doc: a usage example for BOOST_OPENMETHOD_OVERRIDE Tags the two overriders already in snippets/macros.cpp and points the macro at declare;override;call, so the page shows the method declaration for context, both overriders, and the calls they answer -- the same example BOOST_OPENMETHOD renders, with the overriders no longer elided. Placed above Implementation Notes, matching BOOST_OPENMETHOD. --- doc/modules/ROOT/snippets/macros.cpp | 2 ++ include/boost/openmethod/macros.hpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/doc/modules/ROOT/snippets/macros.cpp b/doc/modules/ROOT/snippets/macros.cpp index bf869261..81e9a5a5 100644 --- a/doc/modules/ROOT/snippets/macros.cpp +++ b/doc/modules/ROOT/snippets/macros.cpp @@ -26,6 +26,7 @@ BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); BOOST_OPENMETHOD(poke, (virtual_ptr animal, std::ostream& os), void); // end::declare[] +// tag::override[] BOOST_OPENMETHOD_OVERRIDE( poke, (virtual_ptr animal, std::ostream& os), void) { os << "hiss"; @@ -35,6 +36,7 @@ BOOST_OPENMETHOD_OVERRIDE( poke, (virtual_ptr animal, std::ostream& os), void) { os << "bark"; } +// end::override[] BOOST_AUTO_TEST_CASE(macro_examples) { initialize(); diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index baa3da71..c18306fe 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -401,6 +401,10 @@ inline constexpr bool method_not_found = false; //! //! @note `ID` must be an *identifier*. Qualified names are not allowed. //! +//! @par Example +//! +//! include:macros.cpp#declare;override;call +//! //! @par Implementation Notes //! //! The macro creates additional entities in the current scope. From 202b350b12b0dfcf8f4e5cfda78bafc81e11753a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 14:56:14 -0400 Subject: [PATCH 25/85] doc: point BOOST_OPENMETHOD at the overrider example rather than repeat it The two pages were rendering overlapping halves of one example. Now BOOST_OPENMETHOD's Example section is a link to BOOST_OPENMETHOD_OVERRIDE's, which shows the declaration, both overriders and the calls -- a method declared with nothing overriding it was the less useful half anyway. The link is a markdown link whose target is an Antora resource ID with a fragment, which the markup/a template emits verbatim; Antora resolves it to BOOST_OPENMETHOD_OVERRIDE.html#_example. Macro pages sit at the reference module root, so no relfileprefix is in play. snippets/macros.cpp is unchanged: the `declare` and `call` tags are still rendered, now only through the overrider page. --- include/boost/openmethod/macros.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index c18306fe..e19f2a59 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -176,7 +176,8 @@ inline constexpr bool method_not_found = false; //! //! @par Example //! -//! include:macros.cpp#declare;call +//! See [BOOST_OPENMETHOD_OVERRIDE](xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc#_example) +//! for an example. //! //! @par Implementation Notes //! From 4c1dfe0a7551c2979b2460b8cc239f1d42cd9980 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 15:03:14 -0400 Subject: [PATCH 26/85] doc: examples for BOOST_OPENMETHOD_CLASSES, correct and not Four registrations of the same hierarchy - Cat and Dog under Animal, Bulldog under Dog - showing what does and does not describe the inheritance. All four were run before being written down. The two that work: one call listing everything, or several calls where each class appears alongside its direct bases, `Dog` repeated to attach `Bulldog` to it. The two that do not differ in how they fail. Registering one class per call describes no inheritance and initialize reports missing_base. But listing a class with an ancestor in place of its direct base - `(Animal, Bulldog)` when Bulldog derives from Dog - is accepted: initialize succeeds, and a call passing a Bulldog quietly runs the overrider for Animal rather than the one for Dog. That silence is what makes it worth a paragraph. These stay as @code. They are contrasting registrations of one hierarchy, two of them wrong on purpose, so a compiled snippet would have to be four translation units to say what four blocks say plainly. Also reorders the macro table in ref_macros.adoc to declare, override, register rather than alphabetically. --- doc/modules/ROOT/pages/ref_macros.adoc | 4 +-- include/boost/openmethod/macros.hpp | 39 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_macros.adoc b/doc/modules/ROOT/pages/ref_macros.adoc index 90dd589c..c3250ee9 100644 --- a/doc/modules/ROOT/pages/ref_macros.adoc +++ b/doc/modules/ROOT/pages/ref_macros.adoc @@ -8,12 +8,12 @@ uses of the library. |=== | Name | Description. -| xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[*BOOST_OPENMETHOD_CLASSES*] -| Registers classes. | xref:reference:BOOST_OPENMETHOD.adoc[*BOOST_OPENMETHOD*] | Declares a method. | xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[*BOOST_OPENMETHOD_OVERRIDE*] | Adds an overrider to a method. +| xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[*BOOST_OPENMETHOD_CLASSES*] +| Registers classes. | xref:reference:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] | Adds an overrider to a method as an inline function. | xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index e19f2a59..192c6d13 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -490,6 +490,45 @@ inline constexpr bool method_not_found = false; //! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when `` is //! included. Subsequently changing it has no retroactive effect. //! +//! @par Examples +//! +//! A class and its direct bases must appear together in one call. Take `Cat` +//! and `Dog`, both derived from `Animal`, and `Bulldog`, derived from `Dog`. +//! A single call listing all of them describes the hierarchy: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, Bulldog); +//! @endcode +//! +//! Several calls do just as well, as long as every class appears alongside its +//! direct bases. `Dog` is listed twice here, and that is what attaches +//! `Bulldog` to it: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); +//! BOOST_OPENMETHOD_CLASSES(Dog, Bulldog); +//! @endcode +//! +//! Registering the classes one per call describes no inheritance at all, and +//! @ref boost::openmethod::initialize reports a +//! @ref boost::openmethod::missing_base error: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal); +//! BOOST_OPENMETHOD_CLASSES(Cat); +//! BOOST_OPENMETHOD_CLASSES(Dog); // initialize reports missing_base +//! @endcode +//! +//! Listing a class with an ancestor in place of its direct base is the more +//! dangerous mistake, because nothing reports it. Below, `Bulldog` is recorded +//! as derived from `Animal`; an overrider for `Dog` no longer applies to it, so +//! a call passing a `Bulldog` quietly selects the overrider for `Animal`: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); +//! BOOST_OPENMETHOD_CLASSES(Animal, Bulldog); // OpenMethod thinks Bulldog derives from Animal, not Dog +//! @endcode +//! //! @param ... The classes to register, optionally followed by the registry. //! //! @see [Methods and Overriders](xref:ROOT:basics.adoc) From f086985e63ad5662f3218ef9adca493d031385cf Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 11:31:48 -0400 Subject: [PATCH 27/85] type_vptr -> vptr --- include/boost/openmethod/interop/boost_any.hpp | 6 +++--- include/boost/openmethod/interop/std_any.hpp | 6 +++--- include/boost/openmethod/policies/vptr_map.hpp | 4 ++-- include/boost/openmethod/policies/vptr_vector.hpp | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 75f834c4..0bff558d 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -65,7 +65,7 @@ struct virtual_traits { //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the stored value. static auto dynamic_vptr(const boost::any& arg) -> const vptr_type& { - return Registry::vptr::type_vptr(&arg.type()); + return Registry::vptr::vptr(&arg.type()); } //! Cast to a type. @@ -128,7 +128,7 @@ struct virtual_traits { //! @param arg A reference to a `boost::any`. //! @return A reference to the v-table pointer for the stored value. static auto dynamic_vptr(const boost::any& arg) -> const vptr_type& { - return Registry::vptr::type_vptr(&arg.type()); + return Registry::vptr::vptr(&arg.type()); } //! Cast to a type. @@ -191,7 +191,7 @@ struct virtual_traits { //! @param arg A reference to a `boost::any`. //! @return A reference to the v-table pointer for the stored value. static auto dynamic_vptr(const boost::any& arg) -> const vptr_type& { - return Registry::vptr::type_vptr(&arg.type()); + return Registry::vptr::vptr(&arg.type()); } //! Cast to a type. diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 923e56c1..ce5b6515 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -62,7 +62,7 @@ struct virtual_traits { //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the stored value. static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { - return Registry::vptr::type_vptr(&arg.type()); + return Registry::vptr::vptr(&arg.type()); } //! Cast to a type. @@ -114,7 +114,7 @@ struct virtual_traits { //! @param arg A reference to a `std::any`. //! @return A reference to the v-table pointer for the stored value. static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { - return Registry::vptr::type_vptr(&arg.type()); + return Registry::vptr::vptr(&arg.type()); } //! Cast to a type. @@ -167,7 +167,7 @@ struct virtual_traits { //! @param arg A reference to a const `any`. //! @return A reference to a the v-table pointer for `Class`. static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { - return Registry::vptr::type_vptr(&arg.type()); + return Registry::vptr::vptr(&arg.type()); } //! Cast to a type. diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index 677d5400..52cef07d 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -94,7 +94,7 @@ class vptr_map : public vptr { //! @return A reference to a the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { - return type_vptr(Registry::rtti::dynamic_type(arg)); + return vptr(Registry::rtti::dynamic_type(arg)); } //! Returns a *reference* to a v-table pointer for a type. @@ -107,7 +107,7 @@ class vptr_map : public vptr { //! //! @param type A `type_id`. //! @return A reference to a the v-table pointer for `type`. - static auto type_vptr(type_id type) -> const vptr_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 1494ac24..717a5ee4 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -150,7 +150,7 @@ struct vptr_vector : vptr { //! @return A reference to a the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { - return type_vptr(Registry::rtti::dynamic_type(arg)); + return vptr(Registry::rtti::dynamic_type(arg)); }; //! Returns a *reference* to a v-table pointer for a type. @@ -166,7 +166,7 @@ struct vptr_vector : vptr { //! //! @param type A `type_id`. //! @return A reference to a the v-table pointer for `type`. - static auto type_vptr(type_id type) -> const vptr_type& { + static auto vptr(type_id type) -> const vptr_type& { std::size_t index; if constexpr (has_type_hash) { index = type_hash::hash(type); From fcc7bdcc482403c9165654ac674474efe29e241e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 1 Aug 2026 15:08:30 -0400 Subject: [PATCH 28/85] doc: an example for BOOST_OPENMETHOD_INLINE_OVERRIDE Points the macro at the rolex_3 example, which is the case the macro exists for, already compiled and run and already tagged: roles.hpp declares `pay` and supplies a default overrider that three translation units include, and salesman.cpp adds a more specialized one. The second block is what makes the first legible -- it shows the specialized overrider using plain BOOST_OPENMETHOD_OVERRIDE, because it is defined once, and reaching the header's overrider through BOOST_OPENMETHOD_OVERRIDER. The page also said only that the overrider is "marked inline", which does not tell a reader when to reach for it. Adds the reason, from the implementation comment above the macro and from test/dynamic_loading/shared_overrider.hpp: inline is what makes the repeated definition legal, and it is what lets initialize merge the repeated registrations instead of recording them as distinct overriders for one class and marking the call ambiguous. --- include/boost/openmethod/macros.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 192c6d13..9410d7a4 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -464,8 +464,28 @@ inline constexpr bool method_not_found = false; //! @ref BOOST_OPENMETHOD_OVERRIDE, except that the overrider is marked //! `inline`. //! +//! Use it for an overrider defined in a header, where the same definition +//! reaches more than one translation unit. `inline` is what makes the repeated +//! definition legal, and it lets @ref boost::openmethod::initialize merge the +//! repeated registrations. @ref BOOST_OPENMETHOD_OVERRIDE would instead record +//! them as distinct overriders for the same class, making the call ambiguous. +//! //! @note `ID` must be an *identifier*. Qualified names are not allowed. //! +//! @par Example +//! +//! A header that declares a method and supplies a default overrider for it. +//! Every translation unit including it gets the same definition: +//! +//! include:../examples/rolex/3/roles.hpp#content +//! +//! A translation unit that includes the header adds a more specialized +//! overrider of its own. That one is defined once, so it uses +//! @ref BOOST_OPENMETHOD_OVERRIDE; it reaches the header's overrider through +//! @ref BOOST_OPENMETHOD_OVERRIDER: +//! +//! include:../examples/rolex/3/salesman.cpp#content +//! //! @param ID The method's name. //! @param PARAMETERS The overrider's parameter list, in parentheses. //! @param ... The overrider's return type. From 27b06e6b4a2b115413a4ae2aaa42ef6bb406c2b0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 11:44:17 -0400 Subject: [PATCH 29/85] doc: examples for BOOST_OPENMETHOD_DECLARE_OVERRIDER and _DEFINE_OVERRIDER Points DECLARE at the rolex_2 example, the step in that progression that exists to show this split, in three blocks: roles.hpp declares the overrider without a body, employee.cpp supplies it, and salesman.cpp adds a more specialized overrider. The third is the contrast that makes the first two mean something -- an overrider defined in one place needs no split, so it uses plain BOOST_OPENMETHOD_OVERRIDE, and it reaches the declared one through BOOST_OPENMETHOD_OVERRIDER. All three regions were already tagged, and the example is compiled and run. The two macros are halves of one thing, so DEFINE links to DECLARE's Example section rather than repeating it, as BOOST_OPENMETHOD does with BOOST_OPENMETHOD_OVERRIDE. The lead-in says what the pair is for -- splitting an overrider across a header and an implementation file -- which is the part a reader cannot get from "declares an overrider" and "defines the body". --- include/boost/openmethod/macros.hpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 9410d7a4..3bd97078 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -271,6 +271,25 @@ inline constexpr bool method_not_found = false; //! //! @note `ID` must be an *identifier*. Qualified names are not allowed. //! +//! @par Example +//! +//! Use this macro, rather than @ref BOOST_OPENMETHOD_OVERRIDE, to split an +//! overrider across a header and an implementation file. The header declares +//! the overrider without a body: +//! +//! include:../examples/rolex/2/roles.hpp#content +//! +//! The implementation file supplies the body with +//! @ref BOOST_OPENMETHOD_DEFINE_OVERRIDER: +//! +//! include:../examples/rolex/2/employee.cpp#content +//! +//! An overrider that is defined in one place needs no split, and uses +//! @ref BOOST_OPENMETHOD_OVERRIDE. This one reaches the overrider declared in +//! the header through @ref BOOST_OPENMETHOD_OVERRIDER: +//! +//! include:../examples/rolex/2/salesman.cpp#content +//! //! @par Implementation Notes //! //! The macro creates additional entities in the current scope. @@ -352,6 +371,11 @@ inline constexpr bool method_not_found = false; //! //! @note `ID` must be an *identifier*. Qualified names are not allowed. //! +//! @par Example +//! +//! See [BOOST_OPENMETHOD_DECLARE_OVERRIDER](xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc#_example) +//! for an example. +//! //! @param ID The method's name. //! @param PARAMETERS The overrider's parameter list, in parentheses. //! @param ... The overrider's return type. From 51e2355c64e981ec50dd43e58e9872340ec3ad42 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 12:02:01 -0400 Subject: [PATCH 30/85] doc: an example for BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS Shows the two ways to define the symbol, and what defining it buys: the call-time check in snippets/errors.cpp, where Bulldog is never registered and nothing is amiss until a call passes one. Verified both ways before writing it down -- with the symbol the call reports missing_class, without it the call goes through and returns normally. That check also turned up a bug in the snippet. It relied on default_registry carrying runtime_checks, which is only true because the snippets CMakeLists defines BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS in debug builds, so the last of its four examples would have failed in a release build. Compiling errors.cpp with -O2 and no define confirms it: one failure before, none after. The registry now names policies::runtime_checks explicitly. This completes the first table in ref_macros.adoc, the macros described there as sufficient for most uses. --- doc/modules/ROOT/snippets/errors.cpp | 8 ++++++-- include/boost/openmethod/default_registry.hpp | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/modules/ROOT/snippets/errors.cpp b/doc/modules/ROOT/snippets/errors.cpp index ae5f03fd..be8dea2b 100644 --- a/doc/modules/ROOT/snippets/errors.cpp +++ b/doc/modules/ROOT/snippets/errors.cpp @@ -39,9 +39,13 @@ struct marker final : marker_category { struct fn {}; }; +// `runtime_checks` is named explicitly rather than left to +// BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS, which only a debug build defines: +// the class missing from a *call* is caught by that policy, so without it the +// last example below would proceed on a v-table pointer that was never set up. template -using throwing = - default_registry::with, policies::throw_error_handler>; +using throwing = default_registry::with< + marker, policies::runtime_checks, policies::throw_error_handler>; namespace missing_parameter_class { diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index c8915231..87071c65 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -92,6 +92,25 @@ struct indirect_registry : default_registry::with {}; //! `` to enable runtime checks. See //! @ref boost::openmethod::default_registry for details. //! +//! @par Example +//! +//! Define the symbol before including the library, or on the compiler command +//! line: +//! +//! @code +//! #define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS +//! #include +//! @endcode +//! +//! The checks catch what @ref boost::openmethod::initialize cannot. Below, +//! `Bulldog` is never registered; nothing is amiss until a call passes one, +//! and only then is @ref boost::openmethod::missing_class reported: +//! +//! include:errors.cpp#missing_class_in_call;missing_class_in_call_use +//! +//! Without the checks the same call proceeds on a v-table pointer that was +//! never set up, and the behavior is undefined. +//! //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) #define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS #endif From 7414b78050465d53ee92516e27552c02f584cda3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 12:23:30 -0400 Subject: [PATCH 31/85] doc: the initialize report example checks and bails, as a program would The `report` region asserted the counts with BOOST_TEST. It now shows the branch a program would write: report on stderr that some methods are ambiguous or not implemented, point at BOOST_OPENMETHOD_TRACE, exit. The assertions stay, outside the tag. Also rewords the lead-in to the salesman.cpp block on BOOST_OPENMETHOD_DECLARE_OVERRIDER: what that overrider illustrates is that a specific overrider can be called explicitly from another, with no dynamic dispatch. --- doc/modules/ROOT/snippets/initialize.cpp | 9 ++++++++- include/boost/openmethod/macros.hpp | 14 ++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/doc/modules/ROOT/snippets/initialize.cpp b/doc/modules/ROOT/snippets/initialize.cpp index 53ffb09d..d050a677 100644 --- a/doc/modules/ROOT/snippets/initialize.cpp +++ b/doc/modules/ROOT/snippets/initialize.cpp @@ -33,9 +33,16 @@ BOOST_AUTO_TEST_CASE(initialize_report) { // tag::report[] auto report = bom::initialize(bom::trace::from_env()).report; + if (report.not_implemented != 0 || report.ambiguous) { + std::cerr << "some methods are ambiguous or not implemented for " + "some combinations of virtual arguments\n" + "set BOOST_OPENMETHOD_TRACE=1 to troubleshoot\n"; + exit(1); + } + // end::report[] + BOOST_TEST(report.not_implemented == 0); BOOST_TEST(report.ambiguous == 0); - // end::report[] Dog snoopy; BOOST_TEST(trick(bom::virtual_ptr(snoopy)) == "spin"); diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 3bd97078..7eac6221 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -284,9 +284,8 @@ inline constexpr bool method_not_found = false; //! //! include:../examples/rolex/2/employee.cpp#content //! -//! An overrider that is defined in one place needs no split, and uses -//! @ref BOOST_OPENMETHOD_OVERRIDE. This one reaches the overrider declared in -//! the header through @ref BOOST_OPENMETHOD_OVERRIDER: +//! This specific overrider can be called from other overriders explictly. No +//! dynamic dispatch is performed. //! //! include:../examples/rolex/2/salesman.cpp#content //! @@ -302,11 +301,10 @@ inline constexpr bool method_not_found = false; //! //! @li A specialization of the container for the overrider: //! @code -//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { -//! static auto fn(PARAMETERS...) -> RETURN_TYPE; -//! static auto has_next() -> bool; -//! template -//! static auto next(typename... Args) -> RETURN_TYPE; +//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { static +//! auto fn(PARAMETERS...) -> RETURN_TYPE; static auto has_next() -> bool; +//! template static auto next(typename... Args) -> +//! RETURN_TYPE; //! }; //! @endcode //! From 58c0fa8f9b5df09e7cdd976d8073b04eff77a448 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 12:23:30 -0400 Subject: [PATCH 32/85] doc: error examples show the mistake, not the harness The four regions in snippets/errors.cpp rendered BOOST_CHECK_THROW(initialize<...>(), missing_class) on three reference pages. Boost.Test scaffolding is not what a reader needs, and the registry carried throw_error_handler, which implied you must select that policy to see the error at all. Each region is now the faulty registration and the one operation that reports it, with the description the library writes as a comment above it. The registry drops throw_error_handler and keeps the default handler. The harness moved out of the tags. It has to install an error handler because of two things the library does: the `output` policy writes to the C stderr stream, which a streambuf redirect cannot capture, and abort() follows as soon as the handler returns, so only throwing gets control back. The installed handler writes the same description to std::cerr -- same std::visit over the error variant as default_error_handler::default_handler -- and throws a type the caller catches, both outside the tagged regions. The assertions check the captured text, so the comments cannot drift from what is printed. The notes on the three pages now describe the default behavior, which is what the examples show. --- doc/modules/ROOT/snippets/errors.cpp | 121 ++++++++++++++---- include/boost/openmethod/default_registry.hpp | 5 + include/boost/openmethod/preamble.hpp | 11 ++ 3 files changed, 113 insertions(+), 24 deletions(-) diff --git a/doc/modules/ROOT/snippets/errors.cpp b/doc/modules/ROOT/snippets/errors.cpp index be8dea2b..9817fd57 100644 --- a/doc/modules/ROOT/snippets/errors.cpp +++ b/doc/modules/ROOT/snippets/errors.cpp @@ -3,15 +3,56 @@ // 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 using namespace boost::openmethod; +// Everything between here and the first example is harness, kept out of the +// tagged regions: what the reference pages show is the mistake and the +// operation that reports it, which is all a reader needs. + +// Redirects std::cerr for the duration of a scope, so that the test can check +// what the error handler below wrote. +struct capture_cerr { + std::ostringstream captured; + std::streambuf* previous = std::cerr.rdbuf(captured.rdbuf()); + + ~capture_cerr() { + std::cerr.rdbuf(previous); + } + + auto str() const -> std::string { + return captured.str(); + } +}; + +// Thrown only to unwind out of an example: the library calls `abort` as soon as +// the error handler returns, and an error handler may prevent that only by +// throwing. +struct reported {}; + +// Reports the error the way the default handler does, but on std::cerr. The +// `output` policy writes to the C `stderr` stream, which a streambuf redirect +// cannot intercept, so `capture_cerr` would see nothing otherwise. +template +auto report_on_cerr() -> void { + Registry::error_handler::set([](const auto& error) { + std::visit( + [](auto&& e) { e.template write(std::cerr); }, error); + std::cerr << "\n"; + throw reported{}; + }); +} + struct Animal { virtual ~Animal() { } @@ -20,8 +61,7 @@ struct Dog : Animal {}; struct Bulldog : Dog {}; // Each example below registers its classes in a registry of its own, so that -// one deliberate mistake does not affect the others. They all throw rather -// than abort, which is what `throw_error_handler` is for. +// one deliberate mistake does not affect the others. // // Registries that derive from the same `registry<...>` specialization share // one state, so deriving all of them from a single alias would pool the @@ -44,12 +84,11 @@ struct marker final : marker_category { // the class missing from a *call* is caught by that policy, so without it the // last example below would proceed on a v-table pointer that was never set up. template -using throwing = default_registry::with< - marker, policies::runtime_checks, policies::throw_error_handler>; +using reporting = default_registry::with, policies::runtime_checks>; namespace missing_parameter_class { -struct missing_parameter : throwing<1> {}; +struct missing_parameter : reporting<1> {}; // tag::missing_class_in_method[] BOOST_OPENMETHOD_CLASSES(Dog, missing_parameter); // Animal is missing @@ -66,7 +105,7 @@ BOOST_OPENMETHOD_OVERRIDE( namespace missing_overrider_class { -struct missing_overrider : throwing<2> {}; +struct missing_overrider : reporting<2> {}; // tag::missing_class_in_overrider[] BOOST_OPENMETHOD_CLASSES(Animal, missing_overrider); // Dog is missing @@ -83,7 +122,7 @@ BOOST_OPENMETHOD_OVERRIDE( namespace missing_call_class { -struct missing_argument : throwing<3> {}; +struct missing_argument : reporting<3> {}; // tag::missing_class_in_call[] BOOST_OPENMETHOD_CLASSES(Animal, Dog, missing_argument); // Bulldog is missing @@ -100,7 +139,7 @@ BOOST_OPENMETHOD_OVERRIDE( namespace unrelated_registration { -struct unrelated_classes : throwing<4> {}; +struct unrelated_classes : reporting<4> {}; // tag::missing_base[] // registered separately, so the inheritance is never seen @@ -119,7 +158,7 @@ BOOST_OPENMETHOD_OVERRIDE( namespace related_registration { -struct related_classes : throwing<5> {}; +struct related_classes : reporting<5> {}; // tag::missing_base_fix[] BOOST_OPENMETHOD_CLASSES(Animal, Dog, related_classes); @@ -137,37 +176,71 @@ BOOST_OPENMETHOD_OVERRIDE( BOOST_AUTO_TEST_CASE(missing_class_errors) { { using namespace missing_parameter_class; - // tag::missing_class_in_method_init[] - BOOST_CHECK_THROW(initialize(), missing_class); - // end::missing_class_in_method_init[] + capture_cerr cerr; + report_on_cerr(); + + try { + // tag::missing_class_in_method_init[] + // error: unknown class Animal + initialize(); + // end::missing_class_in_method_init[] + } catch (const reported&) { + } + + BOOST_TEST(cerr.str().find("Animal") != std::string::npos); } { using namespace missing_overrider_class; - // tag::missing_class_in_overrider_init[] - BOOST_CHECK_THROW(initialize(), missing_class); - // end::missing_class_in_overrider_init[] + capture_cerr cerr; + report_on_cerr(); + + try { + // tag::missing_class_in_overrider_init[] + // error: unknown class Dog + initialize(); + // end::missing_class_in_overrider_init[] + } catch (const reported&) { + } + + BOOST_TEST(cerr.str().find("Dog") != std::string::npos); } { using namespace missing_call_class; initialize(); + capture_cerr cerr; + report_on_cerr(); + + try { + // tag::missing_class_in_call_use[] + Bulldog hector; - // tag::missing_class_in_call_use[] - Bulldog hector; + // error: unknown class Bulldog + poke(virtual_ptr(hector)); + // end::missing_class_in_call_use[] + } catch (const reported&) { + } - BOOST_CHECK_THROW( - poke(virtual_ptr(hector)), missing_class); - // end::missing_class_in_call_use[] + BOOST_TEST(cerr.str().find("Bulldog") != std::string::npos); } } BOOST_AUTO_TEST_CASE(missing_base_errors) { { using namespace unrelated_registration; - // tag::missing_base_init[] - BOOST_CHECK_THROW(initialize(), missing_base); - // end::missing_base_init[] + capture_cerr cerr; + report_on_cerr(); + + try { + // tag::missing_base_init[] + // error: missing base Animal -<| Dog + initialize(); + // end::missing_base_init[] + } catch (const reported&) { + } + + BOOST_TEST(cerr.str().find("missing base") != std::string::npos); } { diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 87071c65..2fca04f2 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -102,6 +102,11 @@ struct indirect_registry : default_registry::with {}; //! #include //! @endcode //! +//! @note The error goes to the registry's +//! @ref boost::openmethod::policies::error_handler policy, which writes the +//! description shown in the comments; the program is then terminated. A +//! handler may throw instead, to keep the program running. +//! //! The checks catch what @ref boost::openmethod::initialize cannot. Below, //! `Bulldog` is never registered; nothing is amiss until a call passes one, //! and only then is @ref boost::openmethod::missing_class reported: diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 5ab51054..c3da5c0e 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -158,6 +158,11 @@ struct not_initialized : openmethod_error { //! //! @par Examples //! +//! @note The error goes to the registry's +//! @ref boost::openmethod::policies::error_handler policy, which writes the +//! description shown in the comments; the program is then terminated. A +//! handler may throw instead, to keep the program running. +//! //! Missing registration of a class used as a virtual parameter in a method: //! //! include:errors.cpp#missing_class_in_method;missing_class_in_method_init @@ -190,6 +195,12 @@ struct missing_class : openmethod_error { //! parameter list. //! //! @par Example +//! +//! @note The error goes to the registry's +//! @ref boost::openmethod::policies::error_handler policy, which writes the +//! description shown in the comments; the program is then terminated. A +//! handler may throw instead, to keep the program running. +//! //! In the following code, OpenMethod cannot infer that `Dog` is derived from //! `Animal`, because they are not registered in a same call to @ref //! BOOST_OPENMETHOD_CLASSES. From e375b198d758c84a959799d8b6fcf543559a5bce Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 14:08:12 -0400 Subject: [PATCH 33/85] doc: harness out of every snippet, registry out of the error ones Applies the treatment errors.cpp got to the rest, and finishes the job on errors.cpp itself. Assertions split two ways. Pointer and v-table identity -- the ~72 in virtual_ptr.cpp, plus the use_count ones -- stay as BOOST_TEST: stating which object and which v-table a pointer holds is what those examples are for. The 18 that checked what a method returned now print it, with the output as a trailing comment and the capture and BOOST_TEST outside the tag, the way macros.cpp already worked. Two shared helpers in capture.hpp replace what would have been six copies of the same redirect. The two error-handler examples in policies.cpp keep their try/catch -- there the fact that the error reaches your code is the example, not scaffolding -- and lose only BOOST_CHECK_THROW. initialize.cpp's exit(1) is neutralised by a #define outside the tag, as it is an example of what a program does, not something a test may do. errors.cpp is split into four translation units, one per mistake. It had carried a registry argument on every line so that one deliberate mistake could not poison another; separate translation units give that isolation for free, so the examples now use the default registry and no line mentions a registry at all. The marker-policy device and the reporting alias go with it. The call example defines BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS itself rather than relying on a debug build, which is also what its page documents; verified at -O2 with no external define. --- doc/modules/ROOT/snippets/capture.hpp | 39 +++ doc/modules/ROOT/snippets/error_harness.hpp | 47 ++++ doc/modules/ROOT/snippets/errors.cpp | 253 ------------------ .../ROOT/snippets/errors_missing_base.cpp | 44 +++ .../snippets/errors_missing_class_call.cpp | 56 ++++ .../snippets/errors_missing_class_method.cpp | 42 +++ .../errors_missing_class_overrider.cpp | 42 +++ doc/modules/ROOT/snippets/initialize.cpp | 11 + doc/modules/ROOT/snippets/inplace_vptr.cpp | 10 +- doc/modules/ROOT/snippets/intrusive_ptr.cpp | 25 +- doc/modules/ROOT/snippets/macros.cpp | 9 +- doc/modules/ROOT/snippets/policies.cpp | 32 ++- doc/modules/ROOT/snippets/smart_pointers.cpp | 37 ++- doc/modules/ROOT/snippets/static_rtti.cpp | 9 +- doc/modules/ROOT/snippets/virtual_ptr.cpp | 11 +- include/boost/openmethod/default_registry.hpp | 2 +- include/boost/openmethod/macros.hpp | 9 +- include/boost/openmethod/preamble.hpp | 10 +- 18 files changed, 395 insertions(+), 293 deletions(-) create mode 100644 doc/modules/ROOT/snippets/capture.hpp create mode 100644 doc/modules/ROOT/snippets/error_harness.hpp delete mode 100644 doc/modules/ROOT/snippets/errors.cpp create mode 100644 doc/modules/ROOT/snippets/errors_missing_base.cpp create mode 100644 doc/modules/ROOT/snippets/errors_missing_class_call.cpp create mode 100644 doc/modules/ROOT/snippets/errors_missing_class_method.cpp create mode 100644 doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp diff --git a/doc/modules/ROOT/snippets/capture.hpp b/doc/modules/ROOT/snippets/capture.hpp new file mode 100644 index 00000000..f48f1269 --- /dev/null +++ b/doc/modules/ROOT/snippets/capture.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2018-2025 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) + +// Harness for the snippets in this directory, never part of a tagged region: +// the reference pages show what a program would write, and the capture lets the +// test check that it wrote it. +// +// Note that the library's own `output` policy writes to the C `stderr` stream, +// which a streambuf redirect cannot intercept; only what an example prints +// itself is captured. + +#ifndef BOOST_OPENMETHOD_SNIPPETS_CAPTURE_HPP +#define BOOST_OPENMETHOD_SNIPPETS_CAPTURE_HPP + +#include +#include +#include + +// Redirects a standard stream for the duration of a scope. +template +struct capture_stream { + std::ostringstream captured; + std::streambuf* previous = Stream->rdbuf(captured.rdbuf()); + + ~capture_stream() { + Stream->rdbuf(previous); + } + + auto str() const -> std::string { + return captured.str(); + } +}; + +using capture_cout = capture_stream<&std::cout>; +using capture_cerr = capture_stream<&std::cerr>; + +#endif diff --git a/doc/modules/ROOT/snippets/error_harness.hpp b/doc/modules/ROOT/snippets/error_harness.hpp new file mode 100644 index 00000000..1cd27074 --- /dev/null +++ b/doc/modules/ROOT/snippets/error_harness.hpp @@ -0,0 +1,47 @@ +// Copyright (c) 2018-2025 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) + +// Harness for the error snippets, never part of a tagged region: the reference +// pages show the mistake and the operation that reports it, and nothing else. +// +// Each of those snippets lives in a translation unit of its own, so that one +// deliberate mistake cannot affect another and the examples can use the default +// registry -- which is what keeps a registry argument out of every line. + +#ifndef BOOST_OPENMETHOD_SNIPPETS_ERROR_HARNESS_HPP +#define BOOST_OPENMETHOD_SNIPPETS_ERROR_HARNESS_HPP + +#include +#include + +#include "capture.hpp" + +// Thrown only to unwind out of an example: the library calls `abort` as soon as +// the error handler returns, and a handler may prevent that only by throwing. +struct reported {}; + +// Reports the error the way the default handler does, but on std::cerr. The +// `output` policy writes to the C `stderr` stream, which a streambuf redirect +// cannot intercept, so `capture_cerr` would see nothing otherwise. +template +auto report_on_cerr() -> void { + Registry::error_handler::set([](const auto& error) { + std::visit( + [](auto&& e) { e.template write(std::cerr); }, error); + std::cerr << "\n"; + throw reported{}; + }); +} + +// Runs `f`, swallowing the unwind that `report_on_cerr`'s handler throws. +template +auto reporting(F&& f) -> void { + try { + f(); + } catch (const reported&) { + } +} + +#endif diff --git a/doc/modules/ROOT/snippets/errors.cpp b/doc/modules/ROOT/snippets/errors.cpp deleted file mode 100644 index 9817fd57..00000000 --- a/doc/modules/ROOT/snippets/errors.cpp +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) 2018-2025 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; - -// Everything between here and the first example is harness, kept out of the -// tagged regions: what the reference pages show is the mistake and the -// operation that reports it, which is all a reader needs. - -// Redirects std::cerr for the duration of a scope, so that the test can check -// what the error handler below wrote. -struct capture_cerr { - std::ostringstream captured; - std::streambuf* previous = std::cerr.rdbuf(captured.rdbuf()); - - ~capture_cerr() { - std::cerr.rdbuf(previous); - } - - auto str() const -> std::string { - return captured.str(); - } -}; - -// Thrown only to unwind out of an example: the library calls `abort` as soon as -// the error handler returns, and an error handler may prevent that only by -// throwing. -struct reported {}; - -// Reports the error the way the default handler does, but on std::cerr. The -// `output` policy writes to the C `stderr` stream, which a streambuf redirect -// cannot intercept, so `capture_cerr` would see nothing otherwise. -template -auto report_on_cerr() -> void { - Registry::error_handler::set([](const auto& error) { - std::visit( - [](auto&& e) { e.template write(std::cerr); }, error); - std::cerr << "\n"; - throw reported{}; - }); -} - -struct Animal { - virtual ~Animal() { - } -}; -struct Dog : Animal {}; -struct Bulldog : Dog {}; - -// Each example below registers its classes in a registry of its own, so that -// one deliberate mistake does not affect the others. -// -// Registries that derive from the same `registry<...>` specialization share -// one state, so deriving all of them from a single alias would pool the -// registrations and mask the mistakes. A marker policy carrying an integer -// gives each a distinct base -- the same device as `test_registry_` in -// test/test_util.hpp. - -struct marker_category { - using category = marker_category; -}; - -template -struct marker final : marker_category { - template - struct fn {}; -}; - -// `runtime_checks` is named explicitly rather than left to -// BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS, which only a debug build defines: -// the class missing from a *call* is caught by that policy, so without it the -// last example below would proceed on a v-table pointer that was never set up. -template -using reporting = default_registry::with, policies::runtime_checks>; - -namespace missing_parameter_class { - -struct missing_parameter : reporting<1> {}; - -// tag::missing_class_in_method[] -BOOST_OPENMETHOD_CLASSES(Dog, missing_parameter); // Animal is missing - -BOOST_OPENMETHOD( - poke, (virtual_ptr), void, missing_parameter); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (virtual_ptr), void) { /* ... */ -} -// end::missing_class_in_method[] - -} // namespace missing_parameter_class - -namespace missing_overrider_class { - -struct missing_overrider : reporting<2> {}; - -// tag::missing_class_in_overrider[] -BOOST_OPENMETHOD_CLASSES(Animal, missing_overrider); // Dog is missing - -BOOST_OPENMETHOD( - poke, (virtual_ptr), void, missing_overrider); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (virtual_ptr), void) { /* ... */ -} -// end::missing_class_in_overrider[] - -} // namespace missing_overrider_class - -namespace missing_call_class { - -struct missing_argument : reporting<3> {}; - -// tag::missing_class_in_call[] -BOOST_OPENMETHOD_CLASSES(Animal, Dog, missing_argument); // Bulldog is missing - -BOOST_OPENMETHOD( - poke, (virtual_ptr), void, missing_argument); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (virtual_ptr), void) { /* ... */ -} -// end::missing_class_in_call[] - -} // namespace missing_call_class - -namespace unrelated_registration { - -struct unrelated_classes : reporting<4> {}; - -// tag::missing_base[] -// registered separately, so the inheritance is never seen -BOOST_OPENMETHOD_CLASSES(Animal, unrelated_classes); -BOOST_OPENMETHOD_CLASSES(Dog, unrelated_classes); - -BOOST_OPENMETHOD( - poke, (virtual_ptr), void, unrelated_classes); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (virtual_ptr), void) { /* ... */ -} -// end::missing_base[] - -} // namespace unrelated_registration - -namespace related_registration { - -struct related_classes : reporting<5> {}; - -// tag::missing_base_fix[] -BOOST_OPENMETHOD_CLASSES(Animal, Dog, related_classes); -// end::missing_base_fix[] - -BOOST_OPENMETHOD( - poke, (virtual_ptr), void, related_classes); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (virtual_ptr), void) { /* ... */ -} - -} // namespace related_registration - -BOOST_AUTO_TEST_CASE(missing_class_errors) { - { - using namespace missing_parameter_class; - capture_cerr cerr; - report_on_cerr(); - - try { - // tag::missing_class_in_method_init[] - // error: unknown class Animal - initialize(); - // end::missing_class_in_method_init[] - } catch (const reported&) { - } - - BOOST_TEST(cerr.str().find("Animal") != std::string::npos); - } - - { - using namespace missing_overrider_class; - capture_cerr cerr; - report_on_cerr(); - - try { - // tag::missing_class_in_overrider_init[] - // error: unknown class Dog - initialize(); - // end::missing_class_in_overrider_init[] - } catch (const reported&) { - } - - BOOST_TEST(cerr.str().find("Dog") != std::string::npos); - } - - { - using namespace missing_call_class; - initialize(); - capture_cerr cerr; - report_on_cerr(); - - try { - // tag::missing_class_in_call_use[] - Bulldog hector; - - // error: unknown class Bulldog - poke(virtual_ptr(hector)); - // end::missing_class_in_call_use[] - } catch (const reported&) { - } - - BOOST_TEST(cerr.str().find("Bulldog") != std::string::npos); - } -} - -BOOST_AUTO_TEST_CASE(missing_base_errors) { - { - using namespace unrelated_registration; - capture_cerr cerr; - report_on_cerr(); - - try { - // tag::missing_base_init[] - // error: missing base Animal -<| Dog - initialize(); - // end::missing_base_init[] - } catch (const reported&) { - } - - BOOST_TEST(cerr.str().find("missing base") != std::string::npos); - } - - { - using namespace related_registration; - initialize(); - - Dog snoopy; - poke(virtual_ptr(snoopy)); - } -} diff --git a/doc/modules/ROOT/snippets/errors_missing_base.cpp b/doc/modules/ROOT/snippets/errors_missing_base.cpp new file mode 100644 index 00000000..70eaf0dd --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_base.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; + +// tag::classes[] +// registered separately, so the inheritance is never seen +BOOST_OPENMETHOD_CLASSES(Animal); +BOOST_OPENMETHOD_CLASSES(Dog); + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_base_error) { + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::init[] + // aborts with error message: missing base Animal -<| Dog + initialize(); + // end::init[] + }); + + BOOST_TEST(cerr.str().find("missing base") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/errors_missing_class_call.cpp b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp new file mode 100644 index 00000000..45e48593 --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2018-2025 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) + +// The class missing from a *call* is caught by the `runtime_checks` policy, +// which `default_registry` carries only when this symbol is defined. +#define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS + +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +// The registration below is also the `fix` example on the missing_base page, +// hence the nested tag. +// tag::classes[] +// Bulldog is missing +// tag::fix[] +BOOST_OPENMETHOD_CLASSES(Animal, Dog); +// end::fix[] + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_class_in_call) { + initialize(); + + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::use[] + Bulldog hector; + + // aborts with error message: unknown class Bulldog + poke(hector); + // end::use[] + }); + + BOOST_TEST(cerr.str().find("Bulldog") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/errors_missing_class_method.cpp b/doc/modules/ROOT/snippets/errors_missing_class_method.cpp new file mode 100644 index 00000000..96db8e9f --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_class_method.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; + +// tag::classes[] +BOOST_OPENMETHOD_CLASSES(Dog); // Animal is missing + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_class_in_method) { + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::init[] + // aborts with error message: unknown class Animal + initialize(); + // end::init[] + }); + + BOOST_TEST(cerr.str().find("Animal") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp b/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp new file mode 100644 index 00000000..8c13a030 --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2018-2025 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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; + +// tag::classes[] +BOOST_OPENMETHOD_CLASSES(Animal); // Dog is missing + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_class_in_overrider) { + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::init[] + // aborts with error message: unknown class Dog + initialize(); + // end::init[] + }); + + BOOST_TEST(cerr.str().find("Dog") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/initialize.cpp b/doc/modules/ROOT/snippets/initialize.cpp index d050a677..c51ad1d1 100644 --- a/doc/modules/ROOT/snippets/initialize.cpp +++ b/doc/modules/ROOT/snippets/initialize.cpp @@ -9,6 +9,8 @@ #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + namespace bom = boost::openmethod; struct Animal { @@ -30,6 +32,12 @@ BOOST_OPENMETHOD_OVERRIDE(trick, (bom::virtual_ptr), std::string) { } BOOST_AUTO_TEST_CASE(initialize_report) { + capture_cerr cerr; + +// The example ends with `exit(1)`, as a program would; make it expand to +// nothing so that the test can carry on. +#define exit(code) + // tag::report[] auto report = bom::initialize(bom::trace::from_env()).report; @@ -41,8 +49,11 @@ BOOST_AUTO_TEST_CASE(initialize_report) { } // end::report[] +#undef exit + BOOST_TEST(report.not_implemented == 0); BOOST_TEST(report.ambiguous == 0); + BOOST_TEST(cerr.str().empty()); Dog snoopy; BOOST_TEST(trick(bom::virtual_ptr(snoopy)) == "spin"); diff --git a/doc/modules/ROOT/snippets/inplace_vptr.cpp b/doc/modules/ROOT/snippets/inplace_vptr.cpp index 61d79693..df139fdc 100644 --- a/doc/modules/ROOT/snippets/inplace_vptr.cpp +++ b/doc/modules/ROOT/snippets/inplace_vptr.cpp @@ -12,6 +12,8 @@ #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + using namespace boost::openmethod; // tag::classes[] @@ -33,13 +35,17 @@ BOOST_OPENMETHOD_OVERRIDE(trick, (Dog&), std::string) { // end::classes[] BOOST_AUTO_TEST_CASE(inplace_vptr_examples) { + capture_cout cout; + // tag::dispatch[] initialize(); std::unique_ptr a = std::make_unique(); std::unique_ptr b = std::make_unique(); - BOOST_TEST(trick(*a) == "sulk"); - BOOST_TEST(trick(*b) == "spin"); + std::cout << trick(*a) << "\n"; // sulk + std::cout << trick(*b) << "\n"; // spin // end::dispatch[] + + BOOST_TEST(cout.str() == "sulk\nspin\n"); } diff --git a/doc/modules/ROOT/snippets/intrusive_ptr.cpp b/doc/modules/ROOT/snippets/intrusive_ptr.cpp index f65a14eb..409a7ae1 100644 --- a/doc/modules/ROOT/snippets/intrusive_ptr.cpp +++ b/doc/modules/ROOT/snippets/intrusive_ptr.cpp @@ -13,6 +13,8 @@ #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + using namespace boost::openmethod; // tag::classes[] @@ -83,12 +85,16 @@ BOOST_AUTO_TEST_CASE(intrusive_ptr_examples) { { using namespace vptr; + capture_cout cout; + // tag::make_boost_intrusive_virtual[] boost_intrusive_virtual_ptr animal = make_boost_intrusive_virtual(); - BOOST_TEST(poke(animal) == "bark"); + std::cout << poke(animal) << "\n"; // bark // end::make_boost_intrusive_virtual[] + + BOOST_TEST(cout.str() == "bark\n"); } { @@ -103,19 +109,30 @@ BOOST_AUTO_TEST_CASE(intrusive_ptr_examples) { { using namespace by_value; + capture_cout cout; + // tag::by_value_call[] - BOOST_TEST(poke(boost::intrusive_ptr(new Dog)) == "bark"); - BOOST_TEST(poke(boost::intrusive_ptr(new Cat)) == "hiss"); + std::cout << poke(boost::intrusive_ptr(new Dog)) + << "\n"; // bark + std::cout << poke(boost::intrusive_ptr(new Cat)) + << "\n"; // hiss // end::by_value_call[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); } { using namespace by_reference; + capture_cout cout; + // tag::by_reference_call[] const boost::intrusive_ptr snoopy(new Dog); - BOOST_TEST(poke(snoopy) == "bark"); + std::cout << poke(snoopy) << "\n"; // bark + BOOST_TEST(snoopy->use_count() == 1); // end::by_reference_call[] + + BOOST_TEST(cout.str() == "bark\n"); } } diff --git a/doc/modules/ROOT/snippets/macros.cpp b/doc/modules/ROOT/snippets/macros.cpp index 81e9a5a5..3af63dc0 100644 --- a/doc/modules/ROOT/snippets/macros.cpp +++ b/doc/modules/ROOT/snippets/macros.cpp @@ -12,6 +12,8 @@ #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + using namespace boost::openmethod; struct Animal { @@ -41,8 +43,7 @@ BOOST_OPENMETHOD_OVERRIDE( BOOST_AUTO_TEST_CASE(macro_examples) { initialize(); - std::ostringstream captured; - auto* previous = std::cout.rdbuf(captured.rdbuf()); + capture_cout cout; // tag::call[] Cat felix; @@ -54,7 +55,5 @@ BOOST_AUTO_TEST_CASE(macro_examples) { poke(b, std::cout); // bark // end::call[] - std::cout.rdbuf(previous); - - BOOST_TEST(captured.str() == "hissbark"); + BOOST_TEST(cout.str() == "hissbark"); } diff --git a/doc/modules/ROOT/snippets/policies.cpp b/doc/modules/ROOT/snippets/policies.cpp index 73281b0d..e67f3371 100644 --- a/doc/modules/ROOT/snippets/policies.cpp +++ b/doc/modules/ROOT/snippets/policies.cpp @@ -14,6 +14,8 @@ #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + using namespace boost::openmethod; struct Animal { @@ -184,14 +186,17 @@ BOOST_AUTO_TEST_CASE(rtti_and_storage) { { using namespace std_rtti_demo; initialize(); + capture_cout cout; // tag::std_rtti_dispatch[] Dog snoopy; Animal& animal = snoopy; - BOOST_TEST( - trick(virtual_ptr(animal)) == "spin"); + std::cout << trick(virtual_ptr(animal)) + << "\n"; // spin // end::std_rtti_dispatch[] + + BOOST_TEST(cout.str() == "spin\n"); } { @@ -235,6 +240,8 @@ BOOST_AUTO_TEST_CASE(error_handlers) { using namespace default_error_handler_demo; initialize(); + capture_cerr cerr; + // tag::default_error_handler_set[] handled_registry::error_handler::set([](const auto& error) { if (std::holds_alternative(error)) { @@ -244,21 +251,32 @@ BOOST_AUTO_TEST_CASE(error_handlers) { Cat felix; - BOOST_CHECK_THROW( - trick(virtual_ptr(felix)), - std::runtime_error); + try { + trick(virtual_ptr(felix)); + } catch (const std::runtime_error& error) { + std::cerr << error.what() << "\n"; // not implemented + } // end::default_error_handler_set[] + + BOOST_TEST(cerr.str() == "not implemented\n"); } { using namespace throw_error_handler_demo; initialize(); + capture_cerr cerr; + // tag::throw_error_handler_catch[] Cat felix; - BOOST_CHECK_THROW( - trick(virtual_ptr(felix)), no_overrider); + try { + trick(virtual_ptr(felix)); + } catch (const no_overrider&) { + std::cerr << "no overrider for Cat\n"; + } // end::throw_error_handler_catch[] + + BOOST_TEST(cerr.str() == "no overrider for Cat\n"); } } diff --git a/doc/modules/ROOT/snippets/smart_pointers.cpp b/doc/modules/ROOT/snippets/smart_pointers.cpp index 4ac0eba8..b4fbd908 100644 --- a/doc/modules/ROOT/snippets/smart_pointers.cpp +++ b/doc/modules/ROOT/snippets/smart_pointers.cpp @@ -11,6 +11,8 @@ #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + using namespace boost::openmethod; // tag::classes[] @@ -106,11 +108,15 @@ BOOST_AUTO_TEST_CASE(shared_ptr_examples) { { using namespace shared_vptr; + capture_cout cout; + // tag::make_shared_virtual[] shared_virtual_ptr animal = make_shared_virtual(); - BOOST_TEST(poke(animal) == "bark"); + std::cout << poke(animal) << "\n"; // bark // end::make_shared_virtual[] + + BOOST_TEST(cout.str() == "bark\n"); } { @@ -124,20 +130,29 @@ BOOST_AUTO_TEST_CASE(shared_ptr_examples) { { using namespace by_value; + capture_cout cout; + // tag::shared_by_value_call[] - BOOST_TEST(poke(std::make_shared()) == "bark"); - BOOST_TEST(poke(std::make_shared()) == "hiss"); + std::cout << poke(std::make_shared()) << "\n"; // bark + std::cout << poke(std::make_shared()) << "\n"; // hiss // end::shared_by_value_call[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); } { using namespace by_reference; + capture_cout cout; + // tag::shared_by_reference_call[] const std::shared_ptr snoopy = std::make_shared(); - BOOST_TEST(poke(snoopy) == "bark"); + std::cout << poke(snoopy) << "\n"; // bark + BOOST_TEST(snoopy.use_count() == 1); // end::shared_by_reference_call[] + + BOOST_TEST(cout.str() == "bark\n"); } } @@ -146,11 +161,15 @@ BOOST_AUTO_TEST_CASE(unique_ptr_examples) { { using namespace unique_vptr; + capture_cout cout; + // tag::make_unique_virtual[] unique_virtual_ptr animal = make_unique_virtual(); - BOOST_TEST(poke(std::move(animal)) == "bark"); + std::cout << poke(std::move(animal)) << "\n"; // bark // end::make_unique_virtual[] + + BOOST_TEST(cout.str() == "bark\n"); } { @@ -165,9 +184,13 @@ BOOST_AUTO_TEST_CASE(unique_ptr_examples) { { using namespace unique; + capture_cout cout; + // tag::unique_by_value_call[] - BOOST_TEST(poke(std::make_unique()) == "bark"); - BOOST_TEST(poke(std::make_unique()) == "hiss"); + std::cout << poke(std::make_unique()) << "\n"; // bark + std::cout << poke(std::make_unique()) << "\n"; // hiss // end::unique_by_value_call[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); } } diff --git a/doc/modules/ROOT/snippets/static_rtti.cpp b/doc/modules/ROOT/snippets/static_rtti.cpp index e3d537d3..cd561f55 100644 --- a/doc/modules/ROOT/snippets/static_rtti.cpp +++ b/doc/modules/ROOT/snippets/static_rtti.cpp @@ -23,6 +23,8 @@ struct static_registry #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + using namespace boost::openmethod::aliases; // tag::classes[] @@ -46,13 +48,16 @@ BOOST_OPENMETHOD_OVERRIDE(trick, (virtual_ptr), std::string) { BOOST_AUTO_TEST_CASE(static_rtti_examples) { boost::openmethod::initialize(); + capture_cout cout; // tag::dispatch[] // the exact class must be known where the pointer is created unique_virtual_ptr a = make_unique_virtual(); unique_virtual_ptr b = make_unique_virtual(); - BOOST_TEST(trick(a) == "sulk"); - BOOST_TEST(trick(b) == "spin"); + std::cout << trick(a) << "\n"; // sulk + std::cout << trick(b) << "\n"; // spin // end::dispatch[] + + BOOST_TEST(cout.str() == "sulk\nspin\n"); } diff --git a/doc/modules/ROOT/snippets/virtual_ptr.cpp b/doc/modules/ROOT/snippets/virtual_ptr.cpp index 002878de..f1ba2fae 100644 --- a/doc/modules/ROOT/snippets/virtual_ptr.cpp +++ b/doc/modules/ROOT/snippets/virtual_ptr.cpp @@ -11,6 +11,8 @@ #define BOOST_TEST_MODULE openmethod #include +#include "capture.hpp" + using namespace boost::openmethod; namespace polymorphic_classes { @@ -40,7 +42,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { namespace non_polymorphic_classes { // tag::non_polymorphic_classes[] -// polymorphism not required +// classes need not be polymorphic struct Animal {}; struct Cat : Animal {}; struct Dog : Animal {}; @@ -234,16 +236,19 @@ BOOST_AUTO_TEST_CASE(virtual_ptr_examples) { { using namespace non_polymorphic_classes; + capture_cout cout; // tag::final_virtual_ptr[] Dog snoopy; virtual_ptr animal = final_virtual_ptr(snoopy); - BOOST_TEST(poke(animal) == "bark"); + std::cout << poke(animal) << "\n"; // bark Cat felix; animal = final_virtual_ptr(felix); - BOOST_TEST(poke(animal) == "hiss"); + std::cout << poke(animal) << "\n"; // hiss // end::final_virtual_ptr[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); } } diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 2fca04f2..9df3c7bc 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -111,7 +111,7 @@ struct indirect_registry : default_registry::with {}; //! `Bulldog` is never registered; nothing is amiss until a call passes one, //! and only then is @ref boost::openmethod::missing_class reported: //! -//! include:errors.cpp#missing_class_in_call;missing_class_in_call_use +//! include:errors_missing_class_call.cpp#classes;use //! //! Without the checks the same call proceeds on a v-table pointer that was //! never set up, and the behavior is undefined. diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 7eac6221..209c50da 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -301,10 +301,11 @@ inline constexpr bool method_not_found = false; //! //! @li A specialization of the container for the overrider: //! @code -//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { static -//! auto fn(PARAMETERS...) -> RETURN_TYPE; static auto has_next() -> bool; -//! template static auto next(typename... Args) -> -//! RETURN_TYPE; +//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { +//! static auto fn(PARAMETERS...) -> RETURN_TYPE; +//! static auto has_next() -> bool; +//! template +//! static auto next(typename... Args) -> RETURN_TYPE; //! }; //! @endcode //! diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index c3da5c0e..7fe9270d 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -165,15 +165,15 @@ struct not_initialized : openmethod_error { //! //! Missing registration of a class used as a virtual parameter in a method: //! -//! include:errors.cpp#missing_class_in_method;missing_class_in_method_init +//! include:errors_missing_class_method.cpp#classes;init //! //! Missing registration of a class used as a virtual parameter in an overrider: //! -//! include:errors.cpp#missing_class_in_overrider;missing_class_in_overrider_init +//! include:errors_missing_class_overrider.cpp#classes;init //! //! Missing registration of a class used as a virtual parameter in a call: //! -//! include:errors.cpp#missing_class_in_call;missing_class_in_call_use +//! include:errors_missing_class_call.cpp#classes;use //! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_class : openmethod_error { @@ -205,11 +205,11 @@ struct missing_class : openmethod_error { //! `Animal`, because they are not registered in a same call to @ref //! BOOST_OPENMETHOD_CLASSES. //! -//! include:errors.cpp#missing_base;missing_base_init +//! include:errors_missing_base.cpp#classes;init //! //! Fix: //! -//! include:errors.cpp#missing_base_fix +//! include:errors_missing_class_call.cpp#fix //! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_base : openmethod_error { From 992a2f2daed872f9d13e4d82f7bd8c7937d9e0ec Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 14:13:04 -0400 Subject: [PATCH 34/85] doc: elided overrider bodies read as a comment, not a block comment `{ /* ... */` on the brace line was clang-format's doing, and it renders awkwardly. A line comment on its own line cannot be folded back up. --- doc/modules/ROOT/snippets/errors_missing_base.cpp | 3 ++- doc/modules/ROOT/snippets/errors_missing_class_call.cpp | 3 ++- doc/modules/ROOT/snippets/errors_missing_class_method.cpp | 3 ++- doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/modules/ROOT/snippets/errors_missing_base.cpp b/doc/modules/ROOT/snippets/errors_missing_base.cpp index 70eaf0dd..c9484d71 100644 --- a/doc/modules/ROOT/snippets/errors_missing_base.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_base.cpp @@ -25,7 +25,8 @@ BOOST_OPENMETHOD_CLASSES(Dog); BOOST_OPENMETHOD(poke, (virtual_ptr), void); -BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... } // end::classes[] diff --git a/doc/modules/ROOT/snippets/errors_missing_class_call.cpp b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp index 45e48593..13195809 100644 --- a/doc/modules/ROOT/snippets/errors_missing_class_call.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp @@ -33,7 +33,8 @@ BOOST_OPENMETHOD_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (virtual_ptr), void); -BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... } // end::classes[] diff --git a/doc/modules/ROOT/snippets/errors_missing_class_method.cpp b/doc/modules/ROOT/snippets/errors_missing_class_method.cpp index 96db8e9f..4dcf6886 100644 --- a/doc/modules/ROOT/snippets/errors_missing_class_method.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_class_method.cpp @@ -23,7 +23,8 @@ BOOST_OPENMETHOD_CLASSES(Dog); // Animal is missing BOOST_OPENMETHOD(poke, (virtual_ptr), void); -BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... } // end::classes[] diff --git a/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp b/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp index 8c13a030..11027c18 100644 --- a/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp @@ -23,7 +23,8 @@ BOOST_OPENMETHOD_CLASSES(Animal); // Dog is missing BOOST_OPENMETHOD(poke, (virtual_ptr), void); -BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... } // end::classes[] From 6640077a727fff32be0135f275ae06bc66ef52bb Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 14:43:51 -0400 Subject: [PATCH 35/85] update comment --- doc/modules/ROOT/snippets/virtual_ptr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/modules/ROOT/snippets/virtual_ptr.cpp b/doc/modules/ROOT/snippets/virtual_ptr.cpp index f1ba2fae..d60db436 100644 --- a/doc/modules/ROOT/snippets/virtual_ptr.cpp +++ b/doc/modules/ROOT/snippets/virtual_ptr.cpp @@ -42,7 +42,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { namespace non_polymorphic_classes { // tag::non_polymorphic_classes[] -// classes need not be polymorphic +// classes not required to be polymorphic struct Animal {}; struct Cat : Animal {}; struct Dog : Animal {}; From 9a9ce1d83046f83ae876ce8c9725e56b9d896faa Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 14:46:11 -0400 Subject: [PATCH 36/85] doc: wrap an over-long comment in the BOOST_OPENMETHOD_CLASSES example --- include/boost/openmethod/macros.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 209c50da..5dc0f46e 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -569,7 +569,8 @@ inline constexpr bool method_not_found = false; //! //! @code //! BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); -//! BOOST_OPENMETHOD_CLASSES(Animal, Bulldog); // OpenMethod thinks Bulldog derives from Animal, not Dog +//! BOOST_OPENMETHOD_CLASSES(Animal, Bulldog); +//! // OpenMethod believes that Bulldog derives from Animal, not Dog //! @endcode //! //! @param ... The classes to register, optionally followed by the registry. From deb5154bc8e3cce7b8205f1af3662ae3bc6687b5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 14:53:50 -0400 Subject: [PATCH 37/85] doc: put the final_virtual_ptr example on the overload people call The example was on the `template` overload, so the plain `final_virtual_ptr(obj)` -- the one a reader reaches for -- had none, and the example looked as though it had been lost. It now sits on the default-registry overload, and the explicit-registry one links to it. That link needed the markup/a override extended. It rewrote `xref:ROOT:` on a nested page as a `link:`, because MrDocs sets relfileprefix there and Asciidoctor folds it into an xref target; a link from one reference page to another has exactly the same problem, and the same fix, minus the extra `../` since those targets are already relative to the reference module root. --- .../generator/adoc/partials/markup/a.adoc.hbs | 11 +++++++++++ include/boost/openmethod/core.hpp | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs b/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs index 536b98c6..29f44c7f 100644 --- a/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs +++ b/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs @@ -21,6 +21,11 @@ it, hence the extra `../`. At the root the href is passed through unchanged, so those links stay real xrefs and Antora still validates them. + The `xref:reference:` branch below is the same treatment for a link from one + reference page to another - a doc comment reaching a specific overload's + Example section, say. Those targets are already relative to the reference + module root, so no extra `../` is needed. + Delete this file once #1245 is resolved upstream. Do not relativize links as asciidoc does not support it. @@ -37,6 +42,12 @@ {{~else~}} {{{href}}}[{{> @partial-block }}] {{~/if~}} +{{~else if (starts_with href "xref:reference:")~}} +{{~#if @root.page.relfileprefix~}} + link:{{{@root.page.relfileprefix}}}{{{replace (remove_prefix href "xref:reference:") ".adoc" ".html"}}}[{{> @partial-block }}] +{{~else~}} + {{{href}}}[{{> @partial-block }}] +{{~/if~}} {{~else if (starts_with href "/")~}} xref:{{{remove_prefix href "/"}}}[{{> @partial-block }}] {{~else if (starts_with href ".")~}} diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 95203a65..a4936d72 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -609,7 +609,9 @@ inline vptr_type null_vptr = nullptr; //! different. //! //! @par Example -//! include:virtual_ptr.cpp#non_polymorphic_classes;final_virtual_ptr +//! +//! See [the default-registry overload](xref:reference:boost/openmethod/final_virtual_ptr-08.adoc#_example) +//! for an example. //! //! @tparam Registry A @ref registry. //! @tparam Arg The type of the argument. @@ -672,6 +674,10 @@ inline auto final_virtual_ptr(Arg&& obj) { //! This is an overload of `final_virtual_ptr` that uses the default //! registry as the `Registry` template parameter. //! +//! @par Example +//! +//! include:virtual_ptr.cpp#non_polymorphic_classes;final_virtual_ptr +//! //! @see @ref final_virtual_ptr // We could give a default value to Registry in the main template, but gcc // doesn't like it. From 24c23140ee62b2dde18709c6b3720c6a790c7df0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 14:56:57 -0400 Subject: [PATCH 38/85] doc: "known exact class", not "known dynamic type" `final_virtual_ptr` consults no dynamic type -- that is the whole point. It takes the argument's static type as the object's class and uses static_vptr for it, skipping the RTTI lookup. "Known dynamic type" says close to the opposite. "Known exact class" is also the phrasing the library already uses for the same idea, in the three interop headers ("Since the exact class of the object is known") and in static_rtti. The two `virtual_ptr::final` members get it too. Their briefs were "Construct a virtual_ptr from a reference to an object" and "... from a smart pointer to an object", which describe an ordinary constructor and leave out the precondition entirely. --- include/boost/openmethod/core.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index a4936d72..06529a5a 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -590,7 +590,7 @@ inline vptr_type null_vptr = nullptr; } // namespace detail -//! Creates a `virtual_ptr` for an object of a known dynamic type. +//! Create a `virtual_ptr` for an object of a known exact class. //! //! Creates a @ref virtual_ptr to an object, setting its v-table pointer //! according to the declared type of its argument. Assumes that the static and @@ -669,7 +669,7 @@ inline auto final_virtual_ptr(Arg&& obj) { detail::box_vptr(vptr)); } -//! Create a `virtual_ptr` for an object of a known dynamic type. +//! Create a `virtual_ptr` for an object of a known exact class. //! //! This is an overload of `final_virtual_ptr` that uses the default //! registry as the `Registry` template parameter. @@ -1041,7 +1041,7 @@ class virtual_ptr { traits::template cast(*obj), vp); } - //! Construct a `virtual_ptr` from a reference to an object + //! Construct a `virtual_ptr` for an object of a known exact class //! //! This function forwards to @ref final_virtual_ptr. //! @@ -1564,7 +1564,8 @@ class virtual_ptr< traits::template cast(std::move(obj)), vp); } - //! Construct a `virtual_ptr` from a smart pointer to an object + //! Construct a `virtual_ptr` from a smart pointer to an object of a known + //! exact class //! //! This function forwards to @ref final_virtual_ptr. //! From a4b84b03538b7b227ea10520025b0732f7f54854 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 2 Aug 2026 14:59:28 -0400 Subject: [PATCH 39/85] doc: say why final_virtual_ptr exists The comment described what it does -- take the static type as the class, use static_vptr -- without saying why anyone would want that. Two reasons, now stated: it looks nothing up at runtime, where constructing from a reference or a pointer goes through the rtti policy for the dynamic type and the vptr policy for the v-table; and it is the only way to build a virtual_ptr under static_rtti, which has no dynamic type to consult and disables those constructors. --- include/boost/openmethod/core.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 06529a5a..34a51048 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -599,6 +599,14 @@ inline vptr_type null_vptr = nullptr; //! //! `Class` is _not_ required to be polymorphic. //! +//! Nothing is looked up at runtime. Constructing a `virtual_ptr` from a +//! reference or a pointer reads the object's dynamic type through the +//! registry's `rtti` policy, then finds the v-table through its `vptr` policy; +//! here the v-table pointer is a static variable, read directly. It is also +//! the only way to create a `virtual_ptr` in a registry that uses +//! @ref policies::static_rtti, which has no dynamic type to consult and +//! disables the constructors that would need one. +//! //! If runtime checks are enabled, and the argument is polymorphic, checks if //! the static and dynamic types are the same. If not, calls the error handler //! with a @ref final_error value, then terminates the program with @ref abort. From 7ecd96ce7c6b69bdc05351d0b7d6185ad15296f3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Fri, 7 Aug 2026 20:39:53 -0400 Subject: [PATCH 40/85] dynamic_vptr -> vptr --- include/boost/openmethod/core.hpp | 33 ++++++++++++------- .../boost/openmethod/interop/boost_any.hpp | 6 ++-- include/boost/openmethod/interop/std_any.hpp | 6 ++-- test/test_dispatch_std_any.cpp | 6 ++-- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 1cc081ee..eefc6d9d 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -526,7 +526,7 @@ constexpr bool has_vptr_fn = std::is_same_v< std::declval(), std::declval())), vptr_type>; -BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(dynamic_vptr); +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(vptr); template decltype(auto) acquire_vptr(const ArgType& arg) { @@ -534,13 +534,12 @@ decltype(auto) acquire_vptr(const ArgType& arg) { if constexpr (has_vptr_fn) { return boost_openmethod_vptr(arg, static_cast(nullptr)); - } else if constexpr (has_dynamic_vptr< + } else if constexpr (has_vptr< virtual_traits, type_id>) { - return virtual_traits::dynamic_vptr( - arg); + return virtual_traits::vptr(arg); } else { - return Registry::template policy::dynamic_vptr(arg); + return Registry::template policy::vptr(arg); } } @@ -2348,7 +2347,7 @@ class method void resolve_type_ids(); - template + template auto vptr(const ArgType& arg) const -> vptr_type; template @@ -2502,7 +2501,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)...); @@ -2534,13 +2533,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, + type_id>) { + return virtual_traits::vptr(obj); + } else { + return Registry::template policy::dynamic_vptr(obj); + } } } @@ -2557,7 +2566,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...); @@ -2576,7 +2585,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 @@ -2606,7 +2615,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; diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 0bff558d..6234e5b2 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -64,7 +64,7 @@ struct virtual_traits { //! //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the stored value. - static auto dynamic_vptr(const boost::any& arg) -> const vptr_type& { + static auto vptr(const boost::any& arg) -> const vptr_type& { return Registry::vptr::vptr(&arg.type()); } @@ -127,7 +127,7 @@ struct virtual_traits { //! //! @param arg A reference to a `boost::any`. //! @return A reference to the v-table pointer for the stored value. - static auto dynamic_vptr(const boost::any& arg) -> const vptr_type& { + static auto vptr(const boost::any& arg) -> const vptr_type& { return Registry::vptr::vptr(&arg.type()); } @@ -190,7 +190,7 @@ struct virtual_traits { //! //! @param arg A reference to a `boost::any`. //! @return A reference to the v-table pointer for the stored value. - static auto dynamic_vptr(const boost::any& arg) -> const vptr_type& { + static auto vptr(const boost::any& arg) -> const vptr_type& { return Registry::vptr::vptr(&arg.type()); } diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index ce5b6515..46c340ed 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -61,7 +61,7 @@ struct virtual_traits { //! //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the stored value. - static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { + static auto vptr(const std::any& arg) -> const vptr_type& { return Registry::vptr::vptr(&arg.type()); } @@ -113,7 +113,7 @@ struct virtual_traits { //! //! @param arg A reference to a `std::any`. //! @return A reference to the v-table pointer for the stored value. - static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { + static auto vptr(const std::any& arg) -> const vptr_type& { return Registry::vptr::vptr(&arg.type()); } @@ -166,7 +166,7 @@ struct virtual_traits { //! //! @param arg A reference to a const `any`. //! @return A reference to a the v-table pointer for `Class`. - static auto dynamic_vptr(const std::any& arg) -> const vptr_type& { + static auto vptr(const std::any& arg) -> const vptr_type& { return Registry::vptr::vptr(&arg.type()); } diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 0bc1c12d..9c8e7025 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -27,7 +27,7 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as const std::any& (const ref) -static_assert(detail::has_dynamic_vptr< +static_assert(detail::has_vptr< virtual_traits, type_id>); MAKE_CLASSES(); @@ -66,7 +66,7 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as std::any& (mutable ref) -static_assert(detail::has_dynamic_vptr< +static_assert(detail::has_vptr< virtual_traits, type_id>); MAKE_CLASSES(); @@ -129,7 +129,7 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as std::any&& (xvalue ref) -static_assert(detail::has_dynamic_vptr< +static_assert(detail::has_vptr< virtual_traits, type_id>); MAKE_CLASSES(); From 65cd1b563a13288d081b0e6b67b1e1cbb45167d4 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 11:47:18 -0400 Subject: [PATCH 41/85] fix leftovers of the dynamic_vptr -> vptr rename Commit 7ecd96c renamed acquire_vptr's registry-policy fallback from dynamic_vptr(arg) to vptr(arg), but the policies' object-taking overload is still named dynamic_vptr - vptr(type_id) is the id-taking one. The fallback is reached whenever a plain virtual_ptr is constructed from a reference or pointer to a polymorphic object, so every such construction failed to compile; stale incremental builds masked it. Restore dynamic_vptr, matching method::vptr's own fallback. Also update test_dispatch_boost_any.cpp's has_dynamic_vptr static_asserts to has_vptr; the rename had updated the std counterpart only. Co-Authored-By: Claude Fable 5 --- include/boost/openmethod/core.hpp | 2 +- test/test_dispatch_boost_any.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index eefc6d9d..180adc77 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -539,7 +539,7 @@ decltype(auto) acquire_vptr(const ArgType& arg) { type_id>) { return virtual_traits::vptr(arg); } else { - return Registry::template policy::vptr(arg); + return Registry::template policy::dynamic_vptr(arg); } } diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index ede5d029..bfd850db 100644 --- a/test/test_dispatch_boost_any.cpp +++ b/test/test_dispatch_boost_any.cpp @@ -27,7 +27,7 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as const boost::any& (const ref) -static_assert(detail::has_dynamic_vptr< +static_assert(detail::has_vptr< virtual_traits, type_id>); MAKE_CLASSES(); @@ -66,8 +66,8 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as boost::any& (mutable ref) -static_assert(detail::has_dynamic_vptr< - virtual_traits, type_id>); +static_assert( + detail::has_vptr, type_id>); MAKE_CLASSES(); @@ -129,8 +129,8 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as boost::any&& (xvalue ref) -static_assert(detail::has_dynamic_vptr< - virtual_traits, type_id>); +static_assert( + detail::has_vptr, type_id>); MAKE_CLASSES(); From 57b6c32b8040b34e82ce4ee6f130dbc8aa129e87 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 11:47:32 -0400 Subject: [PATCH 42/85] add virtual_any virtual_any is to `any` what virtual_ptr is to a pointer: it combines an `any` - held by value - with the v-table pointer for the contained value, so methods dispatch on the contained type without looking it up on every call. The v-table pointer is acquired at construction: from the dynamic type of an existing `any` (a hash table lookup via virtual_traits::vptr), or statically when the contained type is known (the value constructor, emplace, and the make_*_virtual factories use static_vptr, like make_unique_virtual). Assignment and emplace re-derive it, and no mutable accessor to the `any` is exposed, so the vptr always matches the payload. Methods take virtual_any by const, mutable or rvalue reference; overriders receive the contained type by a reference of a compatible category - the casts delegate to the existing virtual_traits specializations - or the virtual_any itself, unchanged, for a catch-all overrider. Passing virtual_any by value is rejected: it would copy the payload on every call. The value constructor makes overrider parameters convertible to the method's, so BOOST_OPENMETHOD_OVERRIDE locates virtual_any methods; the mutable lvalue case still needs method<...>::override, as with virtual_. No changes to core.hpp: dispatch reads the stored vptr through the boost_openmethod_vptr hook (a friend, so ADL only finds it when a virtual_any is an argument), and the detail templates (is_virtual, parameter_traits, validate_method_parameter, validate_overrider_parameter, select_overrider_virtual_type_aux) are specialized on the concrete class. The exact-pair validate_overrider_parameter specializations disambiguate with the generic one, which partial ordering ranks neither above nor below . The class is generic: it only requires virtual_traits with vptr and cast, so it serves std::any, boost::any, and future any-likes. std_any.hpp and boost_any.hpp provide the default-registry aliases virtual_std_any and virtual_boost_any and the make_std_any_virtual and make_boost_any_virtual factories. They also delete the final_virtual_ptr overloads for their `any` type: the primary template would silently use static_vptr - the v-table of the `any` root class, not of the contained value. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 26 +- .../boost/openmethod/interop/boost_any.hpp | 52 ++ include/boost/openmethod/interop/std_any.hpp | 52 ++ .../boost/openmethod/interop/virtual_any.hpp | 498 ++++++++++++++++++ test/CMakeLists.txt | 6 + ...compile_fail_final_virtual_ptr_std_any.cpp | 27 + test/compile_fail_virtual_any_by_value.cpp | 26 + test/test_virtual_any_boost.cpp | 244 +++++++++ test/test_virtual_any_std.cpp | 244 +++++++++ 9 files changed, 1173 insertions(+), 2 deletions(-) create mode 100644 include/boost/openmethod/interop/virtual_any.hpp create mode 100644 test/compile_fail_final_virtual_ptr_std_any.cpp create mode 100644 test/compile_fail_virtual_any_by_value.cpp create mode 100644 test/test_virtual_any_boost.cpp create mode 100644 test/test_virtual_any_std.cpp diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 4089b03e..b8c89482 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -71,13 +71,31 @@ 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:{{BASE_URL}}/include/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 - like `virtual_ptr` +combines a pointer to an object with a pointer to its v-table. The v-table +pointer is acquired when the `virtual_any` is created, so methods dispatch on +the contained type without looking it up on every call. Also provides +`make_any_virtual`, which creates a `virtual_any` containing a value of a +statically known type, setting the v-table pointer without any lookup. This +header is included by `std_any.hpp` and `boost_any.hpp`; it can serve any type +with an `any`-like interface, given `virtual_traits` specializations for its +reference types. + [#std_any] ### link:{{BASE_URL}}/include/boost/openmethod/interop/std_any.hpp[] Provides `virtual_traits` specializations that make it possible to use a `std::any` - by const reference, by mutable reference, or by rvalue reference - in virtual parameters. Dispatch is on the type of the contained value. Also provides -`use_std_any_types`, which registers the types that may be contained. +`use_std_any_types`, which registers the types that may be contained; +`virtual_std_any`, an alias for `virtual_any`, and +`make_std_any_virtual`. In addition, the header deletes the +`final_virtual_ptr` overloads for `std::any`, which would otherwise silently +use the v-table of the `any` root class instead of the contained value's. [#boost_any] ### link:{{BASE_URL}}/include/boost/openmethod/interop/boost_any.hpp[] @@ -85,7 +103,11 @@ parameters. Dispatch is on the type of the contained value. Also provides Provides `virtual_traits` specializations that make it possible to use a `boost::any` - by const reference, by mutable reference, or by rvalue reference - in virtual parameters. Dispatch is on the type of the contained value. Also provides -`use_boost_any_types`, which registers the types that may be contained. +`use_boost_any_types`, which registers the types that may be contained; +`virtual_boost_any`, an alias for `virtual_any`, and +`make_boost_any_virtual`. In addition, the header deletes the +`final_virtual_ptr` overloads for `boost::any`, which would otherwise silently +use the v-table of the `any` root class instead of the contained value's. *The headers below are for advanced use*. diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 6234e5b2..3e77d09e 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -8,6 +8,7 @@ #include #include +#include namespace boost::openmethod { @@ -238,6 +239,57 @@ struct use_boost_any_types typename detail::extract_registry::registry, mp11::mp_list>... {}; +//! Alias for a `virtual_any`, in the default registry. +//! +//! With another registry, use `virtual_any` directly. +using virtual_boost_any = virtual_any; + +//! Create a new object and return a `virtual_boost_any` containing it. +//! +//! Create a `Class` from `args`, store it in a `boost::any`, and return a +//! @ref virtual_any with its v-table pointer set to the +//! @ref registry::static_vptr for `Class` - no hash table lookup is +//! involved. +//! +//! @tparam Class The type of the value to create. +//! @tparam Registry A @ref registry. +//! @tparam T Types of the arguments to pass to the constructor of +//! `Class`. +//! @param args Arguments to pass to the constructor of `Class`. +//! @return A `virtual_any` containing a newly created +//! `Class`. +template< + class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + typename... T> +inline auto +make_boost_any_virtual(T&&... args) -> virtual_any { + return make_any_virtual( + std::forward(args)...); +} + +// 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. + +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; + +namespace aliases { +using boost::openmethod::make_boost_any_virtual; +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 index 46c340ed..1b0bda96 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -8,6 +8,7 @@ #include #include +#include namespace boost::openmethod { @@ -201,6 +202,57 @@ struct use_std_any_types typename detail::extract_registry::registry, mp11::mp_list>... {}; +//! Alias for a `virtual_any`, in the default registry. +//! +//! With another registry, use `virtual_any` directly. +using virtual_std_any = virtual_any; + +//! Create a new object and return a `virtual_std_any` containing it. +//! +//! Create a `Class` from `args`, store it in a `std::any`, and return a +//! @ref virtual_any with its v-table pointer set to the +//! @ref registry::static_vptr for `Class` - no hash table lookup is +//! involved. +//! +//! @tparam Class The type of the value to create. +//! @tparam Registry A @ref registry. +//! @tparam T Types of the arguments to pass to the constructor of +//! `Class`. +//! @param args Arguments to pass to the constructor of `Class`. +//! @return A `virtual_any` containing a newly created +//! `Class`. +template< + class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + typename... T> +inline auto +make_std_any_virtual(T&&... args) -> virtual_any { + return make_any_virtual( + std::forward(args)...); +} + +// 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. + +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; + +namespace aliases { +using boost::openmethod::make_std_any_virtual; +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..5afe7036 --- /dev/null +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -0,0 +1,498 @@ +// 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; + +namespace detail { + +template +struct is_virtual_any_aux : std::false_type {}; + +template +struct is_virtual_any_aux> : std::true_type {}; + +} // namespace detail + +//! 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, @ref make_any_virtual, 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. +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; + + 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`. + 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. + template< + typename T, + typename = std::enable_if_t< + !detail::is_virtual_any_aux>::value && + !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< + !detail::is_virtual_any_aux>::value && + !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`. + 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__ + friend auto + boost_openmethod_vptr(const virtual_any& va, Registry*) -> 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 + +//! Create a new object and return a `virtual_any` containing it. +//! +//! Create a `Class` from `args`, store it in a @ref virtual_any, and set +//! 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 create. +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +//! @tparam T Types of the arguments to pass to the constructor of +//! `Class`. +//! @param args Arguments to pass to the constructor of `Class`. +//! @return A `virtual_any` containing a newly created +//! `Class`. +template< + class Class, class Any, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + typename... T> +inline auto make_any_virtual(T&&... args) -> virtual_any { + return virtual_any(Class(std::forward(args)...)); +} + +namespace aliases { +using boost::openmethod::make_any_virtual; +using boost::openmethod::virtual_any; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ed1f9f77..6678cd47 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -163,6 +163,12 @@ 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") +# "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") if (TARGET Boost::dll) add_subdirectory(dynamic_loading) 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_virtual_any_by_value.cpp b/test/compile_fail_virtual_any_by_value.cpp new file mode 100644 index 00000000..97ea54ba --- /dev/null +++ b/test/compile_fail_virtual_any_by_value.cpp @@ -0,0 +1,26 @@ +// 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() { + return 0; +} diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp new file mode 100644 index 00000000..a77b2e37 --- /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"); + + auto felix = make_boost_any_virtual("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(make_boost_any_virtual("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_std.cpp b/test/test_virtual_any_std.cpp new file mode 100644 index 00000000..9cefe118 --- /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"); + + auto felix = make_std_any_virtual("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(make_std_any_virtual("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 From 475b05b8bf64bef3e27545500160a777cc995eff Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 12:22:50 -0400 Subject: [PATCH 43/85] probe virtual_traits::vptr with the argument type, not type_id acquire_vptr and method::vptr detected a traits-supplied vptr with has_vptr, type_id>, i.e. by asking whether traits::vptr is callable with a type_id (a const void*). The member takes a reference to the any, so the probe only passed because std::any and boost::any happen to have a greedy converting constructor that accepts a const void*. An any-like type without such a constructor would silently fail the probe and fall through to the vptr policy's dynamic_vptr, which keys the lookup on typeid(wrapper) - the wrapper class itself, not the contained value. Probe with the actual argument type instead, making the detection ask the intended question: does this specialization provide a vptr member. Co-Authored-By: Claude Fable 5 --- include/boost/openmethod/core.hpp | 4 ++-- test/test_dispatch_boost_any.cpp | 9 ++++++--- test/test_dispatch_std_any.cpp | 9 +++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 180adc77..429f8d02 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -536,7 +536,7 @@ decltype(auto) acquire_vptr(const ArgType& arg) { return boost_openmethod_vptr(arg, static_cast(nullptr)); } else if constexpr (has_vptr< virtual_traits, - type_id>) { + const ArgType&>) { return virtual_traits::vptr(arg); } else { return Registry::template policy::dynamic_vptr(arg); @@ -2545,7 +2545,7 @@ BOOST_FORCEINLINE auto method::vptr( return boost_openmethod_vptr(obj, static_cast(nullptr)); } else if constexpr (detail::has_vptr< virtual_traits, - type_id>) { + decltype(obj)>) { return virtual_traits::vptr(obj); } else { return Registry::template policy::dynamic_vptr(obj); diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index bfd850db..fa2317d3 100644 --- a/test/test_dispatch_boost_any.cpp +++ b/test/test_dispatch_boost_any.cpp @@ -28,7 +28,8 @@ namespace BOOST_OPENMETHOD_GENSYM { // pass virtual args as const boost::any& (const ref) static_assert(detail::has_vptr< - virtual_traits, type_id>); + virtual_traits, + const boost::any&>); MAKE_CLASSES(); @@ -67,7 +68,8 @@ namespace BOOST_OPENMETHOD_GENSYM { // pass virtual args as boost::any& (mutable ref) static_assert( - detail::has_vptr, type_id>); + detail::has_vptr< + virtual_traits, const boost::any&>); MAKE_CLASSES(); @@ -130,7 +132,8 @@ namespace BOOST_OPENMETHOD_GENSYM { // pass virtual args as boost::any&& (xvalue ref) static_assert( - detail::has_vptr, type_id>); + detail::has_vptr< + virtual_traits, const boost::any&>); MAKE_CLASSES(); diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 9c8e7025..88626296 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -27,8 +27,9 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as const std::any& (const ref) -static_assert(detail::has_vptr< - virtual_traits, type_id>); +static_assert( + detail::has_vptr< + virtual_traits, const std::any&>); MAKE_CLASSES(); @@ -67,7 +68,7 @@ namespace BOOST_OPENMETHOD_GENSYM { // pass virtual args as std::any& (mutable ref) static_assert(detail::has_vptr< - virtual_traits, type_id>); + virtual_traits, const std::any&>); MAKE_CLASSES(); @@ -130,7 +131,7 @@ namespace BOOST_OPENMETHOD_GENSYM { // pass virtual args as std::any&& (xvalue ref) static_assert(detail::has_vptr< - virtual_traits, type_id>); + virtual_traits, const std::any&>); MAKE_CLASSES(); From a117a28db9637eea06c6a2e98ab507b0fc015856 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 12:33:13 -0400 Subject: [PATCH 44/85] any traits: pass the any through to catch-all overriders An overrider may take the method's `any` parameter itself, acting as a catch-all for contained types that have no more specific overrider. The virtual_traits cast members passed U to any_cast unconditionally, and any_cast to the any's own type throws unless the any contains an any. Return the argument unchanged when U is the any, by value or by any reference category - as virtual_any's traits already did. Co-Authored-By: Claude Fable 5 --- .../boost/openmethod/interop/boost_any.hpp | 24 ++++++++++-- include/boost/openmethod/interop/std_any.hpp | 38 +++++++++++++++---- test/test_dispatch_boost_any.cpp | 13 +++++++ test/test_dispatch_std_any.cpp | 13 +++++++ 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 3e77d09e..850f9d34 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -87,7 +87,13 @@ struct virtual_traits { !std::is_reference_v || std::is_const_v>>> static auto cast(const boost::any& arg) -> decltype(auto) { - return boost::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } } }; @@ -150,7 +156,13 @@ struct virtual_traits { template< typename U, typename = std::enable_if_t>> static auto cast(boost::any& arg) -> decltype(auto) { - return boost::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } } }; @@ -213,7 +225,13 @@ struct virtual_traits { !std::is_lvalue_reference_v || std::is_const_v>>> static auto cast(boost::any&& arg) -> decltype(auto) { - return boost::any_cast(std::move(arg)); + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return std::move(arg); + } else { + return boost::any_cast(std::move(arg)); + } } }; diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 1b0bda96..924a0d01 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -68,7 +68,9 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `std::any_cast`. Since the `any` + //! If `U` is `std::any` itself (by value or const reference), returns + //! `arg` unchanged - the catch-all overrider case. 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`). @@ -76,7 +78,13 @@ struct virtual_traits { //! @return The value stored in `arg`, cast to `U`. template static auto cast(const std::any& arg) -> decltype(auto) { - return std::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } } }; @@ -120,7 +128,9 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `std::any_cast`. Supports mutable + //! If `U` is `std::any` itself (by reference or by value), returns + //! `arg` unchanged - the catch-all overrider case. 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`. //! @@ -129,7 +139,13 @@ struct virtual_traits { //! @return The value stored in `arg`, cast to `U`. template static auto cast(std::any& arg) -> decltype(auto) { - return std::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } } }; @@ -173,14 +189,22 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `std::any_cast`. + //! If `U` is `std::any` itself, returns `arg` unchanged - the + //! catch-all overrider case. Otherwise, extracts the stored value + //! using `std::any_cast`. //! - //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @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) { - return std::any_cast(std::move(arg)); + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return std::move(arg); + } else { + return std::any_cast(std::move(arg)); + } } }; diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index fa2317d3..7b7b1ebb 100644 --- a/test/test_dispatch_boost_any.cpp +++ b/test/test_dispatch_boost_any.cpp @@ -49,6 +49,15 @@ BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) { return os.str(); } +// A catch-all overrider may take the `any` itself; the argument is passed +// through unchanged, instead of going through boost::any_cast, which +// would throw unless the `any` contains an `any`. +use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +BOOST_OPENMETHOD_OVERRIDE(name, (const boost::any& arg), std::string) { + return !arg.empty() ? "something" : "nothing"; +} + BOOST_AUTO_TEST_CASE(boost_any_by_const_ref) { initialize(trace()); @@ -59,6 +68,10 @@ BOOST_AUTO_TEST_CASE(boost_any_by_const_ref) { BOOST_TEST(name(spot) == "Spot the dog"); BOOST_TEST(name(felix) == "Felix the cat"); BOOST_TEST(name(answer) == "42 the integer"); + + // `double` is registered but has no specific overrider: the catch-all, + // registered for the `boost::any` root, applies + BOOST_TEST(name(boost::any(1.5)) == "something"); } } // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 88626296..7cd2a451 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -49,6 +49,15 @@ BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) { return os.str(); } +// A catch-all overrider may take the `any` itself; the argument is passed +// through unchanged, instead of going through std::any_cast, which would +// throw unless the `any` contains an `any`. +use_std_any_types BOOST_OPENMETHOD_GENSYM; + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::any& arg), std::string) { + return arg.has_value() ? "something" : "nothing"; +} + BOOST_AUTO_TEST_CASE(std_any_by_const_ref) { initialize(trace()); @@ -59,6 +68,10 @@ BOOST_AUTO_TEST_CASE(std_any_by_const_ref) { BOOST_TEST(name(spot) == "Spot the dog"); BOOST_TEST(name(felix) == "Felix the cat"); BOOST_TEST(name(answer) == "42 the integer"); + + // `double` is registered but has no specific overrider: the catch-all, + // registered for the `std::any` root, applies + BOOST_TEST(name(std::any(1.5)) == "something"); } } // namespace BOOST_OPENMETHOD_GENSYM From a6d56d2347aa6963e73d09b401602e1bc74021a4 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 12:33:27 -0400 Subject: [PATCH 45/85] use_*_any_types: do not register a trailing registry as a class The registrars expanded their whole template parameter pack into use_class_aux instantiations, so a trailing registry argument - accepted, and used to select the registry - was also registered as a class derived from the any root. Harmless, but wrong. Factor the expansion into detail::use_any_types_aux (in virtual_any.hpp, shared by all the any interop headers), driven by extract_registry's `others` list, which excludes the registry. Co-Authored-By: Claude Fable 5 --- include/boost/openmethod/interop/boost_any.hpp | 9 +++------ include/boost/openmethod/interop/std_any.hpp | 9 +++------ include/boost/openmethod/interop/virtual_any.hpp | 11 +++++++++++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 850f9d34..3ea1982a 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -250,12 +250,9 @@ struct virtual_traits { //! followed by a @ref registry. 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>... {}; + : detail::use_any_types_aux< + typename detail::extract_registry::registry, boost::any, + typename detail::extract_registry::others> {}; //! Alias for a `virtual_any`, in the default registry. //! diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 924a0d01..5fff708d 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -219,12 +219,9 @@ struct virtual_traits { //! followed by a @ref registry. 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>... {}; + : detail::use_any_types_aux< + typename detail::extract_registry::registry, std::any, + typename detail::extract_registry::others> {}; //! Alias for a `virtual_any`, in the default registry. //! diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 5afe7036..bb925538 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -24,6 +24,17 @@ struct is_virtual_any_aux : std::false_type {}; template struct is_virtual_any_aux> : std::true_type {}; +// Common implementation for the use_*_any_types registrars: register Root +// as a class, and each element of the Classes list as a class derived +// from Root. +template +struct use_any_types_aux; + +template +struct use_any_types_aux> + : use_class_aux>, + use_class_aux>... {}; + } // namespace detail //! A wide `any`, combining an `any` and a pointer to a v-table. From 495abc268e8b12ab5f7b833220a3eea7a591e520 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 12:35:27 -0400 Subject: [PATCH 46/85] support boost::type_erasure (#21) Add interop/boost_type_erasure.hpp: dispatch on the type bound to a boost::type_erasure::any, via virtual_traits and the vptr policies' type-id-keyed entry point - the same approach as the std::any and boost::any interop, with no custom rtti policy or registry. Dispatch keys on the std::type_info returned by typeid_of, so the only requirement on the user's Concept is typeid_<>, which `relaxed` already implies. virtual_traits specializations, generic over the Concept, cover the owning flavor by const, mutable and rvalue reference, and the reference-wrapper flavors (any, any) by value - they are cheap, two-word handles, and te's idiomatic parameter carriers. All use the owning flavor as their virtual_type, so a single registered root per Concept serves every parameter form; overriders receive the bound type by a reference of a compatible category, or the any itself as a catch-all. type_erasure's any_cast has no rvalue overload, so the xvalue trait moves the result of a mutable-reference cast - for the owning flavor only, since the rvalue-ness of a reference wrapper says nothing about the referent's ownership. Casts that cannot work (mutable access to const-bound values, moving out of borrowed referents) are removed from the overload set, mirroring the boost::any constraints. use_type_erasure_types registers the bound types under the Concept's root, normalizing Any to the owning flavor. virtual_any composes with no extra code: virtual_any> looks the v-table pointer up once, at construction - recovering O(1) vptr acquisition, which the concept-interface-injection approach sketched in #21 obtained at the cost of naming the policy inside the user's Concept. The final_virtual_ptr overloads for type_erasure::any are deleted: the primary would silently use the root's static v-table pointer. Dispatch on the reference flavors is on the type bound at construction, never the C++ RTTI dynamic type of the referent; an empty relaxed any yields typeid(void), reported as missing_class under runtime checks. Co-Authored-By: Claude Fable 5 --- CMakeLists.txt | 1 + doc/modules/ROOT/pages/ref_headers.adoc | 16 + .../openmethod/interop/boost_type_erasure.hpp | 490 ++++++++++++++++++ test/CMakeLists.txt | 7 + test/Jamfile | 1 + ...le_fail_final_virtual_ptr_type_erasure.cpp | 33 ++ test/compile_fail_type_erasure_by_value.cpp | 34 ++ ..._type_erasure_const_ref_to_mutable_ref.cpp | 37 ++ test/test_dispatch_type_erasure.cpp | 291 +++++++++++ 9 files changed, 910 insertions(+) create mode 100644 include/boost/openmethod/interop/boost_type_erasure.hpp create mode 100644 test/compile_fail_final_virtual_ptr_type_erasure.cpp create mode 100644 test/compile_fail_type_erasure_by_value.cpp create mode 100644 test/compile_fail_type_erasure_const_ref_to_mutable_ref.cpp create mode 100644 test/test_dispatch_type_erasure.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 193b3939..9d09f7c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,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) + list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::type_erasure) endif() foreach (BOOST_OPENMETHOD_DEPENDENCY ${BOOST_OPENMETHOD_DEPENDENCIES}) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index b8c89482..03613d1f 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -109,6 +109,22 @@ parameters. Dispatch is on the type of the contained value. Also provides `final_virtual_ptr` overloads for `boost::any`, which would otherwise silently use the v-table of the `any` root class instead of the contained value's. +[#boost_type_erasure] +### link:{{BASE_URL}}/include/boost/openmethod/interop/boost_type_erasure.hpp[] + +Provides `virtual_traits` specializations that make it possible to use a +`boost::type_erasure::any` in virtual parameters: the owning flavor by const, +mutable or rvalue reference, and the reference-wrapper flavors +(`any`, `any`) by value. Dispatch is on +the type of the bound value, obtained via `boost::type_erasure::typeid_of`; the +Concept must contain `boost::type_erasure::typeid_<>`, which `relaxed` implies. +Also provides `use_type_erasure_types`, which registers the types that may be +bound; `virtual_any>` works as well, and looks the v-table pointer +up only once, at construction. In addition, the header deletes the +`final_virtual_ptr` overloads for `boost::type_erasure::any`, which would +otherwise silently use the v-table of the `any` root class instead of the bound +value's. + *The headers below are for advanced use*. ## Pre-Core Headers diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp new file mode 100644 index 00000000..ae0461a2 --- /dev/null +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -0,0 +1,490 @@ +// 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_TYPE_ERASURE_HPP +#define BOOST_OPENMETHOD_INTEROP_BOOST_TYPE_ERASURE_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include + +// Dispatch on the type contained in a boost::type_erasure::any. +// +// The Concept must contain boost::type_erasure::typeid_<> - which +// `relaxed` already implies - so that `typeid_of` can identify the +// contained value. Dispatch is on the `std::type_info` object returned by +// `typeid_of`: the type of the contained value for the owning flavor +// (`any`), or the type *bound at construction* for the reference +// flavors (`any`, `any`) - never +// the C++ RTTI dynamic type of the referent. +// +// Supported virtual parameter forms: +// - `virtual_&>`, `virtual_&>`, +// `virtual_&&>` - the owning flavor, by reference, like +// `std::any`; +// - `virtual_>` and +// `virtual_>` - the reference-wrapper +// flavors, by value (they are cheap, two-word handles); +// - `virtual_any>` - looks the v-table pointer up once, at +// construction. The Concept needs `relaxed` for virtual_any's default +// constructor and assignment, and `copy_constructible<>` for copies. +// +// The rvalue-reference flavor (`any`), and placeholders +// other than `_self`, are not supported. + +namespace boost::openmethod { + +namespace detail { + +// Classification of the placeholder of an any. `T = _self` +// (or any non-reference placeholder): the any owns the value. `T = +// _self&`: non-owning handle to a mutable referent. Anything else +// (`const _self&`, `_self&&`) is treated as binding a value that may not +// be mutated or moved from. +template +constexpr bool te_owning = !std::is_reference_v; + +template +constexpr bool te_mutable_bound = std::is_lvalue_reference_v && + !std::is_const_v>; + +// Does U, an overrider parameter type, require mutable access? +template +constexpr bool te_mutable_target = std::is_lvalue_reference_v && + !std::is_const_v>; + +// Is U, an overrider parameter type, the `any` itself (by value or by +// any reference category)? Then the argument is passed through +// unchanged - the catch-all overrider case - instead of going through +// any_cast, which would throw unless the any contains an any. +template +constexpr bool te_pass_through = + std::is_same_v>, Any>; + +// The canonical root class for a Concept: the owning flavor. All the +// virtual_traits below use it as their virtual_type, whatever the +// flavor of the parameter, so methods, overriders and +// use_type_erasure_types agree on a single registered root per Concept. +template +using type_erasure_root = boost::type_erasure::any< + typename boost::type_erasure::concept_of::type>; + +template +struct validate_method_parameter< + virtual_&>, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter< + virtual_&>, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter< + virtual_&&>, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter< + virtual_>, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter< + virtual_>, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter< + virtual_>, Registry, + void> : std::false_type { + static_assert( + false_t, "an owning type_erasure::any must be passed by reference"); +}; + +} // namespace detail + +//! Specialize virtual_traits for `const boost::type_erasure::any&`. +//! +//! Dispatch is based on the type of the value bound to the `any`, +//! obtained via `boost::type_erasure::typeid_of`. `Concept` must contain +//! `boost::type_erasure::typeid_<>`; `relaxed` implies it. +//! +//! This specialization serves the owning flavor (`any`) and, +//! through a const wrapper, the reference flavors. +//! +//! @tparam C The `any`'s Concept. +//! @tparam T The `any`'s placeholder. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&, Registry> { + //! The type used for dispatch: the owning flavor for `C`. + using virtual_type = boost::type_erasure::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to an `any`. + //! @return A const reference to `arg`. + static auto peek(const boost::type_erasure::any& arg) + -> const boost::type_erasure::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for the bound value. + //! + //! Looks up the @ref type_id returned by + //! `boost::type_erasure::typeid_of` in the registry's `vptr` policy. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the bound value. + static auto + vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); + } + + //! Cast to a type. + //! + //! Extracts the bound value using `boost::type_erasure::any_cast`. + //! Since the `any` is const, `U` can be a mutable reference only for + //! the mutable-reference flavor (`any`), whose + //! referent stays mutable through a const wrapper. Rvalue references + //! are never allowed; the overloads are removed from the overload + //! set. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg A reference to a const `any` method argument. + //! @return The value bound to `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + !std::is_rvalue_reference_v && + (!detail::te_mutable_target || detail::te_mutable_bound)>> + static auto + cast(const boost::type_erasure::any& arg) -> decltype(auto) { + if constexpr (detail::te_pass_through< + U, boost::type_erasure::any>) { + return (arg); + } else { + return boost::type_erasure::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `boost::type_erasure::any&` (mutable +//! reference). +//! +//! Dispatch is based on the type of the value bound to the `any`, +//! obtained via `boost::type_erasure::typeid_of`. `Concept` must contain +//! `boost::type_erasure::typeid_<>`; `relaxed` implies it. +//! +//! @tparam C The `any`'s Concept. +//! @tparam T The `any`'s placeholder. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&, Registry> { + //! The type used for dispatch: the owning flavor for `C`. + using virtual_type = boost::type_erasure::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to an `any`. + //! @return A const reference to `arg`. + static auto peek(const boost::type_erasure::any& arg) + -> const boost::type_erasure::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for the bound value. + //! + //! Looks up the @ref type_id returned by + //! `boost::type_erasure::typeid_of` in the registry's `vptr` policy. + //! + //! @param arg A reference to an `any`. + //! @return A reference to the v-table pointer for the bound value. + static auto + vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); + } + + //! Cast to a type. + //! + //! Extracts the bound value using `boost::type_erasure::any_cast`. + //! Supports mutable references (e.g. `Dog&`), except through the + //! const-reference flavor (`any`). `U` cannot + //! be an rvalue reference: moving the value out must go through an + //! explicit rvalue-reference parameter. The disallowed overloads are + //! removed from the overload set. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg A mutable reference to the `any` method argument. + //! @return The value bound to `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + !std::is_rvalue_reference_v && + (!detail::te_mutable_target || detail::te_owning || + detail::te_mutable_bound)>> + static auto cast(boost::type_erasure::any& arg) -> decltype(auto) { + if constexpr (detail::te_pass_through< + U, boost::type_erasure::any>) { + return (arg); + } else { + return boost::type_erasure::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `boost::type_erasure::any&&` (xvalue +//! reference). +//! +//! Dispatch is based on the type of the value bound to the `any`, +//! obtained via `boost::type_erasure::typeid_of`. `Concept` must contain +//! `boost::type_erasure::typeid_<>`; `relaxed` implies it. +//! +//! @tparam C The `any`'s Concept. +//! @tparam T The `any`'s placeholder. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&&, Registry> { + //! The type used for dispatch: the owning flavor for `C`. + using virtual_type = boost::type_erasure::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to an `any`. + //! @return A const reference to `arg`. + static auto peek(const boost::type_erasure::any& arg) + -> const boost::type_erasure::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for the bound value. + //! + //! Looks up the @ref type_id returned by + //! `boost::type_erasure::typeid_of` in the registry's `vptr` policy. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the bound value. + static auto + vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); + } + + //! Cast to a type. + //! + //! Extracts the bound value using `boost::type_erasure::any_cast`. + //! `boost::type_erasure::any_cast` has no rvalue overload, so, for an + //! rvalue-reference `U`, the result of a mutable-reference cast is + //! moved - only for the owning flavor, since the rvalue-ness of a + //! reference wrapper says nothing about the referent. Casting to a + //! value also moves for the owning flavor, and copies otherwise. The + //! disallowed overloads are removed from the overload set. + //! + //! @tparam U The target type (e.g. `Dog&&`, `const Dog&`, `Dog`). + //! @param arg An rvalue reference to the `any` method argument. + //! @return The value bound to `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + (!std::is_rvalue_reference_v || detail::te_owning) && + (!detail::te_mutable_target || detail::te_owning || + detail::te_mutable_bound)>> + static auto cast(boost::type_erasure::any&& arg) -> decltype(auto) { + if constexpr (detail::te_pass_through< + U, boost::type_erasure::any>) { + return std::move(arg); + } else if constexpr (std::is_rvalue_reference_v) { + return std::move( + boost::type_erasure::any_cast&>( + arg)); + } else if constexpr (!std::is_reference_v && detail::te_owning) { + return U(std::move(boost::type_erasure::any_cast(arg))); + } else { + return boost::type_erasure::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for the mutable reference-wrapper flavor, +//! `boost::type_erasure::any`, passed by value. +//! +//! The reference flavors are cheap, two-word handles; passing them by +//! value is the idiomatic way to use them as parameters. Dispatch is on +//! the type *bound at construction*, obtained via +//! `boost::type_erasure::typeid_of` - not the C++ RTTI dynamic type of +//! the referent. `Concept` must contain `boost::type_erasure::typeid_<>`; +//! `relaxed` implies it. +//! +//! @tparam C The `any`'s Concept. +//! @tparam T The referent placeholder (`_self` for `any`). +//! @tparam Registry A @ref registry. +template +struct virtual_traits, Registry> { + //! The type used for dispatch: the owning flavor for `C`. + using virtual_type = boost::type_erasure::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to an `any`. + //! @return A const reference to `arg`. + static auto peek(const boost::type_erasure::any& arg) + -> const boost::type_erasure::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for the bound value. + //! + //! Looks up the @ref type_id returned by + //! `boost::type_erasure::typeid_of` in the registry's `vptr` policy. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the bound value. + static auto + vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); + } + + //! Cast to a type. + //! + //! Extracts the referent using `boost::type_erasure::any_cast`. + //! Supports mutable references (e.g. `Dog&`); modifications through + //! the result are visible through the referent. `U` cannot be an + //! rvalue reference - the referent is borrowed, not owned; the + //! overloads are removed from the overload set. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg The reference-wrapper `any` method argument. + //! @return The value bound to `arg`, cast to `U`. + template< + typename U, typename = std::enable_if_t>> + static auto cast(boost::type_erasure::any arg) -> decltype(auto) { + if constexpr (detail::te_pass_through< + U, boost::type_erasure::any>) { + // by value: a reference would dangle when this function's + // parameter goes out of scope + return arg; + } else { + return boost::type_erasure::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for the const reference-wrapper flavor, +//! `boost::type_erasure::any`, passed by value. +//! +//! The reference flavors are cheap, two-word handles; passing them by +//! value is the idiomatic way to use them as parameters. Dispatch is on +//! the type *bound at construction*, obtained via +//! `boost::type_erasure::typeid_of` - not the C++ RTTI dynamic type of +//! the referent. `Concept` must contain `boost::type_erasure::typeid_<>`; +//! `relaxed` implies it. +//! +//! @tparam C The `any`'s Concept. +//! @tparam T The referent placeholder (`_self` for +//! `any`). +//! @tparam Registry A @ref registry. +template +struct virtual_traits, Registry> { + //! The type used for dispatch: the owning flavor for `C`. + using virtual_type = boost::type_erasure::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to an `any`. + //! @return A const reference to `arg`. + static auto peek(const boost::type_erasure::any& arg) + -> const boost::type_erasure::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for the bound value. + //! + //! Looks up the @ref type_id returned by + //! `boost::type_erasure::typeid_of` in the registry's `vptr` policy. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the bound value. + static auto + vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); + } + + //! Cast to a type. + //! + //! Extracts the referent using `boost::type_erasure::any_cast`. Since + //! the referent is const, `U` must be a value or a const reference; + //! the other overloads are removed from the overload set. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg The reference-wrapper `any` method argument. + //! @return The value bound to `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + !std::is_rvalue_reference_v && !detail::te_mutable_target>> + static auto + cast(boost::type_erasure::any arg) -> decltype(auto) { + if constexpr (detail::te_pass_through< + U, boost::type_erasure::any>) { + // by value: a reference would dangle when this function's + // parameter goes out of scope + return arg; + } else { + return boost::type_erasure::any_cast(arg); + } + } +}; + +//! Register the types that a `boost::type_erasure::any` virtual parameter +//! may contain. +//! +//! Registers the owning flavor of `Any` (i.e. +//! `any::type>`) as a class, and each `T` as a class +//! derived from it. This makes the bound types visible to the dispatch +//! machinery, which resolves a call on the `type_id` returned by +//! `boost::type_erasure::typeid_of`. `Any` may be spelled with any +//! flavor; the root is normalized to the owning flavor, which is also +//! what the virtual_traits use, whatever the flavor of the method +//! parameter. +//! +//! @tparam Any A `boost::type_erasure::any` type. +//! @tparam T... The types that may be bound to the `any`, optionally +//! followed by a @ref registry. +template +struct use_type_erasure_types + : detail::use_any_types_aux< + typename detail::extract_registry::registry, + detail::type_erasure_root, + typename detail::extract_registry::others> {}; + +// The primary final_virtual_ptr would silently use the static v-table +// pointer of the any class itself - the root -, not the bound value's. +// Delete the combination. Both call forms need covering: the (C, T)-only +// templates catch calls that deduce the default registry, and the +// Registry-first templates catch explicit-registry calls; both are more +// specialized than the primary's forwarding-reference parameter. + +template +void final_virtual_ptr(const boost::type_erasure::any&) = delete; +template +void final_virtual_ptr(boost::type_erasure::any&) = delete; +template +void final_virtual_ptr(boost::type_erasure::any&&) = delete; +template +void final_virtual_ptr(const boost::type_erasure::any&) = delete; +template +void final_virtual_ptr(boost::type_erasure::any&) = delete; +template +void final_virtual_ptr(boost::type_erasure::any&&) = delete; + +namespace aliases { +using boost::openmethod::use_type_erasure_types; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6678cd47..4799b98c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -169,6 +169,13 @@ openmethod_compile_fail_test( # "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_type_erasure_by_value + "an owning type_erasure::any must be passed by reference") +openmethod_compile_fail_test( + compile_fail_type_erasure_const_ref_to_mutable_ref "no matching") +openmethod_compile_fail_test( + compile_fail_final_virtual_ptr_type_erasure "deleted function") if (TARGET Boost::dll) add_subdirectory(dynamic_loading) diff --git a/test/Jamfile b/test/Jamfile index 10ab8c56..841e5643 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -21,6 +21,7 @@ project /boost/openmethod//boost_openmethod /boost/any//boost_any + /boost/type_erasure//boost_type_erasure extra diff --git a/test/compile_fail_final_virtual_ptr_type_erasure.cpp b/test/compile_fail_final_virtual_ptr_type_erasure.cpp new file mode 100644 index 00000000..9660b965 --- /dev/null +++ b/test/compile_fail_final_virtual_ptr_type_erasure.cpp @@ -0,0 +1,33 @@ +// 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 + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +using Concept = boost::mpl::vector, te::relaxed>; +using erased = te::any; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_type_erasure_types); + +int main() { + // The primary final_virtual_ptr would use the static v-table pointer + // of the any class itself - the root -, not the bound value's. The + // combination is deleted; use virtual_any instead. + erased spot(Dog{"Spot"}); + final_virtual_ptr(spot); + return 0; +} diff --git a/test/compile_fail_type_erasure_by_value.cpp b/test/compile_fail_type_erasure_by_value.cpp new file mode 100644 index 00000000..9efe2b26 --- /dev/null +++ b/test/compile_fail_type_erasure_by_value.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 +#include + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +using Concept = boost::mpl::vector, te::relaxed>; +using erased = te::any; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_type_erasure_types); + +// The owning flavor must be passed by reference: by value, it would copy +// the `any` - and its payload - on every call. (The reference-wrapper +// flavors, any and any, may be passed by +// value.) +BOOST_OPENMETHOD(name, (virtual_), std::string); + +int main() { + return 0; +} diff --git a/test/compile_fail_type_erasure_const_ref_to_mutable_ref.cpp b/test/compile_fail_type_erasure_const_ref_to_mutable_ref.cpp new file mode 100644 index 00000000..a64b0773 --- /dev/null +++ b/test/compile_fail_type_erasure_const_ref_to_mutable_ref.cpp @@ -0,0 +1,37 @@ +// 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 + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +using Concept = boost::mpl::vector, te::relaxed>; +using erased = te::any; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_type_erasure_types); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +// The `any` is const and owns its value, so the overrider cannot take a +// mutable reference to it; the `cast` overload is removed from the +// overload set. +BOOST_OPENMETHOD_OVERRIDE(name, (Dog & dog), std::string) { + return dog.name; +} + +int main() { + return 0; +} diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp new file mode 100644 index 00000000..9927671a --- /dev/null +++ b/test/test_dispatch_type_erasure.cpp @@ -0,0 +1,291 @@ +// 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 + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +// `relaxed` implies typeid_<>, on which typeid_of and any_cast - thus +// dispatch - rely; no explicit typeid_<> needed. +using Concept = boost::mpl::vector, te::relaxed>; +using erased = te::any; +using erased_ref = te::any; +using erased_cref = te::any; + +static_assert(detail::has_vptr< + virtual_traits, const erased&>); + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + BOOST_OPENMETHOD_REGISTER( \ + use_type_erasure_types); + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const any& (const ref) + +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; +} + +// A catch-all overrider may take the `any` itself; the argument is passed +// through unchanged. +BOOST_OPENMETHOD_OVERRIDE(name, (const erased& arg), std::string) { + return te::is_empty(arg) ? "nothing" : "something"; +} + +BOOST_AUTO_TEST_CASE(type_erasure_by_const_ref) { + initialize(trace()); + + const erased spot(Dog{"Spot"}); + const erased felix(std::string{"Felix the cat"}); + const erased answer(42); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(felix) == "Felix the cat"); + // `int` is registered but has no specific overrider: the catch-all, + // registered for the root, applies + BOOST_TEST(name(answer) == "something"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as any& (mutable ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this: a temporary `any` binds +// to `const any&` and to `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::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(type_erasure_by_mutable_ref) { + initialize(trace()); + + erased spot(Dog{"Spot"}); + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + // the mutation is visible through the `any` + BOOST_TEST(te::any_cast(spot).name == "Spot Jr."); + + erased answer(41); + BOOST_TEST(bump(answer) == "bumped"); + BOOST_TEST(te::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as any&& (xvalue ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +// boost::type_erasure::any_cast has no rvalue overload; the trait moves +// the result of a mutable-reference cast, because the `any` owns its +// value. +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(type_erasure_by_xvalue_ref) { + initialize(trace()); + + erased 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(!te::is_empty(spot)); + BOOST_TEST(te::any_cast(spot).name == ""); + + erased felix(std::string{"Felix the cat"}); + BOOST_TEST(steal(std::move(felix)) == "Felix the cat"); + BOOST_TEST(te::any_cast(felix) == ""); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as any - the mutable reference-wrapper +// flavor - by value + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(poke, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog), std::string) { + dog.name += "!"; + return dog.name; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (int& value), std::string) { + ++value; + return "poked"; +} + +BOOST_AUTO_TEST_CASE(type_erasure_ref_wrapper_by_value) { + initialize(trace()); + + // the wrapper is a cheap handle; mutations reach the referents + Dog snoopy{"Snoopy"}; + int count = 41; + + BOOST_TEST(poke(erased_ref(snoopy)) == "Snoopy!"); + BOOST_TEST(snoopy.name == "Snoopy!"); + + BOOST_TEST(poke(erased_ref(count)) == "poked"); + BOOST_TEST(count == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as any - the const reference-wrapper +// flavor - by value + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +// the catch-all receives a copy of the wrapper - still a cheap handle +BOOST_OPENMETHOD_OVERRIDE(name, (erased_cref arg), std::string) { + return te::is_empty(arg) ? "nothing" : "something"; +} + +BOOST_AUTO_TEST_CASE(type_erasure_cref_wrapper_by_value) { + initialize(trace()); + + Dog snoopy{"Snoopy"}; + const int count = 42; + + BOOST_TEST(name(erased_cref(snoopy)) == "Snoopy the dog"); + BOOST_TEST(name(erased_cref(count)) == "something"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// virtual_any over a type_erasure any: the v-table pointer is looked up +// once, at construction - or set statically when the contained type is +// known - and dispatch does not hash typeid_of on every call + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_any&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_AUTO_TEST_CASE(type_erasure_virtual_any) { + initialize(trace()); + + // from an `any`: runtime lookup via typeid_of + erased spot_any(Dog{"Spot"}); + virtual_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_any rex = Dog{"Rex"}; + BOOST_TEST(rex.vptr() == default_registry::static_vptr); + BOOST_TEST(name(rex) == "Rex the dog"); + + auto snoopy = make_any_virtual(Dog{"Snoopy"}); + BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); + BOOST_TEST(name(snoopy) == "Snoopy the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// indirect vptrs + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER( + use_type_erasure_types); + +using name_method = method< + struct name_id, std::string(virtual_), 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(type_erasure_indirect_vptr) { + initialize(); + + const erased spot(Dog{"Spot"}); + BOOST_TEST(name_method::fn(spot) == "Spot the dog"); + + virtual_any rex = Dog{"Rex"}; + BOOST_TEST(rex.vptr() == indirect_registry::static_vptr); + BOOST_TEST(name_method::fn(rex.get()) == "Rex the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM From 78143c84d453e7d724891a16ed80eedc0abd6e14 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 12:54:55 -0400 Subject: [PATCH 47/85] fix infinite recursion in virtual_any's vptr friend on MSVC MSVC's /std:c++17 does not imply /permissive-, and in permissive mode MSVC injects friend functions into the enclosing namespace, where detail::acquire_vptr's unqualified call finds them. Called with a plain `Any`, boost_openmethod_vptr was viable through virtual_any's implicit converting constructor - which acquires the v-table pointer, calling the friend again. The recursion is unconditional: release builds failed with warning C4717 under /WX, debug builds overflowed the stack at runtime. Constrain the friend's parameter to a deduced type that must be exactly this virtual_any, so no implicit conversion can make it viable. Co-Authored-By: Claude Fable 5 --- include/boost/openmethod/interop/virtual_any.hpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 5afe7036..7f61104a 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -225,8 +225,18 @@ class virtual_any { } #ifndef __MRDOCS__ - friend auto - boost_openmethod_vptr(const virtual_any& va, Registry*) -> vptr_type { + // 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 From 9dcba28870d119d3066879a96d00c67e04278dab Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 13:35:22 -0400 Subject: [PATCH 48/85] doc: an Interoperation page, and reference examples for the `any`s `virtual_any` shipped with tests but no narrative documentation: nothing in the nav mentioned `any`, no guide page covered it, and the reference pages carried no examples. Add an "Interoperation with Other Libraries" page under Advanced Features, structured to take a `boost::intrusive_ptr` section later. It covers, for `std::any`: why dispatch on an `any` at all, registering the contained types, `virtual_std_any` and where its v-table pointer comes from, what overriders receive, the three reference categories and why the macro cannot express the mutable one, and when to prefer a plain `virtual_` instead. `boost::any` gets a mention rather than a repeat. The page's example is a new top-level doc example. The reference examples are regions of doc/modules/ROOT/snippets/virtual_any.cpp, pulled in with `include:` markers, so they are compiled and run like the rest. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/examples/virtual_any.cpp | 73 +++++++ doc/modules/ROOT/nav.adoc | 1 + doc/modules/ROOT/pages/interop.adoc | 141 +++++++++++++ doc/modules/ROOT/snippets/virtual_any.cpp | 193 ++++++++++++++++++ .../boost/openmethod/interop/boost_any.hpp | 12 ++ include/boost/openmethod/interop/std_any.hpp | 12 ++ .../boost/openmethod/interop/virtual_any.hpp | 19 ++ 7 files changed, 451 insertions(+) create mode 100644 doc/modules/ROOT/examples/virtual_any.cpp create mode 100644 doc/modules/ROOT/pages/interop.adoc create mode 100644 doc/modules/ROOT/snippets/virtual_any.cpp diff --git a/doc/modules/ROOT/examples/virtual_any.cpp b/doc/modules/ROOT/examples/virtual_any.cpp new file mode 100644 index 00000000..83619e30 --- /dev/null +++ b/doc/modules/ROOT/examples/virtual_any.cpp @@ -0,0 +1,73 @@ +// 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 { + Dog(std::string name) : name(std::move(name)) {} + std::string name; +}; + +struct Cat { + Cat(std::string name) : name(std::move(name)) {} + std::string name; +}; + +// `std::any` becomes the common base of the types it may contain. +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +BOOST_OPENMETHOD(poke, (const virtual_std_any&), std::string); + +// An overrider takes the contained value... +BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { + return dog.name + " barks"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (const Cat& cat), std::string) { + return cat.name + " hisses"; +} + +// ...or the `virtual_any` itself, which makes it a catch-all. +BOOST_OPENMETHOD_OVERRIDE(poke, (const virtual_std_any& value), std::string) { + return value.get().has_value() ? "it does nothing" : "nothing happens"; +} + +#include + +int main() { + initialize(); + + // From an existing `any`: the v-table pointer is looked up from the type + // of the value it contains. + std::any snoopy_any = Dog("Snoopy"); + virtual_std_any snoopy = snoopy_any; + + // From a value: the type is known at compile time, so the v-table pointer + // is read from a static variable, with no lookup. + virtual_std_any felix = Cat("Felix"); + + // Same, constructing the value in place. + auto hector = make_std_any_virtual("Hector"); + + std::cout << poke(snoopy) << "\n"; // Snoopy barks + std::cout << poke(felix) << "\n"; // Felix hisses + std::cout << poke(hector) << "\n"; // Hector barks + + // `int` is registered, but has no overrider of its own: the catch-all + // applies. The value converts to a temporary `virtual_std_any` at the + // call site. + std::cout << poke(42) << "\n"; // it does nothing +} +// end::content[] diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index b6b8b6c2..5f6dc368 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.adoc[Interoperation with Other Libraries] ** xref:shared_libraries.adoc[Shared Libraries] * xref:reference:index.adoc[Reference] ** xref:ref_headers.adoc[Headers] diff --git a/doc/modules/ROOT/pages/interop.adoc b/doc/modules/ROOT/pages/interop.adoc new file mode 100644 index 00000000..25f36bfa --- /dev/null +++ b/doc/modules/ROOT/pages/interop.adoc @@ -0,0 +1,141 @@ + +[#interop] +## Interoperation with Other Libraries + +Some libraries hand us a value whose type is not visible in the static type of +the variable that holds it - a type-erased container, or a pointer class of +their own. This section covers the constructs that let a method look through +such a wrapper and dispatch on what is really inside. + +### `any` + +An `any` holds a value of almost any type, and remembers which type that is. +That is precisely what a method needs in order to pick an overrider. OpenMethod +can thus dispatch on the type _contained_ in an `any`, in effect treating a set +of otherwise unrelated types as a hierarchy rooted at `std::any`. The types need +not be polymorphic, and need not be related to one another - which makes this a +way of adding behavior to types we do not own, including built-in types. + +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]. + +cpp:virtual_std_any[] - an alias for `virtual_any` - is to an `any` +what cpp:virtual_ptr[] is to a pointer: it bundles the `any` with a pointer to +the v-table for the value it contains, so a call does not have to look that +v-table up. Unlike `virtual_ptr`, it _owns_ the object: the `any` is held by +value. + +Overriders receive the _contained_ value, by a reference of a compatible +category - not the wrapper. An overrider may also take the `virtual_std_any` +itself, unchanged; since every registered type derives from `std::any`, 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] +---- + +#### Where the v-table pointer comes from + +A `virtual_any` acquires its v-table pointer once, when it is created, and +maintains it across assignment and `emplace`. There are two ways it can do so: + +- From an existing `any`, the contained type is known only at run time, so the +v-table pointer is looked up in a hash table, keyed on the type of the contained +value. + +- From a value, or from cpp:make_std_any_virtual[], or from `emplace`, the +contained type is known at compile time, so the v-table pointer is simply read +from a static variable - no lookup at all. + +The second form is the one to prefer where we have the choice. + +#### Reference categories + +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. All three +reference categories are supported, and they determine what the overriders may +take: + +[cols="1,2"] +|=== +| Method parameter | Overrider parameter + +| `const virtual_std_any&` +| `const Dog&`, `Dog` + +| `virtual_std_any&` +| `Dog&`, `const Dog&`, `Dog` + +| `virtual_std_any&&` +| `Dog&&`, `const Dog&`, `Dog` +|=== + +A value converts implicitly to a `virtual_std_any`, so `poke(42)` in the example +above creates a temporary at the call site. That temporary binds to `const +virtual_std_any&` and to `virtual_std_any&&`, but nothing binds to a mutable +lvalue reference. Consequently +xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE], which +locates the method by checking that the overrider's parameters can be passed to +the method's forwarder, cannot be used with a `virtual_std_any&` method. Such +overriders are registered with the core API instead - the primitive the macro +itself expands to: + +```c++ +using bump_method = BOOST_OPENMETHOD_TYPE(bump, (virtual_std_any&), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +``` + +#### Dispatching on a plain `any` + +`virtual_std_any` is not mandatory. A `std::any` can also be used directly in a +virtual parameter, wrapped in `virtual_`, as described in +xref:virtual_ptr_alt.adoc[Alternatives to virtual_ptr]: + +```c++ +BOOST_OPENMETHOD(poke, (virtual_), std::string); +``` + +The overriders are written exactly as before, and the same `use_std_any_types` +registration applies. The difference is where the v-table pointer comes from: +there is nowhere to cache it, so every call performs the hash table lookup. + +Which to use: + +- `virtual_`, when the `any` comes from elsewhere - an existing +API, a container of `std::any` - and is dispatched on once. It adds nothing to +the `any`, and requires no change to the code that produces it. + +- `virtual_std_any`, when the same value is dispatched on repeatedly, or when we +create it ourselves and its type is statically known. The lookup then happens +once, or not at all. + +For the same reason, 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. + +#### `boost::any` + +`boost::any` is supported as well, by +``, with cpp:use_boost_any_types[], +cpp:virtual_boost_any[] and cpp:make_boost_any_virtual[] - 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/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp new file mode 100644 index 00000000..7a093f46 --- /dev/null +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -0,0 +1,193 @@ +// 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 { + Dog(std::string name) : name(std::move(name)) { + } + + std::string name; +}; + +struct Cat { + Cat(std::string name) : name(std::move(name)) { + } + + 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(poke, (const virtual_std_any&), std::string); + +// An overrider takes the contained value... +BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { + return dog.name + " barks"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (const Cat& cat), std::string) { + return cat.name + " hisses"; +} + +// ...or the `virtual_any` itself, which makes it a catch-all. +BOOST_OPENMETHOD_OVERRIDE(poke, (const virtual_std_any& value), std::string) { + return value.get().has_value() ? "it does nothing" : "nothing happens"; +} +// end::method[] + +} // namespace std_any + +namespace boost_any { + +// tag::boost_classes[] +struct Dog { + Dog(std::string name) : name(std::move(name)) { + } + + 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(poke, (const virtual_boost_any&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { + return dog.name + " barks"; +} +// end::boost_classes[] + +} // namespace boost_any + +BOOST_AUTO_TEST_CASE(std_any_examples) { + using namespace std_any; + + initialize(); + + { + capture_cout cout; + + // tag::dispatch[] + virtual_std_any snoopy = Dog("Snoopy"); + + std::cout << poke(snoopy) << "\n"; // Snoopy barks + + // `int` 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 << poke(42) << "\n"; // it does nothing + // end::dispatch[] + + BOOST_TEST(cout.str() == "Snoopy barks\nit does nothing\n"); + } + + { + capture_cout cout; + + // tag::from_any[] + std::any snoopy_any = Dog("Snoopy"); + + // the v-table pointer is looked up from the type of the value the + // `any` contains + virtual_std_any snoopy = snoopy_any; + + std::cout << poke(snoopy) << "\n"; // Snoopy barks + // end::from_any[] + + BOOST_TEST(cout.str() == "Snoopy barks\n"); + } + + { + capture_cout cout; + + // tag::from_value[] + // `Cat` is known at compile time, so the v-table pointer is read + // from a static variable - there is no lookup + virtual_std_any felix = Cat("Felix"); + + std::cout << poke(felix) << "\n"; // Felix hisses + // end::from_value[] + + BOOST_TEST(cout.str() == "Felix hisses\n"); + } + + { + capture_cout cout; + + // tag::emplace[] + virtual_std_any animal; + + animal.emplace("Felix"); + + std::cout << poke(animal) << "\n"; // Felix hisses + // end::emplace[] + + BOOST_TEST(cout.str() == "Felix hisses\n"); + } + + { + capture_cout cout; + + // tag::make_any_virtual[] + auto felix = make_any_virtual("Felix"); + + std::cout << poke(felix) << "\n"; // Felix hisses + // end::make_any_virtual[] + + BOOST_TEST(cout.str() == "Felix hisses\n"); + } + + { + capture_cout cout; + + // tag::make_std_any_virtual[] + auto snoopy = make_std_any_virtual("Snoopy"); + + std::cout << poke(snoopy) << "\n"; // Snoopy barks + // end::make_std_any_virtual[] + + BOOST_TEST(cout.str() == "Snoopy barks\n"); + } +} + +BOOST_AUTO_TEST_CASE(boost_any_examples) { + using namespace boost_any; + + initialize(); + + { + capture_cout cout; + + // tag::boost_dispatch[] + auto snoopy = make_boost_any_virtual("Snoopy"); + + std::cout << poke(snoopy) << "\n"; // Snoopy barks + // end::boost_dispatch[] + + BOOST_TEST(cout.str() == "Snoopy barks\n"); + } +} diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 3e77d09e..ca633cdc 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -230,6 +230,11 @@ struct virtual_traits { //! //! @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 Other Libraries](xref:ROOT:interop.adoc) template struct use_boost_any_types : detail::use_class_aux< @@ -242,6 +247,8 @@ struct use_boost_any_types //! Alias for a `virtual_any`, in the default registry. //! //! With another registry, use `virtual_any` directly. +//! +//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) using virtual_boost_any = virtual_any; //! Create a new object and return a `virtual_boost_any` containing it. @@ -258,6 +265,11 @@ using virtual_boost_any = virtual_any; //! @param args Arguments to pass to the constructor of `Class`. //! @return A `virtual_any` containing a newly created //! `Class`. +//! +//! @par Example +//! include:virtual_any.cpp#boost_dispatch +//! +//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 1b0bda96..6269e3c3 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -193,6 +193,11 @@ struct virtual_traits { //! //! @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 Other Libraries](xref:ROOT:interop.adoc) template struct use_std_any_types : detail::use_class_aux< @@ -205,6 +210,8 @@ struct use_std_any_types //! Alias for a `virtual_any`, in the default registry. //! //! With another registry, use `virtual_any` directly. +//! +//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) using virtual_std_any = virtual_any; //! Create a new object and return a `virtual_std_any` containing it. @@ -221,6 +228,11 @@ using virtual_std_any = virtual_any; //! @param args Arguments to pass to the constructor of `Class`. //! @return A `virtual_any` containing a newly created //! `Class`. +//! +//! @par Example +//! include:virtual_any.cpp#make_std_any_virtual +//! +//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 7f61104a..485f569d 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -57,6 +57,11 @@ struct is_virtual_any_aux> : std::true_type {}; //! //! @tparam Any An `any` type. //! @tparam Registry A @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#classes;method;dispatch +//! +//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) template class virtual_any { static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; @@ -81,6 +86,9 @@ class virtual_any { //! 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))) { @@ -106,6 +114,9 @@ class virtual_any { //! //! @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< @@ -205,6 +216,9 @@ class virtual_any { //! @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)...); @@ -491,6 +505,11 @@ struct select_overrider_virtual_type_aux< //! @param args Arguments to pass to the constructor of `Class`. //! @return A `virtual_any` containing a newly created //! `Class`. +//! +//! @par Example +//! include:virtual_any.cpp#make_any_virtual +//! +//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) template< class Class, class Any, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> From 8bcb1a37b4c2783905356d75021e04c0f27e04e0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 13:37:58 -0400 Subject: [PATCH 49/85] alias use_std_any_types and use_boost_any_types The `any` headers aliased their wrapper type and their `make_` function but not the registration helper, so a program that imported `aliases` still had to spell `boost::openmethod::use_std_any_types` - as the doc example did. Alias them too, and let the example use `aliases` like the others. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/examples/virtual_any.cpp | 4 ++-- include/boost/openmethod/interop/boost_any.hpp | 1 + include/boost/openmethod/interop/std_any.hpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/modules/ROOT/examples/virtual_any.cpp b/doc/modules/ROOT/examples/virtual_any.cpp index 83619e30..e94605ff 100644 --- a/doc/modules/ROOT/examples/virtual_any.cpp +++ b/doc/modules/ROOT/examples/virtual_any.cpp @@ -13,7 +13,7 @@ #include #include -using namespace boost::openmethod; +using namespace boost::openmethod::aliases; struct Dog { Dog(std::string name) : name(std::move(name)) {} @@ -47,7 +47,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (const virtual_std_any& value), std::string) { #include int main() { - initialize(); + boost::openmethod::initialize(); // From an existing `any`: the v-table pointer is looked up from the type // of the value it contains. diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index ca633cdc..70125546 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -299,6 +299,7 @@ void final_virtual_ptr(boost::any&&) = delete; namespace aliases { using boost::openmethod::make_boost_any_virtual; +using boost::openmethod::use_boost_any_types; using boost::openmethod::virtual_boost_any; } // namespace aliases diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 6269e3c3..623ec525 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -262,6 +262,7 @@ void final_virtual_ptr(std::any&&) = delete; namespace aliases { using boost::openmethod::make_std_any_virtual; +using boost::openmethod::use_std_any_types; using boost::openmethod::virtual_std_any; } // namespace aliases From 50d5ec91d6f4ed65fa79a9427e67e31e645bfec6 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 13:55:54 -0400 Subject: [PATCH 50/85] support catch-all overriders on plain `any` virtual parameters `virtual_traits::cast` returns the wrapper unchanged when the overrider asks for it, which is how a catch-all overrider is written. The `std::any` and `boost::any` traits had no such case: they always `any_cast` to the overrider's parameter type, so an overrider taking `const std::any&` looked for an `any` stored inside the `any` and threw `bad_any_cast` at run time - the overrider was selected correctly, only the cast was wrong. Give the six `cast` overloads the same `if constexpr` as `virtual_any`, so a method with a `virtual_` parameter - or `&`, or `&&` - can have a catch-all, as one with a `virtual_any` parameter already could. The new tests also cover an `any` virtual parameter dispatching alongside a `virtual_ptr` in the same method, which had no coverage either. Co-Authored-By: Claude Fable 5 --- .../boost/openmethod/interop/boost_any.hpp | 36 +++++- include/boost/openmethod/interop/std_any.hpp | 42 ++++-- test/test_dispatch_boost_any.cpp | 115 +++++++++++++++++ test/test_dispatch_std_any.cpp | 122 +++++++++++++++++- 4 files changed, 296 insertions(+), 19 deletions(-) diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 70125546..1452ba44 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -71,7 +71,9 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `boost::any_cast`. + //! 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`, @@ -87,7 +89,13 @@ struct virtual_traits { !std::is_reference_v || std::is_const_v>>> static auto cast(const boost::any& arg) -> decltype(auto) { - return boost::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } } }; @@ -134,7 +142,9 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `boost::any_cast`. Supports mutable + //! 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`. //! @@ -150,7 +160,13 @@ struct virtual_traits { template< typename U, typename = std::enable_if_t>> static auto cast(boost::any& arg) -> decltype(auto) { - return boost::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } } }; @@ -197,7 +213,9 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `boost::any_cast`. + //! 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 @@ -213,7 +231,13 @@ struct virtual_traits { !std::is_lvalue_reference_v || std::is_const_v>>> static auto cast(boost::any&& arg) -> decltype(auto) { - return boost::any_cast(std::move(arg)); + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return std::move(arg); + } else { + return boost::any_cast(std::move(arg)); + } } }; diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 623ec525..6a13ca43 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -68,15 +68,23 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `std::any_cast`. Since the `any` - //! argument is const, `U` cannot be a mutable reference. + //! 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) { - return std::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } } }; @@ -120,16 +128,24 @@ struct virtual_traits { //! Cast to a type. //! - //! 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`. + //! 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) { - return std::any_cast(arg); + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } } }; @@ -173,14 +189,22 @@ struct virtual_traits { //! Cast to a type. //! - //! Extracts the stored value using `std::any_cast`. + //! 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) { - return std::any_cast(std::move(arg)); + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return std::move(arg); + } else { + return std::any_cast(std::move(arg)); + } } }; diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index bfd850db..7142f9bc 100644 --- a/test/test_dispatch_boost_any.cpp +++ b/test/test_dispatch_boost_any.cpp @@ -172,3 +172,118 @@ BOOST_AUTO_TEST_CASE(boost_any_by_xvalue_ref) { 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 index 9c8e7025..61ee19f5 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -66,8 +66,8 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as std::any& (mutable ref) -static_assert(detail::has_vptr< - virtual_traits, type_id>); +static_assert( + detail::has_vptr, type_id>); MAKE_CLASSES(); @@ -129,8 +129,8 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // pass virtual args as std::any&& (xvalue ref) -static_assert(detail::has_vptr< - virtual_traits, type_id>); +static_assert( + detail::has_vptr, type_id>); MAKE_CLASSES(); @@ -172,3 +172,117 @@ BOOST_AUTO_TEST_CASE(std_any_by_xvalue_ref) { 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 From ff11651ef30435491cf27bee9ec5c1e331c48072 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 13:56:05 -0400 Subject: [PATCH 51/85] doc: lead the Interoperation page with `virtual_`, not `virtual_any` The page opened on `virtual_std_any`, which put the wrapper - an optimization - before the plain thing it optimizes. Lead with `virtual_` instead: the example loses the construction dance and shrinks to a registration, four overriders and four calls. `virtual_std_any` becomes a section of its own, saying what it buys (the v-table lookup happens once, or not at all) and what limits it: the wrapper is not what an overrider receives, so an overrider cannot pass it on and save the lookup again. Only a catch-all overrider gets it. Also note that `any` virtual parameters and ordinary ones mix freely in a multi-method. The example and the reference snippets now use the classes and overriders of test/test_dispatch_std_any.cpp, so a reader moving between them meets one cast rather than two. `float` is registered without an overrider of its own, which is what the catch-all demonstrates - previously that role fell to `int`, which read as if it were registered for no reason. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/examples/virtual_any.cpp | 60 +++++------ doc/modules/ROOT/pages/interop.adoc | 115 +++++++++++----------- doc/modules/ROOT/snippets/virtual_any.cpp | 97 +++++++++--------- 3 files changed, 127 insertions(+), 145 deletions(-) diff --git a/doc/modules/ROOT/examples/virtual_any.cpp b/doc/modules/ROOT/examples/virtual_any.cpp index e94605ff..f1a6adf8 100644 --- a/doc/modules/ROOT/examples/virtual_any.cpp +++ b/doc/modules/ROOT/examples/virtual_any.cpp @@ -13,61 +13,51 @@ #include #include -using namespace boost::openmethod::aliases; +using namespace boost::openmethod; struct Dog { - Dog(std::string name) : name(std::move(name)) {} - std::string name; -}; - -struct Cat { - Cat(std::string name) : name(std::move(name)) {} std::string name; }; // `std::any` becomes the common base of the types it may contain. -BOOST_OPENMETHOD_REGISTER(use_std_any_types); +BOOST_OPENMETHOD_REGISTER(use_std_any_types); -BOOST_OPENMETHOD(poke, (const virtual_std_any&), std::string); +BOOST_OPENMETHOD(name, (virtual_), std::string); // An overrider takes the contained value... -BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { - return dog.name + " barks"; +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; } -BOOST_OPENMETHOD_OVERRIDE(poke, (const Cat& cat), std::string) { - return cat.name + " hisses"; +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; } -// ...or the `virtual_any` itself, which makes it a catch-all. -BOOST_OPENMETHOD_OVERRIDE(poke, (const virtual_std_any& value), std::string) { - return value.get().has_value() ? "it does nothing" : "nothing happens"; +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() { - boost::openmethod::initialize(); - - // From an existing `any`: the v-table pointer is looked up from the type - // of the value it contains. - std::any snoopy_any = Dog("Snoopy"); - virtual_std_any snoopy = snoopy_any; - - // From a value: the type is known at compile time, so the v-table pointer - // is read from a static variable, with no lookup. - virtual_std_any felix = Cat("Felix"); + initialize(); - // Same, constructing the value in place. - auto hector = make_std_any_virtual("Hector"); + std::any spot = Dog{"Spot"}; + std::any felix = std::string("Felix the cat"); + std::any answer = 42; + std::any pi = 3.14f; - std::cout << poke(snoopy) << "\n"; // Snoopy barks - std::cout << poke(felix) << "\n"; // Felix hisses - std::cout << poke(hector) << "\n"; // Hector barks + std::cout << name(spot) << "\n"; // Spot the dog + std::cout << name(felix) << "\n"; // Felix the cat + std::cout << name(answer) << "\n"; // 42 the integer - // `int` is registered, but has no overrider of its own: the catch-all - // applies. The value converts to a temporary `virtual_std_any` at the - // call site. - std::cout << poke(42) << "\n"; // it does nothing + // `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/pages/interop.adoc b/doc/modules/ROOT/pages/interop.adoc index 25f36bfa..adb907d8 100644 --- a/doc/modules/ROOT/pages/interop.adoc +++ b/doc/modules/ROOT/pages/interop.adoc @@ -26,15 +26,11 @@ 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]. -cpp:virtual_std_any[] - an alias for `virtual_any` - is to an `any` -what cpp:virtual_ptr[] is to a pointer: it bundles the `any` with a pointer to -the v-table for the value it contains, so a call does not have to look that -v-table up. Unlike `virtual_ptr`, it _owns_ the object: the `any` is held by -value. - -Overriders receive the _contained_ value, by a reference of a compatible -category - not the wrapper. An overrider may also take the `virtual_std_any` -itself, unchanged; since every registered type derives from `std::any`, such an +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 a reference of a compatible category. An overrider may +also 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: @@ -43,54 +39,54 @@ specific overrider: include::example$virtual_any.cpp[tag=content] ---- -#### Where the v-table pointer comes from +#### Mixing with ordinary virtual parameters -A `virtual_any` acquires its v-table pointer once, when it is created, and -maintains it across assignment and `emplace`. There are two ways it can do so: +An `any` virtual parameter is an ordinary virtual parameter that happens to +resolve through the contained type, so it composes with the others without +restriction. A multi-method can dispatch on an `any` and on a `virtual_ptr`, or +a plain reference, in the same call: -- From an existing `any`, the contained type is known only at run time, so the -v-table pointer is looked up in a hash table, keyed on the type of the contained -value. - -- From a value, or from cpp:make_std_any_virtual[], or from `emplace`, the -contained type is known at compile time, so the v-table pointer is simply read -from a static variable - no lookup at all. +```c++ +BOOST_OPENMETHOD( + meet, (virtual_, virtual_ptr), std::string); -The second form is the one to prefer where we have the choice. +BOOST_OPENMETHOD_OVERRIDE( + meet, (const Dog& dog, virtual_ptr), std::string) { + return dog.name + " meets a cat"; +} +``` #### Reference categories -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. All three -reference categories are supported, and they determine what the overriders may -take: +All three reference categories are supported, and they determine what the +overriders may take: [cols="1,2"] |=== | Method parameter | Overrider parameter -| `const virtual_std_any&` +| `virtual_` | `const Dog&`, `Dog` -| `virtual_std_any&` +| `virtual_` | `Dog&`, `const Dog&`, `Dog` -| `virtual_std_any&&` +| `virtual_` | `Dog&&`, `const Dog&`, `Dog` |=== -A value converts implicitly to a `virtual_std_any`, so `poke(42)` in the example -above creates a temporary at the call site. That temporary binds to `const -virtual_std_any&` and to `virtual_std_any&&`, but nothing binds to a mutable -lvalue reference. Consequently -xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE], which -locates the method by checking that the overrider's parameters can be passed to -the method's forwarder, cannot be used with a `virtual_std_any&` method. Such -overriders are registered with the core API instead - the primitive the macro -itself expands to: +The mutable lvalue reference is the awkward one. +xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] locates +the method by checking that the overrider's parameters can be passed to the +method's forwarder, and `Dog&` does not convert to `std::any&`. A temporary +`std::any` binds to `const std::any&` and to `std::any&&`, which is why the +other two categories can use the macro; nothing binds to a mutable lvalue +reference. Those overriders are registered with the core API instead - the +primitive the macro itself expands to: ```c++ -using bump_method = BOOST_OPENMETHOD_TYPE(bump, (virtual_std_any&), std::string); +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); auto bump_dog(Dog& dog) -> std::string { dog.name += " Jr."; @@ -100,33 +96,34 @@ auto bump_dog(Dog& dog) -> std::string { BOOST_OPENMETHOD_REGISTER(bump_method::override); ``` -#### Dispatching on a plain `any` +#### `virtual_std_any` -`virtual_std_any` is not mandatory. A `std::any` can also be used directly in a -virtual parameter, wrapped in `virtual_`, as described in -xref:virtual_ptr_alt.adoc[Alternatives to virtual_ptr]: +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 to an `any` what cpp:virtual_ptr[] is to a +pointer, except that it _owns_ the object: the `any` is held by value. -```c++ -BOOST_OPENMETHOD(poke, (virtual_), std::string); -``` - -The overriders are written exactly as before, and the same `use_std_any_types` -registration applies. The difference is where the v-table pointer comes from: -there is nowhere to cache it, so every call performs the hash table lookup. +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 cpp:make_std_any_virtual[], or by `emplace`, since the type +is then known at compile time. -Which to use: +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. -- `virtual_`, when the `any` comes from elsewhere - an existing -API, a container of `std::any` - and is dispatched on once. It adds nothing to -the `any`, and requires no change to the code that produces it. - -- `virtual_std_any`, when the same value is dispatched on repeatedly, or when we -create it ourselves and its type is statically known. The lookup then happens -once, or not at all. +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, 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. +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. #### `boost::any` diff --git a/doc/modules/ROOT/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp index 7a093f46..ad8e3713 100644 --- a/doc/modules/ROOT/snippets/virtual_any.cpp +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -23,38 +23,32 @@ namespace std_any { // tag::classes[] struct Dog { - Dog(std::string name) : name(std::move(name)) { - } - - std::string name; -}; - -struct Cat { - Cat(std::string name) : name(std::move(name)) { - } - std::string name; }; // `std::any` becomes the common base of the types it may contain. -BOOST_OPENMETHOD_REGISTER(use_std_any_types); +BOOST_OPENMETHOD_REGISTER(use_std_any_types); // end::classes[] // tag::method[] -BOOST_OPENMETHOD(poke, (const virtual_std_any&), std::string); +BOOST_OPENMETHOD(name, (const virtual_std_any&), std::string); // An overrider takes the contained value... -BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { - return dog.name + " barks"; +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; } -BOOST_OPENMETHOD_OVERRIDE(poke, (const Cat& cat), std::string) { - return cat.name + " hisses"; +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(poke, (const virtual_std_any& value), std::string) { - return value.get().has_value() ? "it does nothing" : "nothing happens"; +BOOST_OPENMETHOD_OVERRIDE(name, (const virtual_std_any& value), std::string) { + return value.get().has_value() ? "something else" : "nothing"; } // end::method[] @@ -64,20 +58,21 @@ namespace boost_any { // tag::boost_classes[] struct Dog { - Dog(std::string name) : name(std::move(name)) { - } - 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_REGISTER(use_boost_any_types); + +BOOST_OPENMETHOD(name, (const virtual_boost_any&), std::string); -BOOST_OPENMETHOD(poke, (const virtual_boost_any&), std::string); +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} -BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { - return dog.name + " barks"; +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; } // end::boost_classes[] @@ -92,85 +87,85 @@ BOOST_AUTO_TEST_CASE(std_any_examples) { capture_cout cout; // tag::dispatch[] - virtual_std_any snoopy = Dog("Snoopy"); + virtual_std_any spot = Dog{"Spot"}; - std::cout << poke(snoopy) << "\n"; // Snoopy barks + std::cout << name(spot) << "\n"; // Spot the dog - // `int` is registered, but has no overrider of its own, so the + // `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 << poke(42) << "\n"; // it does nothing + std::cout << name(3.14f) << "\n"; // something else // end::dispatch[] - BOOST_TEST(cout.str() == "Snoopy barks\nit does nothing\n"); + BOOST_TEST(cout.str() == "Spot the dog\nsomething else\n"); } { capture_cout cout; // tag::from_any[] - std::any snoopy_any = Dog("Snoopy"); + 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 snoopy = snoopy_any; + virtual_std_any spot = spot_any; - std::cout << poke(snoopy) << "\n"; // Snoopy barks + std::cout << name(spot) << "\n"; // Spot the dog // end::from_any[] - BOOST_TEST(cout.str() == "Snoopy barks\n"); + BOOST_TEST(cout.str() == "Spot the dog\n"); } { capture_cout cout; // tag::from_value[] - // `Cat` is known at compile time, so the v-table pointer is read + // 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 felix = Cat("Felix"); + virtual_std_any answer = 42; - std::cout << poke(felix) << "\n"; // Felix hisses + std::cout << name(answer) << "\n"; // 42 the integer // end::from_value[] - BOOST_TEST(cout.str() == "Felix hisses\n"); + BOOST_TEST(cout.str() == "42 the integer\n"); } { capture_cout cout; // tag::emplace[] - virtual_std_any animal; + virtual_std_any value; - animal.emplace("Felix"); + value.emplace("Felix the cat"); - std::cout << poke(animal) << "\n"; // Felix hisses + std::cout << name(value) << "\n"; // Felix the cat // end::emplace[] - BOOST_TEST(cout.str() == "Felix hisses\n"); + BOOST_TEST(cout.str() == "Felix the cat\n"); } { capture_cout cout; // tag::make_any_virtual[] - auto felix = make_any_virtual("Felix"); + auto felix = make_any_virtual("Felix the cat"); - std::cout << poke(felix) << "\n"; // Felix hisses + std::cout << name(felix) << "\n"; // Felix the cat // end::make_any_virtual[] - BOOST_TEST(cout.str() == "Felix hisses\n"); + BOOST_TEST(cout.str() == "Felix the cat\n"); } { capture_cout cout; // tag::make_std_any_virtual[] - auto snoopy = make_std_any_virtual("Snoopy"); + auto felix = make_std_any_virtual("Felix the cat"); - std::cout << poke(snoopy) << "\n"; // Snoopy barks + std::cout << name(felix) << "\n"; // Felix the cat // end::make_std_any_virtual[] - BOOST_TEST(cout.str() == "Snoopy barks\n"); + BOOST_TEST(cout.str() == "Felix the cat\n"); } } @@ -183,11 +178,11 @@ BOOST_AUTO_TEST_CASE(boost_any_examples) { capture_cout cout; // tag::boost_dispatch[] - auto snoopy = make_boost_any_virtual("Snoopy"); + auto felix = make_boost_any_virtual("Felix the cat"); - std::cout << poke(snoopy) << "\n"; // Snoopy barks + std::cout << name(felix) << "\n"; // Felix the cat // end::boost_dispatch[] - BOOST_TEST(cout.str() == "Snoopy barks\n"); + BOOST_TEST(cout.str() == "Felix the cat\n"); } } From acdd8e0324e857bc06c45da985879fe1a7bfe290 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 15:10:37 -0400 Subject: [PATCH 52/85] test: make the virtual_any-by-value compile-fail test actually fail on MSVC `BOOST_OPENMETHOD` only declares a forwarder function template; it does not instantiate `method<...>`. The guard against a by-value `virtual_any` lives in the `method` class body, so GCC and Clang - which instantiate the class at the declaration - diagnosed it, while MSVC waited until the method was used. The test never used it, so it compiled clean and the `*fail` target failed on both Windows Drone stages. Call the method in `main()`, like every other compile-fail test. Co-Authored-By: Claude Opus 5 --- test/compile_fail_virtual_any_by_value.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/compile_fail_virtual_any_by_value.cpp b/test/compile_fail_virtual_any_by_value.cpp index 97ea54ba..22afdf0b 100644 --- a/test/compile_fail_virtual_any_by_value.cpp +++ b/test/compile_fail_virtual_any_by_value.cpp @@ -22,5 +22,6 @@ BOOST_OPENMETHOD_REGISTER(use_std_any_types); BOOST_OPENMETHOD(name, (virtual_std_any), std::string); int main() { - return 0; + auto dog = make_std_any_virtual(Dog{"Snoopy"}); + return name(dog).size(); } From e946dd26a41432b1b19b8fa6ea1d95a45b3bc53c Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 15:38:22 -0400 Subject: [PATCH 53/85] doc: fix the broken `any` header links on the Headers page The virtual_any, std_any and boost_any entries spelled the source link as `{{BASE_URL}}/...`, which Antora does not substitute, so the three links rendered with the placeholder as literal text. Use `{base-url}`, the attribute defined in antora.yml and used by the other 17 header links. Co-Authored-By: Claude Opus 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 7c30c404..fd6df86e 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -72,7 +72,7 @@ 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:{{BASE_URL}}/include/boost/openmethod/interop/virtual_any.hpp[] +### link:{base-url}/include/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 - like `virtual_ptr` @@ -86,7 +86,7 @@ with an `any`-like interface, given `virtual_traits` specializations for its reference types. [#std_any] -### link:{{BASE_URL}}/include/boost/openmethod/interop/std_any.hpp[] +### link:{base-url}/include/boost/openmethod/interop/std_any.hpp[] Provides `virtual_traits` specializations that make it possible to use a `std::any` - by const reference, by mutable reference, or by rvalue reference - in virtual @@ -98,7 +98,7 @@ parameters. Dispatch is on the type of the contained value. Also provides use the v-table of the `any` root class instead of the contained value's. [#boost_any] -### link:{{BASE_URL}}/include/boost/openmethod/interop/boost_any.hpp[] +### link:{base-url}/include/boost/openmethod/interop/boost_any.hpp[] Provides `virtual_traits` specializations that make it possible to use a `boost::any` - by const reference, by mutable reference, or by rvalue reference - in virtual From f1d3cd82ec98cfabeb136717cd630b273cd75f32 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 15:46:14 -0400 Subject: [PATCH 54/85] doc: narrow the Interoperation page to `any` Every section of the page is about dispatching on the type contained in an `any`, but the title, the file name and the opening paragraph all promised a broader page. Rename interop.adoc to interop_any.adoc, retitle it "Interoperation with `any`", and drop the intro's "or a pointer class of their own" clause, which anticipated content the page does not have. Update the nav entry, the page anchor, and the eight `@see` links in the interop headers. Co-Authored-By: Claude Opus 5 --- doc/modules/ROOT/nav.adoc | 2 +- .../ROOT/pages/{interop.adoc => interop_any.adoc} | 11 +++++------ include/boost/openmethod/interop/boost_any.hpp | 6 +++--- include/boost/openmethod/interop/std_any.hpp | 6 +++--- include/boost/openmethod/interop/virtual_any.hpp | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) rename doc/modules/ROOT/pages/{interop.adoc => interop_any.adoc} (94%) diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 5f6dc368..6edd8231 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -13,7 +13,7 @@ ** xref:custom_rtti.adoc[Custom RTTI] ** xref:error_handling.adoc[Error Handling] ** xref:virtual_ptr_alt.adoc[Virtual Pointer Alternatives] -** xref:interop.adoc[Interoperation with Other Libraries] +** xref:interop_any.adoc[Interoperation with `any`] ** xref:shared_libraries.adoc[Shared Libraries] * xref:reference:index.adoc[Reference] ** xref:ref_headers.adoc[Headers] diff --git a/doc/modules/ROOT/pages/interop.adoc b/doc/modules/ROOT/pages/interop_any.adoc similarity index 94% rename from doc/modules/ROOT/pages/interop.adoc rename to doc/modules/ROOT/pages/interop_any.adoc index adb907d8..d91e7c86 100644 --- a/doc/modules/ROOT/pages/interop.adoc +++ b/doc/modules/ROOT/pages/interop_any.adoc @@ -1,11 +1,10 @@ -[#interop] -## Interoperation with Other Libraries +[#interop_any] +## Interoperation with `any` -Some libraries hand us a value whose type is not visible in the static type of -the variable that holds it - a type-erased container, or a pointer class of -their own. This section covers the constructs that let a method look through -such a wrapper and dispatch on what is really inside. +A value held in an `any` has a type that is not visible in the static type of +the variable holding the `any`. This section covers the constructs that let a +method look through the wrapper and dispatch on what is really inside. ### `any` diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 1452ba44..6bab0463 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -258,7 +258,7 @@ struct virtual_traits { //! @par Example //! include:virtual_any.cpp#boost_classes;boost_dispatch //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) template struct use_boost_any_types : detail::use_class_aux< @@ -272,7 +272,7 @@ struct use_boost_any_types //! //! With another registry, use `virtual_any` directly. //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) using virtual_boost_any = virtual_any; //! Create a new object and return a `virtual_boost_any` containing it. @@ -293,7 +293,7 @@ using virtual_boost_any = virtual_any; //! @par Example //! include:virtual_any.cpp#boost_dispatch //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 6a13ca43..73f05f71 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -221,7 +221,7 @@ struct virtual_traits { //! @par Example //! include:virtual_any.cpp#classes //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) template struct use_std_any_types : detail::use_class_aux< @@ -235,7 +235,7 @@ struct use_std_any_types //! //! With another registry, use `virtual_any` directly. //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) using virtual_std_any = virtual_any; //! Create a new object and return a `virtual_std_any` containing it. @@ -256,7 +256,7 @@ using virtual_std_any = virtual_any; //! @par Example //! include:virtual_any.cpp#make_std_any_virtual //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 485f569d..6a9e1171 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -61,7 +61,7 @@ struct is_virtual_any_aux> : std::true_type {}; //! @par Example //! include:virtual_any.cpp#classes;method;dispatch //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) template class virtual_any { static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; @@ -509,7 +509,7 @@ struct select_overrider_virtual_type_aux< //! @par Example //! include:virtual_any.cpp#make_any_virtual //! -//! @see [Interoperation with Other Libraries](xref:ROOT:interop.adoc) +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) template< class Class, class Any, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> From 96639749adb77407f467d0308e98132e0dcc8e4e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 16:13:54 -0400 Subject: [PATCH 55/85] doc: an Interoperation with Boost.TypeErasure page, and reference examples Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/examples/type_erasure.cpp | 67 +++++++++ .../ROOT/examples/type_erasure_ref.cpp | 59 ++++++++ doc/modules/ROOT/nav.adoc | 1 + doc/modules/ROOT/pages/interop_any.adoc | 2 + .../ROOT/pages/interop_type_erasure.adoc | 131 ++++++++++++++++++ doc/modules/ROOT/snippets/type_erasure.cpp | 78 +++++++++++ .../openmethod/interop/boost_type_erasure.hpp | 15 ++ 7 files changed, 353 insertions(+) create mode 100644 doc/modules/ROOT/examples/type_erasure.cpp create mode 100644 doc/modules/ROOT/examples/type_erasure_ref.cpp create mode 100644 doc/modules/ROOT/pages/interop_type_erasure.adoc create mode 100644 doc/modules/ROOT/snippets/type_erasure.cpp diff --git a/doc/modules/ROOT/examples/type_erasure.cpp b/doc/modules/ROOT/examples/type_erasure.cpp new file mode 100644 index 00000000..21920eb4 --- /dev/null +++ b/doc/modules/ROOT/examples/type_erasure.cpp @@ -0,0 +1,67 @@ +// 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 +#include + +#include +#include + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +// `relaxed` implies `typeid_<>`, which dispatch relies on. +using Concept = boost::mpl::vector, te::relaxed>; +using erased = te::any; + +struct Dog { + std::string name; +}; + +// The owning flavor, `any`, becomes the common base of the types +// the `any` may bind. +BOOST_OPENMETHOD_REGISTER(use_type_erasure_types); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +// An overrider takes the bound 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; +} + +// ...or the `any` itself, which makes it a catch-all. +BOOST_OPENMETHOD_OVERRIDE(name, (const erased& value), std::string) { + return te::is_empty(value) ? "nothing" : "something else"; +} + +#include + +int main() { + initialize(); + + const erased spot(Dog{"Spot"}); + const erased felix(std::string("Felix the cat")); + const erased answer(42); + + std::cout << name(spot) << "\n"; // Spot the dog + std::cout << name(felix) << "\n"; // Felix the cat + + // `int` is registered, but has no overrider of its own, so the + // catch-all applies. + std::cout << name(answer) << "\n"; // something else +} +// end::content[] diff --git a/doc/modules/ROOT/examples/type_erasure_ref.cpp b/doc/modules/ROOT/examples/type_erasure_ref.cpp new file mode 100644 index 00000000..13daf9f2 --- /dev/null +++ b/doc/modules/ROOT/examples/type_erasure_ref.cpp @@ -0,0 +1,59 @@ +// 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 + +#include +#include + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +using Concept = boost::mpl::vector, te::relaxed>; +using erased_ref = te::any; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_type_erasure_types); + +// The reference-wrapper flavor is a cheap handle; it is passed by value. +BOOST_OPENMETHOD(poke, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog& dog), std::string) { + dog.name += "!"; + return dog.name; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (int& value), std::string) { + ++value; + return "poked"; +} + +#include + +int main() { + initialize(); + + Dog snoopy{"Snoopy"}; + int count = 41; + + // mutations reach the referents + std::cout << poke(erased_ref(snoopy)) << "\n"; // Snoopy! + std::cout << snoopy.name << "\n"; // Snoopy! + + std::cout << poke(erased_ref(count)) << "\n"; // poked + std::cout << count << "\n"; // 42 +} +// end::content[] diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 6edd8231..859605d7 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -14,6 +14,7 @@ ** xref:error_handling.adoc[Error Handling] ** xref:virtual_ptr_alt.adoc[Virtual Pointer Alternatives] ** xref:interop_any.adoc[Interoperation with `any`] +** xref:interop_type_erasure.adoc[Interoperation with Boost.TypeErasure] ** xref:shared_libraries.adoc[Shared Libraries] * xref:reference:index.adoc[Reference] ** xref:ref_headers.adoc[Headers] diff --git a/doc/modules/ROOT/pages/interop_any.adoc b/doc/modules/ROOT/pages/interop_any.adoc index d91e7c86..0f5d2149 100644 --- a/doc/modules/ROOT/pages/interop_any.adoc +++ b/doc/modules/ROOT/pages/interop_any.adoc @@ -135,3 +135,5 @@ 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. +Boost.TypeErasure's `any` is supported on the same model - see +xref:interop_type_erasure.adoc[Interoperation with Boost.TypeErasure]. diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc new file mode 100644 index 00000000..957406ff --- /dev/null +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -0,0 +1,131 @@ + +[#interop_type_erasure] +## Interoperation with Boost.TypeErasure + +link:https://www.boost.org/doc/libs/release/doc/html/boost_typeerasure.html[Boost.TypeErasure]'s +`any` erases the type of its content, like `std::any`, but couples that with a +Concept: a compile-time list of the operations the content must support. +OpenMethod can dispatch on the type bound to such an `any`, in the same manner +as for a plain `any` - see xref:interop_any.adoc[Interoperation with `any`]. + +Support is provided by ``. It +is not included by ``, so it must be included explicitly. + +### `type_erasure::any` + +Dispatch resolves on the type returned by `boost::type_erasure::typeid_of`, so +the only requirement placed on the Concept is that it contain +`boost::type_erasure::typeid_<>` - which `relaxed` already implies. + +The types the `any` may bind have to be registered. +cpp:use_type_erasure_types[] does that, registering the owning flavor - +`any` - as a class, and each of the types as a class derived from it. +Each Concept gets a root of its own, so `any`s with different Concepts - and +the plain `any`s - can coexist in the same registry. A type that is not +registered cannot be dispatched on; a call with such a value bound to the +`any` is a cpp:missing_class[] error - see +xref:error_handling.adoc[Error Handling]. + +The `any` is passed wrapped in `virtual_`, and overriders receive the _bound_ +value - or, for a catch-all overrider, the `any` itself: + +[source,c++] +---- +include::example$type_erasure.cpp[tag=content] +---- + +#### Reference categories + +The owning flavor is passed by reference, in any of the three categories - +passing it by value would copy the bound value on every call, and is rejected +at compile time. The category determines 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 has the same limitation as `virtual_`: +xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] +cannot locate the method, because nothing binds a temporary `any` to a mutable +lvalue reference - see the +xref:interop_any.adoc#interop_any[explanation there]. Those overriders are +registered with the core API instead: + +```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); +``` + +`boost::type_erasure::any_cast` has no rvalue overload, so, in the rvalue +case, moving the value out of the `any` is performed by the interop code +itself: an overrider taking `Dog&&` receives the bound value ready to be moved +from, and the `any` still owns the moved-from object afterwards. + +#### The reference-wrapper flavors + +Boost.TypeErasure also has non-owning flavors, `any` and +`any`, which hold a _reference_ to a value stored +elsewhere. They are cheap, two-word handles, and, unlike the owning flavor, +they are passed by value - the idiomatic way to use them as parameters. +Modifications made through a mutable-reference wrapper reach the referent: + +[source,c++] +---- +include::example$type_erasure_ref.cpp[tag=content] +---- + +Dispatch is on the type _bound at construction_ of the wrapper - never on the +C++ RTTI dynamic type of the referent. The rvalue-reference flavor +(`any`), and placeholders other than `_self`, are not +supported. + +#### `virtual_any` + +Every call above looks the v-table up in a hash table, keyed on the type the +`any` binds. cpp:virtual_any[] works for a `type_erasure::any` exactly as it +does for a `std::any`: `virtual_any>` bundles the `any` with the +v-table pointer for the value inside it, acquiring it once, on construction - +or not at all, when it is built from a value or by cpp:make_any_virtual[], +since the type is then known at compile time: + +```c++ +BOOST_OPENMETHOD(name, (const virtual_any&), std::string); + +// from an `any`: one lookup, at construction +virtual_any spot = erased(Dog{"Spot"}); + +// from a value, or with make_any_virtual: no lookup at all +virtual_any rex = Dog{"Rex"}; +auto snoopy = make_any_virtual(Dog{"Snoopy"}); +``` + +The Concept must contain `relaxed` - `virtual_any`'s default constructor and +assignment rely on it - and `copy_constructible<>`, for copies. + +For the same reason as for `std::any`, cpp:final_virtual_ptr[] is _deleted_ +for `type_erasure::any`: it would silently produce the v-table of the root +class rather than the one for the bound value. + +#### Empty `any`s + +An empty relaxed `any` reports `typeid(void)`, which is not a registered +class, so dispatching on it is a cpp:missing_class[] error. A catch-all +overrider does not help: dispatch never reaches it. Check with +`boost::type_erasure::is_empty` before calling. diff --git a/doc/modules/ROOT/snippets/type_erasure.cpp b/doc/modules/ROOT/snippets/type_erasure.cpp new file mode 100644 index 00000000..872247c7 --- /dev/null +++ b/doc/modules/ROOT/snippets/type_erasure.cpp @@ -0,0 +1,78 @@ +// 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 +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include "capture.hpp" + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +// tag::classes[] +// `relaxed` implies `typeid_<>`, which dispatch relies on. +using Concept = boost::mpl::vector, te::relaxed>; +using erased = te::any; + +struct Dog { + std::string name; +}; + +// The owning flavor, `any`, becomes the common base of the types +// the `any` may bind. +BOOST_OPENMETHOD_REGISTER( + use_type_erasure_types); +// end::classes[] + +// tag::method[] +BOOST_OPENMETHOD(name, (virtual_), std::string); + +// An overrider takes the bound 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; +} + +// ...or the `any` itself, which makes it a catch-all. +BOOST_OPENMETHOD_OVERRIDE(name, (const erased& value), std::string) { + return te::is_empty(value) ? "nothing" : "something else"; +} +// end::method[] + +BOOST_AUTO_TEST_CASE(type_erasure_examples) { + initialize(); + + { + capture_cout cout; + + // tag::dispatch[] + const erased spot(Dog{"Spot"}); + const erased answer(42); + + std::cout << name(spot) << "\n"; // Spot the dog + + // `int` is registered, but has no overrider of its own, so the + // catch-all applies. + std::cout << name(answer) << "\n"; // something else + // end::dispatch[] + + BOOST_TEST(cout.str() == "Spot the dog\nsomething else\n"); + } +} diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index ae0461a2..b5340069 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -125,6 +125,8 @@ struct validate_method_parameter< //! @tparam C The `any`'s Concept. //! @tparam T The `any`'s placeholder. //! @tparam Registry A @ref registry. +//! +//! @see [Interoperation with Boost.TypeErasure](xref:ROOT:interop_type_erasure.adoc) template struct virtual_traits&, Registry> { //! The type used for dispatch: the owning flavor for `C`. @@ -188,6 +190,8 @@ struct virtual_traits&, Registry> { //! @tparam C The `any`'s Concept. //! @tparam T The `any`'s placeholder. //! @tparam Registry A @ref registry. +//! +//! @see [Interoperation with Boost.TypeErasure](xref:ROOT:interop_type_erasure.adoc) template struct virtual_traits&, Registry> { //! The type used for dispatch: the owning flavor for `C`. @@ -251,6 +255,8 @@ struct virtual_traits&, Registry> { //! @tparam C The `any`'s Concept. //! @tparam T The `any`'s placeholder. //! @tparam Registry A @ref registry. +//! +//! @see [Interoperation with Boost.TypeErasure](xref:ROOT:interop_type_erasure.adoc) template struct virtual_traits&&, Registry> { //! The type used for dispatch: the owning flavor for `C`. @@ -324,6 +330,8 @@ struct virtual_traits&&, Registry> { //! @tparam C The `any`'s Concept. //! @tparam T The referent placeholder (`_self` for `any`). //! @tparam Registry A @ref registry. +//! +//! @see [Interoperation with Boost.TypeErasure](xref:ROOT:interop_type_erasure.adoc) template struct virtual_traits, Registry> { //! The type used for dispatch: the owning flavor for `C`. @@ -388,6 +396,8 @@ struct virtual_traits, Registry> { //! @tparam T The referent placeholder (`_self` for //! `any`). //! @tparam Registry A @ref registry. +//! +//! @see [Interoperation with Boost.TypeErasure](xref:ROOT:interop_type_erasure.adoc) template struct virtual_traits, Registry> { //! The type used for dispatch: the owning flavor for `C`. @@ -454,6 +464,11 @@ struct virtual_traits, Registry> { //! @tparam Any A `boost::type_erasure::any` type. //! @tparam T... The types that may be bound to the `any`, optionally //! followed by a @ref registry. +//! +//! @par Example +//! include:type_erasure.cpp#classes +//! +//! @see [Interoperation with Boost.TypeErasure](xref:ROOT:interop_type_erasure.adoc) template struct use_type_erasure_types : detail::use_any_types_aux< From 15ddee5f9e622fc964016d7626204c457ef84930 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 8 Aug 2026 16:13:58 -0400 Subject: [PATCH 56/85] doc: fix the broken type_erasure header link on the Headers page Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index d694af7f..68fac536 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -110,7 +110,7 @@ parameters. Dispatch is on the type of the contained value. Also provides use the v-table of the `any` root class instead of the contained value's. [#boost_type_erasure] -### link:{{BASE_URL}}/include/boost/openmethod/interop/boost_type_erasure.hpp[] +### link:{base-url}/include/boost/openmethod/interop/boost_type_erasure.hpp[] Provides `virtual_traits` specializations that make it possible to use a `boost::type_erasure::any` in virtual parameters: the owning flavor by const, From 22aad401dc26df6981c333f3f68aee640dd5c724 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 11:03:09 -0400 Subject: [PATCH 57/85] doc: remove base-url dance --- doc/antora.yml | 4 --- doc/build_antora.sh | 83 ++------------------------------------------- doc/mrdocs.yml | 1 - 3 files changed, 2 insertions(+), 86 deletions(-) diff --git a/doc/antora.yml b/doc/antora.yml index f26c5d02..dfcfac86 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -15,10 +15,6 @@ asciidoc: attributes: source-language: asciidoc@ table-caption: false - # Base of the links to header sources in ref_headers.adoc. build_antora.sh - # overrides this with the exact commit when it can determine one; this is - # the fallback for builds that cannot, such as a local preview. - base-url: https://github.com/boostorg/openmethod/blob/master nav: - modules/ROOT/nav.adoc ext: diff --git a/doc/build_antora.sh b/doc/build_antora.sh index 0b6f680a..7d287c05 100755 --- a/doc/build_antora.sh +++ b/doc/build_antora.sh @@ -22,93 +22,14 @@ fi SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "$SCRIPT_DIR" -if [ -z "${BOOST_SRC_DIR:-}" ]; then - CANDIDATE=$( cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd ) - if [ -n "$CANDIDATE" ]; then - BOOST_SRC_DIR_IS_VALID=ON - for F in "CMakeLists.txt" "Jamroot" "boost-build.jam" "bootstrap.sh" "libs"; do - if [ ! -e "$CANDIDATE/$F" ]; then - BOOST_SRC_DIR_IS_VALID=OFF - break - fi - done - if [ "$BOOST_SRC_DIR_IS_VALID" = "ON" ]; then - export BOOST_SRC_DIR="$CANDIDATE" - echo "Using BOOST_SRC_DIR=$BOOST_SRC_DIR" - fi - fi -fi - -BRANCH=master - -if [ -n "${BOOST_SRC_DIR:-}" ]; then - if [ -n "${CIRCLE_REPOSITORY_URL:-}" ]; then - if [[ "$CIRCLE_REPOSITORY_URL" =~ boostorg/boost(\.git)?$ ]]; then - LIB="$(basename "$(dirname "$SCRIPT_DIR")")" - REPOSITORY="boostorg/${LIB}" - BRANCH=$(git -C "$BOOST_SRC_DIR" rev-parse --abbrev-ref HEAD) - else - ACCOUNT="${CIRCLE_REPOSITORY_URL#*:}" - ACCOUNT="${ACCOUNT%%/*}" - LIB=$(basename "$(git rev-parse --show-toplevel)") - REPOSITORY="${ACCOUNT}/${LIB}" - fi - SHA=$(git -C "$BOOST_SRC_DIR/libs" ls-tree HEAD | grep -w openmethod | awk '{print $3}') - elif [ -n "${GITHUB_REPOSITORY:-}" ]; then - REPOSITORY="${GITHUB_REPOSITORY}" - SHA="${GITHUB_SHA}" - fi -fi - -cd "$SCRIPT_DIR" - -# MrDocs takes its own base-url - the one behind the "Declared in
" link -# on every reference page - from mrdocs.yml, and the Antora extension invokes it -# with a fixed argument list, so there is no way to pass the commit other than -# editing the file. Restore it from an EXIT trap rather than at the end of the -# script: without one, a failed build leaves mrdocs.yml patched, and the next -# run backs up the patched file and loses the original. -restore_mrdocs_yml() { - if [ -f "$SCRIPT_DIR/mrdocs.yml.bak" ]; then - mv -f "$SCRIPT_DIR/mrdocs.yml.bak" "$SCRIPT_DIR/mrdocs.yml" - echo "Restored original mrdocs.yml" - fi -} - -if [ -n "${REPOSITORY}" ] && [ -n "${SHA}" ]; then - BASE_URL="https://github.com/${REPOSITORY}/blob/${SHA}" - echo "Setting base-url to $BASE_URL" - cp mrdocs.yml mrdocs.yml.bak - trap restore_mrdocs_yml EXIT - perl -i -pe 's{^\s*base-url:.*$}{base-url: '"$BASE_URL/"'}' mrdocs.yml -else - echo "REPOSITORY or SHA not set; skipping base-url modification" -fi - echo "Building documentation with Antora..." + echo "Installing npm dependencies..." npm ci echo "Building docs in custom dir..." PATH="$(pwd)/node_modules/.bin:${PATH}" export PATH - -# ref_headers.adoc links each header to its source with `link:{base-url}/...`. -# Point that at the exact commit when we know it; otherwise antora.yml's -# fallback applies. A command-line attribute outranks the one in antora.yml. -ANTORA_ATTRS=() -if [ -n "${BASE_URL:-}" ]; then - ANTORA_ATTRS+=(--attribute "base-url=$BASE_URL") -fi - -npx antora --clean --fetch "$PLAYBOOK" "${ANTORA_ATTRS[@]}" --stacktrace # --log-level all - -echo "Fixing links to non-mrdocs URIs..." -echo "BRANCH='${BRANCH:-}'" -echo "BASE_URL='${BASE_URL:-}'" - -for f in $(find html -name '*.html'); do - perl -i -pe "s{Boost.OpenMethod}{Boost.OpenMethod}g" "$f" -done +npx antora --clean --fetch "$PLAYBOOK" --stacktrace --log-level all echo "Done" diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index afaf61f9..32a92a73 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -60,7 +60,6 @@ transform-options: # Generator generate: adoc -base-url: https://www.github.com/boostorg/openmethod/blob/master/ # Style verbose: true From 3bedb21abc22fa80324467316c25cda4d49a70f6 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 12:04:15 -0400 Subject: [PATCH 58/85] doc: document `vptr` in VirtualTraits and VptrFn `vptr` is a customization point at two levels, and neither was in the exposition-only blueprints: - `virtual_traits::vptr(arg)` - optional; lets a traits specialization override the default v-table lookup. - `policies::vptr::fn::vptr(type_id)` - the type-id-keyed lookup the above calls. Both arrived with 1eb22d8 ("inter-operate with 'any'") as `type_vptr`, renamed by f086985 and 7ecd96c; neither commit updated the blueprints. Document them, including when to implement them and why they exist, and mention in the `any` specializations that the vptr policy must provide `vptr(type_id)`. Also fix the detection of `virtual_traits::vptr`: it probed callability with a `type_id` (= `const void*`), which compiles for `std::any` and `boost::any` only because their converting constructors accept a `const void*`. An `any`-like type without such a constructor was silently ignored and fell back to `dynamic_vptr`, dispatching on the wrapper instead of the contained value. Probe with the actual argument type instead. Drive-bys: four `@ref policies::vptr::fn::dynamic_vptr` did not resolve (rendered as plain text) - use `@ref policies::VptrFn::dynamic_vptr`; drop a stray "a the" and align two stale std_any comments that claimed the rtti policy supplies the type id. Co-Authored-By: Claude Opus 5 (1M context) --- include/boost/openmethod/core.hpp | 41 ++++++++++++++++--- .../boost/openmethod/interop/boost_any.hpp | 12 ++++++ include/boost/openmethod/interop/std_any.hpp | 22 +++++++--- .../boost/openmethod/policies/vptr_map.hpp | 6 +-- .../boost/openmethod/policies/vptr_vector.hpp | 4 +- include/boost/openmethod/preamble.hpp | 21 +++++++++- 6 files changed, 89 insertions(+), 17 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index bb496785..bd6fc90e 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -568,7 +568,7 @@ decltype(auto) acquire_vptr(const ArgType& arg) { return boost_openmethod_vptr(arg, static_cast(nullptr)); } else if constexpr (has_vptr< virtual_traits, - type_id>) { + const ArgType&>) { return virtual_traits::vptr(arg); } else { return Registry::template policy::dynamic_vptr(arg); @@ -788,7 +788,7 @@ class virtual_ptr { //! //! The pointer to the v-table is obtained by calling //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's + //! @ref policies::VptrFn::dynamic_vptr of the registry's //! `vptr` policy otherwise. //! //! @param other A reference to a polymorphic object @@ -823,7 +823,7 @@ class virtual_ptr { //! //! The pointer to the v-table is obtained by calling //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's + //! @ref policies::VptrFn::dynamic_vptr of the registry's //! `vptr` policy otherwise. //! //! @par Example @@ -892,7 +892,7 @@ class virtual_ptr { //! //! The pointer to the v-table is obtained by calling //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's + //! @ref policies::VptrFn::dynamic_vptr of the registry's //! `vptr` policy otherwise. //! //! @par Example @@ -930,7 +930,7 @@ class virtual_ptr { //! //! The pointer to the v-table is obtained by calling //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's + //! @ref policies::VptrFn::dynamic_vptr of the registry's //! `vptr` policy otherwise. //! //! @par Example @@ -2343,7 +2343,7 @@ BOOST_FORCEINLINE auto method::vptr( return boost_openmethod_vptr(obj, static_cast(nullptr)); } else if constexpr (detail::has_vptr< virtual_traits, - type_id>) { + decltype(obj)>) { return virtual_traits::vptr(obj); } else { return Registry::template policy::dynamic_vptr(obj); @@ -2721,6 +2721,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/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 6bab0463..2e932385 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -54,6 +54,10 @@ struct virtual_traits { //! `boost::any::type()` yields the same `std::type_info` object, 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. //! @@ -125,6 +129,10 @@ struct virtual_traits { //! `boost::any::type()` yields the same `std::type_info` object, 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. //! @@ -196,6 +204,10 @@ struct virtual_traits { //! `boost::any::type()` yields the same `std::type_info` object, 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. //! diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 73f05f71..125026c9 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -48,8 +48,12 @@ struct virtual_traits { //! 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. + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. + //! + //! 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. @@ -111,6 +115,10 @@ struct virtual_traits { //! Acquires the @ref type_id of the value stored in `arg`, using //! `std::any::type()`. //! + //! 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. //! @@ -169,8 +177,12 @@ struct virtual_traits { //! 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. + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. + //! + //! 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. @@ -182,7 +194,7 @@ struct virtual_traits { //! terminates the program with @ref abort. //! //! @param arg A reference to a const `any`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for the stored value. static auto vptr(const std::any& arg) -> const vptr_type& { return Registry::vptr::vptr(&arg.type()); } diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index 92588269..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,7 +96,7 @@ 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& { return vptr(Registry::rtti::dynamic_type(arg)); @@ -111,7 +111,7 @@ class vptr_map : public vptr { //! terminates the program with @ref abort. //! //! @param type A `type_id`. - //! @return A reference to a the v-table pointer for `type`. + //! @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); diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index 7496e414..304d2ab9 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -152,7 +152,7 @@ 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& { return vptr(Registry::rtti::dynamic_type(arg)); @@ -170,7 +170,7 @@ struct vptr_vector : vptr { //! terminates the program with @ref abort. //! //! @param type A `type_id`. - //! @return A reference to a the v-table pointer for `type`. + //! @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) { 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. From f77e3c881109dc7d65755c0d3a710a0761b099be Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 12:37:10 -0400 Subject: [PATCH 59/85] doc: make the header links follow the deployment The links to the header sources were written against a `base-url` attribute that no longer exists, so they rendered as a literal `href="{base-url}/include/boost/openmethod/core.hpp"`. Point them at the headers relative to the built page, which lands in doc/html/openmethod/. The links then work wherever the docs are deployed -- the local tree, a PR preview, or a boost.org version -- with no attribute to define and none to go stale. This is also what dynamic_bitset does: its antora.yml defines no base-url either. They have to be `link:`, not `xref:`: Antora resolves an `xref:` target as a resource id and rejects a relative path, while a `link:` target is passed through verbatim to the stock converter. While here, fix the label on initialize.hpp, which read ``. Co-Authored-By: Claude Opus 5 (1M context) --- doc/modules/ROOT/pages/ref_headers.adoc | 39 ++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 4bddf4cc..8a8b7416 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -1,6 +1,11 @@ [#ref_headers] = xref:ref_headers.adoc[Headers] +// The links to the headers are relative to the built page, which lands in +// doc/html/openmethod/, so that they follow the deployment: the local tree, a PR +// preview, or a boost.org version. They must be `link:`, not `xref:` -- Antora +// resolves an `xref:` target as a resource id, and rejects this one. + {empty} ## Headers for General Use @@ -25,14 +30,14 @@ parameters: ## High-level Headers [#core] -### link:{base-url}/include/boost/openmethod/core.hpp[] +### link:../../../include/boost/openmethod/core.hpp[] Defines the main constructs of the library: methods, overriders and virtual pointers, and mechanisms to implement them. Does not define any public macros apart from `BOOST_OPENMETHOD_DEFAULT_REGISTRY`, if it is not defined already. [#macros] -### link:{base-url}/include/boost/openmethod/macros.hpp[] +### link:../../../include/boost/openmethod/macros.hpp[] Defines the public macros of the library, such as `BOOST_OPENMETHOD`, `BOOST_OPENMETHOD_CLASSES`, etc. @@ -41,12 +46,12 @@ There is little point in including this header directly, as this has the same effect as including `boost/openmethod.hpp`, which is shorter. [#openmethod] -### link:{base-url}/include/boost/openmethod.hpp[] +### link:../../../include/boost/openmethod.hpp[] Includes `core.hpp` and `macros.hpp`. [#initialize] -### link:{base-url}/include/boost/openmethod/initialize.hpp[] +### link:../../../include/boost/openmethod/initialize.hpp[] Provides the cpp:initialize[] and cpp:finalize[] functions. This header is typically included in the translation unit containing `main`. Translation units @@ -54,19 +59,19 @@ that dynamically load or unload shared libraries may also need to call those functions. [#std_shared_ptr] -### link:{base-url}/include/boost/openmethod/interop/std_shared_ptr.hpp[] +### link:../../../include/boost/openmethod/interop/std_shared_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::shared_ptr` in place of a raw pointer or reference in virtual parameters. [#std_unique_ptr] -### link:{base-url}/include/boost/openmethod/interop/std_unique_ptr.hpp[] +### link:../../../include/boost/openmethod/interop/std_unique_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::unique_ptr` in place of a raw pointer or reference in virtual parameters. [#boost_intrusive_ptr] -### link:{base-url}/include/boost/openmethod/interop/boost_intrusive_ptr.hpp[] +### link:../../../include/boost/openmethod/interop/boost_intrusive_ptr.hpp[] 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. @@ -79,52 +84,52 @@ The following headers can be included before `core.hpp` to define custom registries and policies, and override the default registry by defining xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. -### link:{base-url}/include/boost/openmethod/preamble.hpp[] +### link:../../../include/boost/openmethod/preamble.hpp[] Defines `registry` and stock policy categories. Also defines all types and functions necessary for the definition of `registry`. -### link:{base-url}/include/boost/openmethod/policies/std_rtti.hpp[] +### link:../../../include/boost/openmethod/policies/std_rtti.hpp[] Provides an implementation of the `rtti` policy using standard RTTI. -### link:{base-url}/include/boost/openmethod/policies/fast_perfect_hash.hpp[] +### link:../../../include/boost/openmethod/policies/fast_perfect_hash.hpp[] Provides an implementation of the `hash` policy using a fast perfect hash function. -### link:{base-url}/include/boost/openmethod/policies/vptr_vector.hpp[] +### link:../../../include/boost/openmethod/policies/vptr_vector.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a `std::vector` indexed by type ids, possibly hashed. -### link:{base-url}/include/boost/openmethod/policies/default_error_handler.hpp[] +### link:../../../include/boost/openmethod/policies/default_error_handler.hpp[] Provides an implementation of the `error_handler` policy that calls a `std::function` when an error is encountered, and before the library aborts the program. -### link:{base-url}/include/boost/openmethod/policies/stderr_output.hpp[] +### link:../../../include/boost/openmethod/policies/stderr_output.hpp[] Provides an implementation of the `output` policy that writes diagnostics to the C standard error stream (not using iostreams). -### link:{base-url}/include/boost/openmethod/default_registry.hpp[] +### link:../../../include/boost/openmethod/default_registry.hpp[] Defines the default registry, which contains all the stock policies listed above. Includes all the headers listed in this section so far. -### link:{base-url}/include/boost/openmethod/policies/static_rtti.hpp[] +### link:../../../include/boost/openmethod/policies/static_rtti.hpp[] Provides a minimal implementation of the `rtti` policy that does not depend on standard RTTI. -### link:{base-url}/include/boost/openmethod/policies/throw_error_handler.hpp[] +### link:../../../include/boost/openmethod/policies/throw_error_handler.hpp[] Provides an implementation of the `error_handler` policy that throws errors as exceptions. -### link:{base-url}/include/boost/openmethod/policies/vptr_map.hpp[] +### link:../../../include/boost/openmethod/policies/vptr_map.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a map (by default a `std::map`) indexed by type ids. From a735c458854b52a85252f6b356c9987ceeb63c45 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 12:45:48 -0400 Subject: [PATCH 60/85] doc: make the `any` header links follow the deployment too The merge brought in the relative header links, but the three `any` interop headers were added on this branch and still pointed at the `base-url` attribute, which no longer exists. Convert them like the rest. Co-Authored-By: Claude Opus 5 (1M context) --- doc/modules/ROOT/pages/ref_headers.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index f62f8d80..2cbd7eed 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -77,7 +77,7 @@ 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:{base-url}/include/boost/openmethod/interop/virtual_any.hpp[] +### link:../../../include/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 - like `virtual_ptr` @@ -91,7 +91,7 @@ with an `any`-like interface, given `virtual_traits` specializations for its reference types. [#std_any] -### link:{base-url}/include/boost/openmethod/interop/std_any.hpp[] +### link:../../../include/boost/openmethod/interop/std_any.hpp[] Provides `virtual_traits` specializations that make it possible to use a `std::any` - by const reference, by mutable reference, or by rvalue reference - in virtual @@ -103,7 +103,7 @@ parameters. Dispatch is on the type of the contained value. Also provides use the v-table of the `any` root class instead of the contained value's. [#boost_any] -### link:{base-url}/include/boost/openmethod/interop/boost_any.hpp[] +### link:../../../include/boost/openmethod/interop/boost_any.hpp[] Provides `virtual_traits` specializations that make it possible to use a `boost::any` - by const reference, by mutable reference, or by rvalue reference - in virtual From 65904e39affbb403ba1be6d2a235d76c25de6903 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 13:02:24 -0400 Subject: [PATCH 61/85] doc: inline the final_virtual_ptr example instead of linking to it The explicit-`Registry` overload pointed at the default-registry overload's example with a hardcoded page name, `final_virtual_ptr-08.adoc`. MrDocs disambiguates overload pages with a content-derived hash, so adding the `any` overloads renamed that page to `final_virtual_ptr-08ea.adoc` and the link went dead. It went dead silently: mrdocs-addons rewrites `xref:reference:` into a plain `link:` on nested pages, to work around cppalliance/mrdocs#1245, and Antora does not validate a link macro. Any cross-reference to an *overload* page is therefore a link that rots without warning -- the two in macros.hpp are safe only because macro page names carry no hash. Pull in the snippet instead, with the same `include:` directive the default-registry overload already uses. There is no page name left to rot, and the example now comes from a file the build compiles and runs. Co-Authored-By: Claude Opus 5 (1M context) --- include/boost/openmethod/core.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index bd6fc90e..aada59b6 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -624,8 +624,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. From 1fc8ea35bd676efad65223a89a56f37574500fd4 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 13:11:10 -0400 Subject: [PATCH 62/85] doc: hide the deleted `final_virtual_ptr` overloads from the reference The `any` headers delete twelve `final_virtual_ptr` overloads to stop the primary from silently using `static_vptr`. They are a guard, not API, and MrDocs gave each one its own page: the overload list went from 3 entries to 15. Guard them with `#ifndef __MRDOCS__`, as the friend declarations in core.hpp already are. The symbol is defined only while generating the reference, so the overloads are unchanged for every real compiler -- confirmed by compile_fail_final_virtual_ptr_std_any.cpp, which still fails with "use of deleted function". This also silences cppalliance/mrdocs#1251: the malformed link on the `aliases::final_virtual_ptr` page only appeared once the overload set grew, and the table is empty again now, so the Antora build is back to zero errors. Co-Authored-By: Claude Opus 5 (1M context) --- include/boost/openmethod/interop/boost_any.hpp | 5 +++++ include/boost/openmethod/interop/std_any.hpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 2e932385..9a707a62 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -322,7 +322,11 @@ make_boost_any_virtual(T&&... args) -> virtual_any { // 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 @@ -332,6 +336,7 @@ 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::make_boost_any_virtual; diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 125026c9..b87a9104 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -285,7 +285,11 @@ make_std_any_virtual(T&&... args) -> virtual_any { // 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 @@ -295,6 +299,7 @@ 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::make_std_any_virtual; From 597f03bfb8394974bf8fd6195c9c374426b82a6c Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 14:31:02 -0400 Subject: [PATCH 63/85] add the openmethod_vptr concept (#21) A Boost.TypeErasure concept that stores the v-table pointer for the bound type in the any's own dispatch table, and surfaces it as a boost_openmethod_vptr overload, which dispatch prefers over the vptr policy: calls through such an any - in every flavor - resolve in constant time, without hashing the result of typeid_of. Binding a value to the any also registers its type, as a class derived from the owning flavor, so use_type_erasure_types is unnecessary for these anys. Based on a design by Steven Watanabe (https://github.com/boostorg/openmethod/issues/21), modernized for the registry-era API: the two-argument intrinsic hook makes the registry a parameter of the concept instead of the identity of the class, and the virtual_traits shipped in this header replace the specialized rtti policy of the original. Co-Authored-By: Claude Fable 5 --- .../ROOT/examples/type_erasure_concept.cpp | 60 +++++++++++ .../ROOT/pages/interop_type_erasure.adoc | 39 +++++++- doc/modules/ROOT/pages/ref_headers.adoc | 11 ++- .../ROOT/snippets/type_erasure_concept.cpp | 62 ++++++++++++ .../openmethod/interop/boost_type_erasure.hpp | 99 +++++++++++++++++++ test/test_dispatch_type_erasure.cpp | 68 +++++++++++++ 6 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 doc/modules/ROOT/examples/type_erasure_concept.cpp create mode 100644 doc/modules/ROOT/snippets/type_erasure_concept.cpp diff --git a/doc/modules/ROOT/examples/type_erasure_concept.cpp b/doc/modules/ROOT/examples/type_erasure_concept.cpp new file mode 100644 index 00000000..9bb261a8 --- /dev/null +++ b/doc/modules/ROOT/examples/type_erasure_concept.cpp @@ -0,0 +1,60 @@ +// 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 +#include + +#include +#include + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +// The concept must name the Concept it is part of, so the Concept is +// defined as a struct. +struct Dispatchable + : boost::mpl::vector< + te::copy_constructible<>, te::relaxed, + openmethod_vptr> {}; + +using erased = te::any; + +// No use_type_erasure_types: binding a value to the `any` registers its +// type. + +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 erased& value), std::string) { + return te::is_empty(value) ? "nothing" : "something else"; +} + +#include + +int main() { + initialize(); + + const erased spot(Dog{"Spot"}); + const erased answer(42); + + std::cout << name(spot) << "\n"; // Spot the dog + std::cout << name(answer) << "\n"; // something else +} +// end::content[] diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc index 957406ff..f7e7b6bf 100644 --- a/doc/modules/ROOT/pages/interop_type_erasure.adoc +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -123,9 +123,46 @@ For the same reason as for `std::any`, cpp:final_virtual_ptr[] is _deleted_ for `type_erasure::any`: it would silently produce the v-table of the root class rather than the one for the bound value. +#### The `openmethod_vptr` concept + +`virtual_any` removes the hash lookup by making the _object_ wider. +Boost.TypeErasure offers a way to remove it for plain, unwidened `any`\s: +since the `any` already carries a dispatch table of Concept operations, the +v-table pointer can be one of them. cpp:openmethod_vptr[] is a +Boost.TypeErasure concept that does exactly that. Include it in the Concept, +and every flavor of the `any` gains an operation that returns the registry's +static v-table pointer (`registry::static_vptr`) for the bound type - +instantiated once per bound type, retrieved in constant time. The concept surfaces the operation as a +`boost_openmethod_vptr` overload, which dispatch prefers over the `vptr` +policy, so calls through such an `any` never hash the result of `typeid_of`. + +Binding a value to such an `any` also _registers_ its type, as a class derived +from the owning flavor - the registrar is instantiated along with the +operation. `use_type_erasure_types` becomes unnecessary for these `any`\s, +although the two registration styles may coexist. + +The concept must name the Concept it is part of, so the Concept is defined as +a struct - the class name is already in scope in its own base-clause: + +[source,c++] +---- +include::example$type_erasure_concept.cpp[tag=content] +---- + +The price is coupling: the Concept must be OpenMethod-aware, and the registry +is part of the `any`'s type - whereas the `typeid_of`-based dispatch above +works with any pre-existing Concept containing `typeid_<>`. To use an `any` +with several registries, list the concept several times, once per registry: +`openmethod_vptr`. + +This concept is based on a design contributed by Steven Watanabe in +link:https://github.com/boostorg/openmethod/issues/21[issue #21]. + #### Empty `any`s An empty relaxed `any` reports `typeid(void)`, which is not a registered class, so dispatching on it is a cpp:missing_class[] error. A catch-all overrider does not help: dispatch never reaches it. Check with -`boost::type_erasure::is_empty` before calling. +`boost::type_erasure::is_empty` before calling. With the `openmethod_vptr` +concept, the failure mode differs: calling a concept operation on an empty +relaxed `any` throws `boost::type_erasure::bad_function_call`. diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 68fac536..1de6f5b9 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -120,10 +120,13 @@ the type of the bound value, obtained via `boost::type_erasure::typeid_of`; the Concept must contain `boost::type_erasure::typeid_<>`, which `relaxed` implies. Also provides `use_type_erasure_types`, which registers the types that may be bound; `virtual_any>` works as well, and looks the v-table pointer -up only once, at construction. In addition, the header deletes the -`final_virtual_ptr` overloads for `boost::type_erasure::any`, which would -otherwise silently use the v-table of the `any` root class instead of the bound -value's. +up only once, at construction. `openmethod_vptr` - based on a design by Steven +Watanabe - is a Boost.TypeErasure concept that stores the v-table pointer for +the bound type in the `any`'s own dispatch table, removing the hash lookup +altogether, and registers bound types automatically. In addition, the header +deletes the `final_virtual_ptr` overloads for `boost::type_erasure::any`, which +would otherwise silently use the v-table of the `any` root class instead of the +bound value's. *The headers below are for advanced use*. diff --git a/doc/modules/ROOT/snippets/type_erasure_concept.cpp b/doc/modules/ROOT/snippets/type_erasure_concept.cpp new file mode 100644 index 00000000..969e695f --- /dev/null +++ b/doc/modules/ROOT/snippets/type_erasure_concept.cpp @@ -0,0 +1,62 @@ +// 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 + +#include "capture.hpp" + +namespace te = boost::type_erasure; +using namespace boost::openmethod; + +// tag::concept[] +struct Dog { + std::string name; +}; + +// The concept must name the Concept it is part of, so the Concept is +// defined as a struct. +struct Dispatchable : boost::mpl::vector< + te::copy_constructible<>, te::relaxed, + openmethod_vptr> {}; + +using erased = te::any; + +// No use_type_erasure_types: binding a value to the `any` registers its +// type. + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} +// end::concept[] + +BOOST_AUTO_TEST_CASE(type_erasure_concept_example) { + initialize(); + + { + capture_cout cout; + + // tag::dispatch[] + const erased spot(Dog{"Spot"}); + + std::cout << name(spot) << "\n"; // Spot the dog + // end::dispatch[] + + BOOST_TEST(cout.str() == "Spot the dog\n"); + } +} diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index b5340069..1ccfc610 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -8,7 +8,11 @@ #include #include +#include +#include #include +#include +#include #include #include @@ -40,6 +44,13 @@ // // The rvalue-reference flavor (`any`), and placeholders // other than `_self`, are not supported. +// +// In addition, `openmethod_vptr` - based on a design by Steven Watanabe - +// is a Boost.TypeErasure concept that stores the v-table pointer for the +// bound type in the any's own dispatch table, making every flavor of the +// any intrinsically polymorphic: calls resolve in constant time, without +// hashing the result of `typeid_of`, and binding a value to the any +// registers its type. namespace boost::openmethod { @@ -476,6 +487,93 @@ struct use_type_erasure_types detail::type_erasure_root, typename detail::extract_registry::others> {}; +namespace detail { + +// Registers Class as deriving from the owning flavor for Concept - the +// same shape use_type_erasure_types produces - when odr-used from +// openmethod_vptr::apply. +template +use_class_aux>> + use_type_erasure_class; + +} // namespace detail + +//! A Boost.TypeErasure concept that makes an `any` intrinsically +//! polymorphic. +//! +//! Including `openmethod_vptr` in a Concept adds an operation, +//! to the dispatch table of every flavor of `any`, that returns +//! the @ref registry::static_vptr for the bound type; and it surfaces the +//! operation as a @ref boost_openmethod_vptr overload, which dispatch +//! prefers over the registry's `vptr` policy. Calls thus resolve in +//! constant time, without hashing the result of +//! `boost::type_erasure::typeid_of`. +//! +//! In addition, binding a value to such an `any` registers its type as a +//! class derived from the owning flavor - the same shape +//! @ref use_type_erasure_types produces, with which it can coexist. No +//! explicit registration is needed for the types bound to `any`\s that +//! carry this concept. +//! +//! `Concept` must be the very Concept the `any` is instantiated with. +//! Since the concept appears inside that Concept, the Concept must name +//! itself: define it as a struct deriving from the concept list. +//! +//! Unlike the `vptr` policy, which reports a @ref missing_class error, +//! calling a method on an empty relaxed `any` throws +//! `boost::type_erasure::bad_function_call`. +//! +//! Based on a design by +//! [Steven Watanabe](https://github.com/boostorg/openmethod/issues/21). +//! +//! @tparam Concept The Concept containing this concept. +//! @tparam Registry A @ref registry. +//! @tparam T A placeholder; leave it to its default, `_self`. +//! +//! @par Example +//! include:type_erasure_concept.cpp#concept +//! +//! @see [Interoperation with Boost.TypeErasure](xref:ROOT:interop_type_erasure.adoc) +template< + class Concept, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + typename T = boost::type_erasure::_self> +struct openmethod_vptr { + //! Returns the v-table pointer for the bound type. + //! + //! Also registers `T`, and the owning flavor for `Concept` as its + //! base, by odr-using their registrars. + //! + //! @return The @ref registry::static_vptr for `T`. + static auto apply(const T&) -> vptr_type { + (void)&detail::use_type_erasure_class< + Registry, boost::type_erasure::any, Concept>; + (void)&detail::use_type_erasure_class; + return Registry::template static_vptr; + } +}; + +} // namespace boost::openmethod + +namespace boost::type_erasure { + +// Surface the openmethod_vptr operation as the boost_openmethod_vptr +// intrinsic hook, injected into the interface of every flavor of any +// whose Concept contains the concept. +template +struct concept_interface< + boost::openmethod::openmethod_vptr, Base, T> : Base { + friend auto boost_openmethod_vptr( + const typename derived::type& arg, + Registry*) -> boost::openmethod::vptr_type { + return call( + boost::openmethod::openmethod_vptr(), arg); + } +}; + +} // namespace boost::type_erasure + +namespace boost::openmethod { + // The primary final_virtual_ptr would silently use the static v-table // pointer of the any class itself - the root -, not the bound value's. // Delete the combination. Both call forms need covering: the (C, T)-only @@ -497,6 +595,7 @@ template void final_virtual_ptr(boost::type_erasure::any&&) = delete; namespace aliases { +using boost::openmethod::openmethod_vptr; using boost::openmethod::use_type_erasure_types; } // namespace aliases diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp index 9927671a..352d3ded 100644 --- a/test/test_dispatch_type_erasure.cpp +++ b/test/test_dispatch_type_erasure.cpp @@ -289,3 +289,71 @@ BOOST_AUTO_TEST_CASE(type_erasure_indirect_vptr) { BOOST_TEST(name_method::fn(rex.get()) == "Rex the dog"); } } // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// the openmethod_vptr concept (based on a design by Steven Watanabe): the +// any carries the v-table pointer for its bound type in its own dispatch +// table, and binding a value registers its type + +struct Dog { + std::string name; +}; + +// The concept must name the Concept it is part of: define the Concept as +// a struct. +struct Dispatchable : boost::mpl::vector< + te::copy_constructible<>, te::relaxed, + openmethod_vptr> {}; + +using dispatchable = te::any; +using dispatchable_ref = te::any; + +// the intrinsic hook is found for every flavor, so dispatch prefers it +// over the vptr policy's hash lookup +static_assert(detail::has_vptr_fn); +static_assert(detail::has_vptr_fn); + +// explicit registration is not needed, but may coexist (class dedup) +BOOST_OPENMETHOD_REGISTER(use_type_erasure_types); + +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 dispatchable& value), std::string) { + return te::is_empty(value) ? "nothing" : "something"; +} + +BOOST_OPENMETHOD(poke, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog), std::string) { + dog.name += "!"; + return dog.name; +} + +BOOST_AUTO_TEST_CASE(type_erasure_openmethod_vptr_concept) { + initialize(trace()); + + const dispatchable spot(Dog{"Spot"}); + + // the hook returns the static vptr for the bound type + BOOST_TEST( + boost_openmethod_vptr(spot, static_cast(nullptr)) == + default_registry::static_vptr); + BOOST_TEST(name(spot) == "Spot the dog"); + + // std::string appears nowhere in this section; binding it registered + // it, and the catch-all applies + const dispatchable felix(std::string{"Felix"}); + BOOST_TEST(name(felix) == "something"); + + // the reference-wrapper flavor takes the fast path too + Dog snoopy{"Snoopy"}; + BOOST_TEST(poke(dispatchable_ref(snoopy)) == "Snoopy!"); + BOOST_TEST(snoopy.name == "Snoopy!"); +} +} // namespace BOOST_OPENMETHOD_GENSYM From 6c6cb440eb067c7abb68112dbf93f3d5c313dc93 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 14:31:23 -0400 Subject: [PATCH 64/85] doc: hide the deleted final_virtual_ptr overloads from the reference Same as for the any headers: they are a guard, not API, and six deleted overloads crowd the final_virtual_ptr overload list - and push it past the size that triggers the MrDocs "Introduced Symbols" URL bug on the aliases/final_virtual_ptr page. Co-Authored-By: Claude Fable 5 --- include/boost/openmethod/interop/boost_type_erasure.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index 1ccfc610..6898b113 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -580,7 +580,11 @@ namespace boost::openmethod { // templates catch calls that deduce the default registry, and the // Registry-first templates catch explicit-registry calls; both are more // specialized than the primary's forwarding-reference parameter. +// +// 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::type_erasure::any&) = delete; template @@ -593,6 +597,7 @@ template void final_virtual_ptr(boost::type_erasure::any&) = delete; template void final_virtual_ptr(boost::type_erasure::any&&) = delete; +#endif namespace aliases { using boost::openmethod::openmethod_vptr; From 8737e867c0000447e8dd82ed28f195dbb018804d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 14:45:20 -0400 Subject: [PATCH 65/85] doc: tighten the boost_type_erasure entry on the Headers page Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 1de6f5b9..04b2bf99 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -112,21 +112,15 @@ use the v-table of the `any` root class instead of the contained value's. [#boost_type_erasure] ### link:{base-url}/include/boost/openmethod/interop/boost_type_erasure.hpp[] -Provides `virtual_traits` specializations that make it possible to use a -`boost::type_erasure::any` in virtual parameters: the owning flavor by const, -mutable or rvalue reference, and the reference-wrapper flavors -(`any`, `any`) by value. Dispatch is on -the type of the bound value, obtained via `boost::type_erasure::typeid_of`; the -Concept must contain `boost::type_erasure::typeid_<>`, which `relaxed` implies. -Also provides `use_type_erasure_types`, which registers the types that may be -bound; `virtual_any>` works as well, and looks the v-table pointer -up only once, at construction. `openmethod_vptr` - based on a design by Steven -Watanabe - is a Boost.TypeErasure concept that stores the v-table pointer for -the bound type in the `any`'s own dispatch table, removing the hash lookup -altogether, and registers bound types automatically. In addition, the header -deletes the `final_virtual_ptr` overloads for `boost::type_erasure::any`, which -would otherwise silently use the v-table of the `any` root class instead of the -bound value's. +Provides `virtual_traits` specializations for using a `boost::type_erasure::any` +in virtual parameters: the owning flavor by reference, the reference-wrapper +flavors by value. Dispatch is on the type of the bound value; the Concept must +contain `typeid_<>`, which `relaxed` implies. Also provides +`use_type_erasure_types`, which registers the types that may be bound, and +`openmethod_vptr`, a Boost.TypeErasure concept that stores the v-table pointer +in the `any`'s own dispatch table and registers bound types automatically. +`final_virtual_ptr` is deleted for `type_erasure::any`. Based on a design by +Steven Watanabe. *The headers below are for advanced use*. From 437c6662600b51a2d9e9952f119420aa3e02afac Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 14:50:44 -0400 Subject: [PATCH 66/85] reject virtual_ptr over classes with a boost_openmethod_vptr overload virtual_ptr and the intrinsic hook fill the same goal - fast access to the v-table pointer - so combining them buys nothing; and, with an indirect registry, it was outright broken: acquire_vptr preferred the hook, which returns the vptr by value, and box_vptr stored the address of the temporary - a dangling pointer read back on every dispatch (caught by ASan as stack-use-after-return). acquire_vptr is only called from virtual_ptr and virtual_any construction and assignment - dispatch uses method::vptr, which keeps the hook fast path. Make acquire_vptr static_assert that no hook applies, and drop its now-unreachable hook branch; the remaining branches (virtual_traits, vptr policy) return references into stable storage, so box_vptr is safe for everything acquire_vptr can return. Closes #87 Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/virtual_ptr_alt.adoc | 4 ++ include/boost/openmethod/core.hpp | 57 ++++++++++++------- include/boost/openmethod/inplace_vptr.hpp | 4 ++ test/CMakeLists.txt | 3 + .../compile_fail_virtual_ptr_inplace_vptr.cpp | 22 +++++++ test/test_core.cpp | 14 ++--- 6 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 test/compile_fail_virtual_ptr_inplace_vptr.cpp diff --git a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc index 2c936031..eb5886f8 100644 --- a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc +++ b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc @@ -102,3 +102,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/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index aada59b6..67fcf4da 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -562,13 +562,20 @@ 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 (has_vptr_fn) { - return boost_openmethod_vptr(arg, static_cast(nullptr)); - } else if constexpr (has_vptr< - virtual_traits, - const ArgType&>) { + if constexpr (has_vptr< + virtual_traits, + const ArgType&>) { return virtual_traits::vptr(arg); } else { return Registry::template policy::dynamic_vptr(arg); @@ -785,10 +792,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 //! @@ -820,10 +829,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 @@ -889,10 +900,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 @@ -927,10 +940,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 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/test/CMakeLists.txt b/test/CMakeLists.txt index 6678cd47..a47bce34 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -169,6 +169,9 @@ openmethod_compile_fail_test( # "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") if (TARGET Boost::dll) add_subdirectory(dynamic_loading) 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 From e3a2537b67ff797c970c8e4e3f8885f3167c6d4e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 14:54:29 -0400 Subject: [PATCH 67/85] doc: openmethod_vptr anys are not wrapped in virtual_any Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/interop_type_erasure.adoc | 4 ++++ doc/modules/ROOT/pages/ref_headers.adoc | 3 +-- include/boost/openmethod/interop/boost_type_erasure.hpp | 5 +++++ test/test_dispatch_type_erasure.cpp | 5 ++--- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc index f7e7b6bf..9f46e22e 100644 --- a/doc/modules/ROOT/pages/interop_type_erasure.adoc +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -155,6 +155,10 @@ works with any pre-existing Concept containing `typeid_<>`. To use an `any` with several registries, list the concept several times, once per registry: `openmethod_vptr`. +An `any` that carries the concept cannot be wrapped in a `virtual_any` - and +does not need to be: both fill the same goal, constant-time access to the +v-table pointer. Wrapping one is rejected at compile time. + This concept is based on a design contributed by Steven Watanabe in link:https://github.com/boostorg/openmethod/issues/21[issue #21]. diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 4fbb8171..19137c79 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -124,8 +124,7 @@ contain `typeid_<>`, which `relaxed` implies. Also provides `use_type_erasure_types`, which registers the types that may be bound, and `openmethod_vptr`, a Boost.TypeErasure concept that stores the v-table pointer in the `any`'s own dispatch table and registers bound types automatically. -`final_virtual_ptr` is deleted for `type_erasure::any`. Based on a design by -Steven Watanabe. +`final_virtual_ptr` is deleted for `type_erasure::any`. *The headers below are for advanced use*. diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index 6898b113..feb97535 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -523,6 +523,11 @@ use_class_aux>> //! calling a method on an empty relaxed `any` throws //! `boost::type_erasure::bad_function_call`. //! +//! An `any` that carries this concept cannot be wrapped in a +//! @ref virtual_any - and does not need to be: both fill the same goal, +//! constant-time access to the v-table pointer. Wrapping one is rejected +//! at compile time. +//! //! Based on a design by //! [Steven Watanabe](https://github.com/boostorg/openmethod/issues/21). //! diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp index 352d3ded..29929ee0 100644 --- a/test/test_dispatch_type_erasure.cpp +++ b/test/test_dispatch_type_erasure.cpp @@ -293,9 +293,8 @@ BOOST_AUTO_TEST_CASE(type_erasure_indirect_vptr) { namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- -// the openmethod_vptr concept (based on a design by Steven Watanabe): the -// any carries the v-table pointer for its bound type in its own dispatch -// table, and binding a value registers its type +// the openmethod_vptr concept: the any carries the v-table pointer for its +// bound type in its own dispatch table, and binding a value registers its type struct Dog { std::string name; From b1d78b8f9a5cf116deb6bb3bcd8248ef85cfcfe2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 15:00:11 -0400 Subject: [PATCH 68/85] doc: make the type_erasure header link follow the deployment Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 19137c79..88e4950f 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -115,7 +115,7 @@ parameters. Dispatch is on the type of the contained value. Also provides use the v-table of the `any` root class instead of the contained value's. [#boost_type_erasure] -### link:{base-url}/include/boost/openmethod/interop/boost_type_erasure.hpp[] +### link:../../../include/boost/openmethod/interop/boost_type_erasure.hpp[] Provides `virtual_traits` specializations for using a `boost::type_erasure::any` in virtual parameters: the owning flavor by reference, the reference-wrapper From dbdd9511a2bb5d6fc144e62fbac2966e550993f6 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 15:08:40 -0400 Subject: [PATCH 69/85] doc: fix the plural any's rendering with literal backslashes and backticks Constrained monospace breaks when the closing backtick is followed by a letter, and the \s escape renders literally. Use unconstrained monospace for the plurals on the page, and reword the doc comment. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/interop_type_erasure.adoc | 10 +++++----- .../boost/openmethod/interop/boost_type_erasure.hpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc index 9f46e22e..6b814041 100644 --- a/doc/modules/ROOT/pages/interop_type_erasure.adoc +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -20,8 +20,8 @@ the only requirement placed on the Concept is that it contain The types the `any` may bind have to be registered. cpp:use_type_erasure_types[] does that, registering the owning flavor - `any` - as a class, and each of the types as a class derived from it. -Each Concept gets a root of its own, so `any`s with different Concepts - and -the plain `any`s - can coexist in the same registry. A type that is not +Each Concept gets a root of its own, so ``any``s with different Concepts - and +the plain ``any``s - can coexist in the same registry. A type that is not registered cannot be dispatched on; a call with such a value bound to the `any` is a cpp:missing_class[] error - see xref:error_handling.adoc[Error Handling]. @@ -126,7 +126,7 @@ class rather than the one for the bound value. #### The `openmethod_vptr` concept `virtual_any` removes the hash lookup by making the _object_ wider. -Boost.TypeErasure offers a way to remove it for plain, unwidened `any`\s: +Boost.TypeErasure offers a way to remove it for plain, unwidened ``any``s: since the `any` already carries a dispatch table of Concept operations, the v-table pointer can be one of them. cpp:openmethod_vptr[] is a Boost.TypeErasure concept that does exactly that. Include it in the Concept, @@ -138,7 +138,7 @@ policy, so calls through such an `any` never hash the result of `typeid_of`. Binding a value to such an `any` also _registers_ its type, as a class derived from the owning flavor - the registrar is instantiated along with the -operation. `use_type_erasure_types` becomes unnecessary for these `any`\s, +operation. `use_type_erasure_types` becomes unnecessary for these ``any``s, although the two registration styles may coexist. The concept must name the Concept it is part of, so the Concept is defined as @@ -162,7 +162,7 @@ v-table pointer. Wrapping one is rejected at compile time. This concept is based on a design contributed by Steven Watanabe in link:https://github.com/boostorg/openmethod/issues/21[issue #21]. -#### Empty `any`s +#### Empty ``any``s An empty relaxed `any` reports `typeid(void)`, which is not a registered class, so dispatching on it is a cpp:missing_class[] error. A catch-all diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index feb97535..bfe8abbc 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -512,8 +512,8 @@ use_class_aux>> //! In addition, binding a value to such an `any` registers its type as a //! class derived from the owning flavor - the same shape //! @ref use_type_erasure_types produces, with which it can coexist. No -//! explicit registration is needed for the types bound to `any`\s that -//! carry this concept. +//! explicit registration is needed for the types bound to an `any` that +//! carries this concept. //! //! `Concept` must be the very Concept the `any` is instantiated with. //! Since the concept appears inside that Concept, the Concept must name From 516606ce7be796e51bfed740d661253c97e5f962 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 15:13:09 -0400 Subject: [PATCH 70/85] doc: trim the any-interop entries on the Headers page Co-Authored-By: Claude Sonnet 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 32 +++++-------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 2cbd7eed..efdb60ed 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -79,40 +79,20 @@ Provides a `virtual_traits` specialization that makes it possible to use a [#virtual_any] ### link:../../../include/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 - like `virtual_ptr` -combines a pointer to an object with a pointer to its v-table. The v-table -pointer is acquired when the `virtual_any` is created, so methods dispatch on -the contained type without looking it up on every call. Also provides -`make_any_virtual`, which creates a `virtual_any` containing a value of a -statically known type, setting the v-table pointer without any lookup. This -header is included by `std_any.hpp` and `boost_any.hpp`; it can serve any type -with an `any`-like interface, given `virtual_traits` specializations for its -reference types. +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`. [#std_any] ### link:../../../include/boost/openmethod/interop/std_any.hpp[] -Provides `virtual_traits` specializations that make it possible to use a `std::any` - -by const reference, by mutable reference, or by rvalue reference - in virtual -parameters. Dispatch is on the type of the contained value. Also provides -`use_std_any_types`, which registers the types that may be contained; -`virtual_std_any`, an alias for `virtual_any`, and -`make_std_any_virtual`. In addition, the header deletes the -`final_virtual_ptr` overloads for `std::any`, which would otherwise silently -use the v-table of the `any` root class instead of the contained value's. +Provides `virtual_traits` specializations that make it possible to use a +`std::any` in virtual parameters. [#boost_any] ### link:../../../include/boost/openmethod/interop/boost_any.hpp[] -Provides `virtual_traits` specializations that make it possible to use a `boost::any` - -by const reference, by mutable reference, or by rvalue reference - in virtual -parameters. Dispatch is on the type of the contained value. Also provides -`use_boost_any_types`, which registers the types that may be contained; -`virtual_boost_any`, an alias for `virtual_any`, and -`make_boost_any_virtual`. In addition, the header deletes the -`final_virtual_ptr` overloads for `boost::any`, which would otherwise silently -use the v-table of the `any` root class instead of the contained value's. +Provides `virtual_traits` specializations that make it possible to use a +`boost::any` in virtual parameters. *The headers below are for advanced use*. From c42b1bd47b5bfa481051f023538742817d77ba86 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 15:15:19 -0400 Subject: [PATCH 71/85] doc: trim the type_erasure entry on the Headers page to match Co-Authored-By: Claude Sonnet 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 2da0d85b..58cfd43b 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -97,14 +97,8 @@ Provides `virtual_traits` specializations that make it possible to use a [#boost_type_erasure] ### link:../../../include/boost/openmethod/interop/boost_type_erasure.hpp[] -Provides `virtual_traits` specializations for using a `boost::type_erasure::any` -in virtual parameters: the owning flavor by reference, the reference-wrapper -flavors by value. Dispatch is on the type of the bound value; the Concept must -contain `typeid_<>`, which `relaxed` implies. Also provides -`use_type_erasure_types`, which registers the types that may be bound, and -`openmethod_vptr`, a Boost.TypeErasure concept that stores the v-table pointer -in the `any`'s own dispatch table and registers bound types automatically. -`final_virtual_ptr` is deleted for `type_erasure::any`. +Provides specializations for using a `boost::type_erasure::any` in virtual +parameters. *The headers below are for advanced use*. From 9da7fd9b550565dddbf8b25a4fc7385897a4734b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 15:31:46 -0400 Subject: [PATCH 72/85] doc: give the example Dog a ctor so make_any_virtual forwards its arguments make_any_virtual paren-constructs the Class from its arguments, and an aggregate cannot be paren-initialized in C++17. With a constructor, the tighter make_any_virtual("Snoopy") spelling works, and the test suite now exercises that forwarding path. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/examples/type_erasure.cpp | 1 + doc/modules/ROOT/pages/interop_type_erasure.adoc | 2 +- test/test_dispatch_type_erasure.cpp | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/modules/ROOT/examples/type_erasure.cpp b/doc/modules/ROOT/examples/type_erasure.cpp index 21920eb4..e2819a9c 100644 --- a/doc/modules/ROOT/examples/type_erasure.cpp +++ b/doc/modules/ROOT/examples/type_erasure.cpp @@ -25,6 +25,7 @@ using Concept = boost::mpl::vector, te::relaxed>; using erased = te::any; struct Dog { + Dog(std::string name) : name(std::move(name)) {} std::string name; }; diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc index 6b814041..20522dce 100644 --- a/doc/modules/ROOT/pages/interop_type_erasure.adoc +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -113,7 +113,7 @@ virtual_any spot = erased(Dog{"Spot"}); // from a value, or with make_any_virtual: no lookup at all virtual_any rex = Dog{"Rex"}; -auto snoopy = make_any_virtual(Dog{"Snoopy"}); +auto snoopy = make_any_virtual("Snoopy"); ``` The Concept must contain `relaxed` - `virtual_any`'s default constructor and diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp index 29929ee0..f4563f41 100644 --- a/test/test_dispatch_type_erasure.cpp +++ b/test/test_dispatch_type_erasure.cpp @@ -32,6 +32,8 @@ static_assert(detail::has_vptr< #define MAKE_CLASSES() \ struct Dog { \ + Dog(std::string name) : name(std::move(name)) { \ + } \ std::string name; \ }; \ \ @@ -251,7 +253,7 @@ BOOST_AUTO_TEST_CASE(type_erasure_virtual_any) { BOOST_TEST(rex.vptr() == default_registry::static_vptr); BOOST_TEST(name(rex) == "Rex the dog"); - auto snoopy = make_any_virtual(Dog{"Snoopy"}); + auto snoopy = make_any_virtual("Snoopy"); BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); BOOST_TEST(name(snoopy) == "Snoopy the dog"); } From b7706b366de6659c46110ace896c5e7e2a98b7e2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 15:34:49 -0400 Subject: [PATCH 73/85] add a make_any_virtual overload that deduces the type of the value make_any_virtual(Dog{"Snoopy"}) now works: only the any type - and optionally the registry - needs to be spelled. The overload does not collide with the existing one: with a single explicit template argument, the existing overload's Any is neither deducible nor defaulted, and an is_registry constraint keeps the new one out of Class-and-Any calls. Co-Authored-By: Claude Fable 5 --- .../ROOT/pages/interop_type_erasure.adoc | 1 + doc/modules/ROOT/snippets/virtual_any.cpp | 12 +++++++++ .../boost/openmethod/interop/virtual_any.hpp | 27 +++++++++++++++++++ test/test_dispatch_type_erasure.cpp | 5 ++++ 4 files changed, 45 insertions(+) diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc index 20522dce..0becd74d 100644 --- a/doc/modules/ROOT/pages/interop_type_erasure.adoc +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -114,6 +114,7 @@ virtual_any spot = erased(Dog{"Spot"}); // from a value, or with make_any_virtual: no lookup at all virtual_any rex = Dog{"Rex"}; auto snoopy = make_any_virtual("Snoopy"); +auto duke = make_any_virtual(Dog{"Duke"}); ``` The Concept must contain `relaxed` - `virtual_any`'s default constructor and diff --git a/doc/modules/ROOT/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp index ad8e3713..c10a2fb6 100644 --- a/doc/modules/ROOT/snippets/virtual_any.cpp +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -156,6 +156,18 @@ BOOST_AUTO_TEST_CASE(std_any_examples) { BOOST_TEST(cout.str() == "Felix the cat\n"); } + { + capture_cout cout; + + // tag::make_any_virtual_value[] + auto felix = make_any_virtual(std::string("Felix the cat")); + + std::cout << name(felix) << "\n"; // Felix the cat + // end::make_any_virtual_value[] + + BOOST_TEST(cout.str() == "Felix the cat\n"); + } + { capture_cout cout; diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 14f593e4..49511825 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -528,6 +528,33 @@ inline auto make_any_virtual(T&&... args) -> virtual_any { return virtual_any(Class(std::forward(args)...)); } +//! Create a `virtual_any` from an existing value. +//! +//! This overload deduces the type of the value: only the `any` type - and +//! optionally the registry - needs to be spelled. Stores `value` in a +//! @ref virtual_any, and sets the v-table pointer to the +//! @ref registry::static_vptr for `value`\'s type - no hash table lookup +//! is involved. If `value` is itself an `Any`, this is equivalent to the +//! `virtual_any` constructor taking an `any`, which looks the v-table +//! pointer up once. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +//! @tparam Class The type of the value (deduced). +//! @param value The value to store in the `any`. +//! @return A `virtual_any` containing `value`. +//! +//! @par Example +//! include:virtual_any.cpp#make_any_virtual_value +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template< + class Any, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, class Class, + typename = std::enable_if_t>> +inline auto make_any_virtual(Class&& value) -> virtual_any { + return virtual_any(std::forward(value)); +} + namespace aliases { using boost::openmethod::make_any_virtual; using boost::openmethod::virtual_any; diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp index f4563f41..14bee64d 100644 --- a/test/test_dispatch_type_erasure.cpp +++ b/test/test_dispatch_type_erasure.cpp @@ -256,6 +256,11 @@ BOOST_AUTO_TEST_CASE(type_erasure_virtual_any) { auto snoopy = make_any_virtual("Snoopy"); BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); BOOST_TEST(name(snoopy) == "Snoopy the dog"); + + // from an existing value, deducing its type + auto duke = make_any_virtual(Dog{"Duke"}); + BOOST_TEST(duke.vptr() == default_registry::static_vptr); + BOOST_TEST(name(duke) == "Duke the dog"); } } // namespace BOOST_OPENMETHOD_GENSYM From 4b0c30aef77f750cd1264f42706e94f9dd0aeaac Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 15:56:43 -0400 Subject: [PATCH 74/85] Revert the value-deducing make_any_virtual overload and the example ctor Constructing the virtual_any directly from a value is just as direct and shorter; the factory forms added nothing. Show only the direct construction on the TypeErasure page. This reverts commits b7706b3 and 9da7fd9. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/examples/type_erasure.cpp | 1 - .../ROOT/pages/interop_type_erasure.adoc | 8 +++--- doc/modules/ROOT/snippets/virtual_any.cpp | 12 --------- .../boost/openmethod/interop/virtual_any.hpp | 27 ------------------- test/test_dispatch_type_erasure.cpp | 9 +------ 5 files changed, 4 insertions(+), 53 deletions(-) diff --git a/doc/modules/ROOT/examples/type_erasure.cpp b/doc/modules/ROOT/examples/type_erasure.cpp index e2819a9c..21920eb4 100644 --- a/doc/modules/ROOT/examples/type_erasure.cpp +++ b/doc/modules/ROOT/examples/type_erasure.cpp @@ -25,7 +25,6 @@ using Concept = boost::mpl::vector, te::relaxed>; using erased = te::any; struct Dog { - Dog(std::string name) : name(std::move(name)) {} std::string name; }; diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc index 0becd74d..4e374e47 100644 --- a/doc/modules/ROOT/pages/interop_type_erasure.adoc +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -102,8 +102,8 @@ Every call above looks the v-table up in a hash table, keyed on the type the `any` binds. cpp:virtual_any[] works for a `type_erasure::any` exactly as it does for a `std::any`: `virtual_any>` bundles the `any` with the v-table pointer for the value inside it, acquiring it once, on construction - -or not at all, when it is built from a value or by cpp:make_any_virtual[], -since the type is then known at compile time: +or not at all, when it is built from a value, since the type is then known at +compile time: ```c++ BOOST_OPENMETHOD(name, (const virtual_any&), std::string); @@ -111,10 +111,8 @@ BOOST_OPENMETHOD(name, (const virtual_any&), std::string); // from an `any`: one lookup, at construction virtual_any spot = erased(Dog{"Spot"}); -// from a value, or with make_any_virtual: no lookup at all +// from a value: no lookup at all virtual_any rex = Dog{"Rex"}; -auto snoopy = make_any_virtual("Snoopy"); -auto duke = make_any_virtual(Dog{"Duke"}); ``` The Concept must contain `relaxed` - `virtual_any`'s default constructor and diff --git a/doc/modules/ROOT/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp index c10a2fb6..ad8e3713 100644 --- a/doc/modules/ROOT/snippets/virtual_any.cpp +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -156,18 +156,6 @@ BOOST_AUTO_TEST_CASE(std_any_examples) { BOOST_TEST(cout.str() == "Felix the cat\n"); } - { - capture_cout cout; - - // tag::make_any_virtual_value[] - auto felix = make_any_virtual(std::string("Felix the cat")); - - std::cout << name(felix) << "\n"; // Felix the cat - // end::make_any_virtual_value[] - - BOOST_TEST(cout.str() == "Felix the cat\n"); - } - { capture_cout cout; diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 49511825..14f593e4 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -528,33 +528,6 @@ inline auto make_any_virtual(T&&... args) -> virtual_any { return virtual_any(Class(std::forward(args)...)); } -//! Create a `virtual_any` from an existing value. -//! -//! This overload deduces the type of the value: only the `any` type - and -//! optionally the registry - needs to be spelled. Stores `value` in a -//! @ref virtual_any, and sets the v-table pointer to the -//! @ref registry::static_vptr for `value`\'s type - no hash table lookup -//! is involved. If `value` is itself an `Any`, this is equivalent to the -//! `virtual_any` constructor taking an `any`, which looks the v-table -//! pointer up once. -//! -//! @tparam Any An `any` type. -//! @tparam Registry A @ref registry. -//! @tparam Class The type of the value (deduced). -//! @param value The value to store in the `any`. -//! @return A `virtual_any` containing `value`. -//! -//! @par Example -//! include:virtual_any.cpp#make_any_virtual_value -//! -//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) -template< - class Any, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, class Class, - typename = std::enable_if_t>> -inline auto make_any_virtual(Class&& value) -> virtual_any { - return virtual_any(std::forward(value)); -} - namespace aliases { using boost::openmethod::make_any_virtual; using boost::openmethod::virtual_any; diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp index 14bee64d..29929ee0 100644 --- a/test/test_dispatch_type_erasure.cpp +++ b/test/test_dispatch_type_erasure.cpp @@ -32,8 +32,6 @@ static_assert(detail::has_vptr< #define MAKE_CLASSES() \ struct Dog { \ - Dog(std::string name) : name(std::move(name)) { \ - } \ std::string name; \ }; \ \ @@ -253,14 +251,9 @@ BOOST_AUTO_TEST_CASE(type_erasure_virtual_any) { BOOST_TEST(rex.vptr() == default_registry::static_vptr); BOOST_TEST(name(rex) == "Rex the dog"); - auto snoopy = make_any_virtual("Snoopy"); + auto snoopy = make_any_virtual(Dog{"Snoopy"}); BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); BOOST_TEST(name(snoopy) == "Snoopy the dog"); - - // from an existing value, deducing its type - auto duke = make_any_virtual(Dog{"Duke"}); - BOOST_TEST(duke.vptr() == default_registry::static_vptr); - BOOST_TEST(name(duke) == "Duke the dog"); } } // namespace BOOST_OPENMETHOD_GENSYM From 69b5d43bc6d76ba32f14ced602e15ede91a79ffd Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 17:05:37 -0400 Subject: [PATCH 75/85] remove the make_*_virtual factories make_any_virtual(args...) constructs the Class and moves it into the any - exactly what constructing the virtual_any from a value does, with more characters and one more name to learn; and constructing in place, the one thing a factory could add, is already covered by the emplace member. Remove make_any_virtual, make_std_any_virtual and make_boost_any_virtual. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/interop_any.adoc | 8 ++--- doc/modules/ROOT/snippets/virtual_any.cpp | 26 +--------------- .../boost/openmethod/interop/boost_any.hpp | 29 ------------------ include/boost/openmethod/interop/std_any.hpp | 29 ------------------ .../boost/openmethod/interop/virtual_any.hpp | 30 +------------------ test/compile_fail_virtual_any_by_value.cpp | 2 +- test/test_virtual_any_boost.cpp | 4 +-- test/test_virtual_any_std.cpp | 4 +-- 8 files changed, 10 insertions(+), 122 deletions(-) diff --git a/doc/modules/ROOT/pages/interop_any.adoc b/doc/modules/ROOT/pages/interop_any.adoc index d91e7c86..8af19b55 100644 --- a/doc/modules/ROOT/pages/interop_any.adoc +++ b/doc/modules/ROOT/pages/interop_any.adoc @@ -106,8 +106,7 @@ pointer, 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 cpp:make_std_any_virtual[], or by `emplace`, since the type -is then known at compile time. +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 @@ -127,9 +126,8 @@ the v-table of the `any` root class rather than the one for the contained value. #### `boost::any` `boost::any` is supported as well, by -``, with cpp:use_boost_any_types[], -cpp:virtual_boost_any[] and cpp:make_boost_any_virtual[] - the exact -counterparts of the constructs above. The two root classes are distinct, so +``, 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. diff --git a/doc/modules/ROOT/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp index ad8e3713..1d98b14a 100644 --- a/doc/modules/ROOT/snippets/virtual_any.cpp +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -143,30 +143,6 @@ BOOST_AUTO_TEST_CASE(std_any_examples) { BOOST_TEST(cout.str() == "Felix the cat\n"); } - - { - capture_cout cout; - - // tag::make_any_virtual[] - auto felix = make_any_virtual("Felix the cat"); - - std::cout << name(felix) << "\n"; // Felix the cat - // end::make_any_virtual[] - - BOOST_TEST(cout.str() == "Felix the cat\n"); - } - - { - capture_cout cout; - - // tag::make_std_any_virtual[] - auto felix = make_std_any_virtual("Felix the cat"); - - std::cout << name(felix) << "\n"; // Felix the cat - // end::make_std_any_virtual[] - - BOOST_TEST(cout.str() == "Felix the cat\n"); - } } BOOST_AUTO_TEST_CASE(boost_any_examples) { @@ -178,7 +154,7 @@ BOOST_AUTO_TEST_CASE(boost_any_examples) { capture_cout cout; // tag::boost_dispatch[] - auto felix = make_boost_any_virtual("Felix the cat"); + virtual_boost_any felix = std::string("Felix the cat"); std::cout << name(felix) << "\n"; // Felix the cat // end::boost_dispatch[] diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 9a707a62..e8d58473 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -287,34 +287,6 @@ struct use_boost_any_types //! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) using virtual_boost_any = virtual_any; -//! Create a new object and return a `virtual_boost_any` containing it. -//! -//! Create a `Class` from `args`, store it in a `boost::any`, and return a -//! @ref virtual_any with its v-table pointer set to the -//! @ref registry::static_vptr for `Class` - no hash table lookup is -//! involved. -//! -//! @tparam Class The type of the value to create. -//! @tparam Registry A @ref registry. -//! @tparam T Types of the arguments to pass to the constructor of -//! `Class`. -//! @param args Arguments to pass to the constructor of `Class`. -//! @return A `virtual_any` containing a newly created -//! `Class`. -//! -//! @par Example -//! include:virtual_any.cpp#boost_dispatch -//! -//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) -template< - class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, - typename... T> -inline auto -make_boost_any_virtual(T&&... args) -> virtual_any { - return make_any_virtual( - std::forward(args)...); -} - // 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 @@ -339,7 +311,6 @@ void final_virtual_ptr(boost::any&&) = delete; #endif namespace aliases { -using boost::openmethod::make_boost_any_virtual; using boost::openmethod::use_boost_any_types; using boost::openmethod::virtual_boost_any; } // namespace aliases diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index b87a9104..54772417 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -250,34 +250,6 @@ struct use_std_any_types //! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) using virtual_std_any = virtual_any; -//! Create a new object and return a `virtual_std_any` containing it. -//! -//! Create a `Class` from `args`, store it in a `std::any`, and return a -//! @ref virtual_any with its v-table pointer set to the -//! @ref registry::static_vptr for `Class` - no hash table lookup is -//! involved. -//! -//! @tparam Class The type of the value to create. -//! @tparam Registry A @ref registry. -//! @tparam T Types of the arguments to pass to the constructor of -//! `Class`. -//! @param args Arguments to pass to the constructor of `Class`. -//! @return A `virtual_any` containing a newly created -//! `Class`. -//! -//! @par Example -//! include:virtual_any.cpp#make_std_any_virtual -//! -//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) -template< - class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, - typename... T> -inline auto -make_std_any_virtual(T&&... args) -> virtual_any { - return make_any_virtual( - std::forward(args)...); -} - // 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 @@ -302,7 +274,6 @@ void final_virtual_ptr(std::any&&) = delete; #endif namespace aliases { -using boost::openmethod::make_std_any_virtual; using boost::openmethod::use_std_any_types; using boost::openmethod::virtual_std_any; } // namespace aliases diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 6a9e1171..3bfe99f0 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -38,8 +38,7 @@ struct is_virtual_any_aux> : std::true_type {}; //! 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, @ref make_any_virtual, and @ref emplace use @ref -//! registry::static_vptr). +//! 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 @@ -491,34 +490,7 @@ struct select_overrider_virtual_type_aux< } // namespace detail -//! Create a new object and return a `virtual_any` containing it. -//! -//! Create a `Class` from `args`, store it in a @ref virtual_any, and set -//! 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 create. -//! @tparam Any An `any` type. -//! @tparam Registry A @ref registry. -//! @tparam T Types of the arguments to pass to the constructor of -//! `Class`. -//! @param args Arguments to pass to the constructor of `Class`. -//! @return A `virtual_any` containing a newly created -//! `Class`. -//! -//! @par Example -//! include:virtual_any.cpp#make_any_virtual -//! -//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) -template< - class Class, class Any, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, - typename... T> -inline auto make_any_virtual(T&&... args) -> virtual_any { - return virtual_any(Class(std::forward(args)...)); -} - namespace aliases { -using boost::openmethod::make_any_virtual; using boost::openmethod::virtual_any; } // namespace aliases diff --git a/test/compile_fail_virtual_any_by_value.cpp b/test/compile_fail_virtual_any_by_value.cpp index 22afdf0b..fa30d78e 100644 --- a/test/compile_fail_virtual_any_by_value.cpp +++ b/test/compile_fail_virtual_any_by_value.cpp @@ -22,6 +22,6 @@ BOOST_OPENMETHOD_REGISTER(use_std_any_types); BOOST_OPENMETHOD(name, (virtual_std_any), std::string); int main() { - auto dog = make_std_any_virtual(Dog{"Snoopy"}); + virtual_std_any dog = Dog{"Snoopy"}; return name(dog).size(); } diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp index a77b2e37..a9e87588 100644 --- a/test/test_virtual_any_boost.cpp +++ b/test/test_virtual_any_boost.cpp @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(virtual_any_by_const_ref) { BOOST_TEST(rex.vptr() == default_registry::static_vptr); BOOST_TEST(name(rex) == "Rex the dog"); - auto felix = make_boost_any_virtual("Felix the cat"); + virtual_boost_any felix = std::string("Felix the cat"); BOOST_TEST(felix.vptr() == default_registry::static_vptr); BOOST_TEST(name(felix) == "Felix the cat"); @@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE(virtual_any_by_xvalue_ref) { BOOST_TEST(boost::any_cast(spot.get()).name == ""); BOOST_TEST( - steal(make_boost_any_virtual("Felix the cat")) == + steal(virtual_boost_any(std::string("Felix the cat"))) == "Felix the cat"); } } // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp index 9cefe118..bb41a1b7 100644 --- a/test/test_virtual_any_std.cpp +++ b/test/test_virtual_any_std.cpp @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(virtual_any_by_const_ref) { BOOST_TEST(rex.vptr() == default_registry::static_vptr); BOOST_TEST(name(rex) == "Rex the dog"); - auto felix = make_std_any_virtual("Felix the cat"); + virtual_std_any felix = std::string("Felix the cat"); BOOST_TEST(felix.vptr() == default_registry::static_vptr); BOOST_TEST(name(felix) == "Felix the cat"); @@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE(virtual_any_by_xvalue_ref) { BOOST_TEST(std::any_cast(spot.get()).name == ""); BOOST_TEST( - steal(make_std_any_virtual("Felix the cat")) == + steal(virtual_std_any(std::string("Felix the cat"))) == "Felix the cat"); } } // namespace BOOST_OPENMETHOD_GENSYM From df3c0b7e0f14f866cc0c7791fa0c1ab76a99aee0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 17:10:58 -0400 Subject: [PATCH 76/85] add virtual_any_ref, a non-owning counterpart of virtual_any virtual_any_ref borrows an existing any instead of holding a copy, and carries the v-table pointer for the contained value: a cheap, two-word handle with pointer semantics, passed to methods by value - like the reference-wrapper flavors of Boost.TypeErasure's any. The v-table pointer is acquired once, when the handle is created, or taken at no cost from a virtual_any. Any may be const-qualified; a mutable handle converts to a const one. Since a plain value does not convert to a virtual_any_ref, overriders that take the contained value are registered with the core API; the catch-all, which takes the handle itself, can use the macro. Co-Authored-By: Claude Fable 5 --- doc/modules/ROOT/pages/interop_any.adoc | 35 +++ doc/modules/ROOT/pages/ref_headers.adoc | 2 + doc/modules/ROOT/snippets/virtual_any.cpp | 44 ++++ .../boost/openmethod/interop/virtual_any.hpp | 242 ++++++++++++++++++ test/CMakeLists.txt | 3 + test/compile_fail_virtual_any_ref_by_ref.cpp | 27 ++ test/test_virtual_any_ref.cpp | 177 +++++++++++++ 7 files changed, 530 insertions(+) create mode 100644 test/compile_fail_virtual_any_ref_by_ref.cpp create mode 100644 test/test_virtual_any_ref.cpp diff --git a/doc/modules/ROOT/pages/interop_any.adoc b/doc/modules/ROOT/pages/interop_any.adoc index 8af19b55..d11797f8 100644 --- a/doc/modules/ROOT/pages/interop_any.adoc +++ b/doc/modules/ROOT/pages/interop_any.adoc @@ -123,6 +123,41 @@ 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 _borrows_ 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 borrow - 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 diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index efdb60ed..7f195a1a 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -81,6 +81,8 @@ Provides a `virtual_traits` specialization that makes it possible to use a 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 borrows an +existing `any`. [#std_any] ### link:../../../include/boost/openmethod/interop/std_any.hpp[] diff --git a/doc/modules/ROOT/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp index 1d98b14a..b832239b 100644 --- a/doc/modules/ROOT/snippets/virtual_any.cpp +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -78,6 +78,28 @@ BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { } // 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; @@ -145,6 +167,28 @@ BOOST_AUTO_TEST_CASE(std_any_examples) { } } +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 borrows 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; diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 3bfe99f0..109e1b00 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -16,6 +16,9 @@ namespace boost::openmethod { template class virtual_any; +template +class virtual_any_ref; + namespace detail { template @@ -24,6 +27,9 @@ struct is_virtual_any_aux : std::false_type {}; template struct is_virtual_any_aux> : std::true_type {}; +template +struct is_virtual_any_aux> : std::true_type {}; + } // namespace detail //! A wide `any`, combining an `any` and a pointer to a v-table. @@ -71,6 +77,9 @@ class virtual_any { template friend struct virtual_traits; + template + friend class virtual_any_ref; + public: //! Construct an empty `virtual_any`. //! @@ -490,8 +499,241 @@ struct select_overrider_virtual_type_aux< } // 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 *borrows* 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 borrow a temporary `any`. + virtual_any_ref(std::remove_const_t&&) = delete; + + //! Construct from a `virtual_any`. + //! + //! Borrows the `any` held by `other`, and copies its v-table pointer + //! - no lookup is involved. A `virtual_any_ref` can + //! borrow from 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 borrow 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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a47bce34..0edda63c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -165,6 +165,9 @@ 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") # "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( 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/test_virtual_any_ref.cpp b/test/test_virtual_any_ref.cpp new file mode 100644 index 00000000..ac72afbd --- /dev/null +++ b/test/test_virtual_any_ref.cpp @@ -0,0 +1,177 @@ +// 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 { + +// ----------------------------------------------------------------------------- +// 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 borrows 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); + + // borrowing from 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 { + +// ----------------------------------------------------------------------------- +// 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 From c9807ab82337269ec34cdcaf9d93c1b2f0451f42 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 17:13:52 -0400 Subject: [PATCH 77/85] doc: stop advertising virtual_any for type_erasure anys Where the Concept can be edited, the openmethod_vptr concept strictly dominates virtual_any: constant time for every any of the type, in every flavor, with no wrapper and no per-object cost. Drop the virtual_any section from the TypeErasure page and the corresponding tests; the generic machinery still works for Concepts that cannot be modified. Co-Authored-By: Claude Fable 5 --- .../ROOT/pages/interop_type_erasure.adoc | 34 ++-------------- test/test_dispatch_type_erasure.cpp | 39 ------------------- 2 files changed, 4 insertions(+), 69 deletions(-) diff --git a/doc/modules/ROOT/pages/interop_type_erasure.adoc b/doc/modules/ROOT/pages/interop_type_erasure.adoc index 4e374e47..b722fd63 100644 --- a/doc/modules/ROOT/pages/interop_type_erasure.adoc +++ b/doc/modules/ROOT/pages/interop_type_erasure.adoc @@ -96,38 +96,16 @@ C++ RTTI dynamic type of the referent. The rvalue-reference flavor (`any`), and placeholders other than `_self`, are not supported. -#### `virtual_any` - -Every call above looks the v-table up in a hash table, keyed on the type the -`any` binds. cpp:virtual_any[] works for a `type_erasure::any` exactly as it -does for a `std::any`: `virtual_any>` bundles the `any` with the -v-table pointer for the value inside it, acquiring it once, on construction - -or not at all, when it is built from a value, since the type is then known at -compile time: - -```c++ -BOOST_OPENMETHOD(name, (const virtual_any&), std::string); - -// from an `any`: one lookup, at construction -virtual_any spot = erased(Dog{"Spot"}); - -// from a value: no lookup at all -virtual_any rex = Dog{"Rex"}; -``` - -The Concept must contain `relaxed` - `virtual_any`'s default constructor and -assignment rely on it - and `copy_constructible<>`, for copies. - For the same reason as for `std::any`, cpp:final_virtual_ptr[] is _deleted_ for `type_erasure::any`: it would silently produce the v-table of the root class rather than the one for the bound value. #### The `openmethod_vptr` concept -`virtual_any` removes the hash lookup by making the _object_ wider. -Boost.TypeErasure offers a way to remove it for plain, unwidened ``any``s: -since the `any` already carries a dispatch table of Concept operations, the -v-table pointer can be one of them. cpp:openmethod_vptr[] is a +Every call above looks the v-table up in a hash table, keyed on the type the +`any` binds. Boost.TypeErasure offers a way to remove that cost: since the +`any` already carries a dispatch table of Concept operations, the v-table +pointer can be one of them. cpp:openmethod_vptr[] is a Boost.TypeErasure concept that does exactly that. Include it in the Concept, and every flavor of the `any` gains an operation that returns the registry's static v-table pointer (`registry::static_vptr`) for the bound type - @@ -154,10 +132,6 @@ works with any pre-existing Concept containing `typeid_<>`. To use an `any` with several registries, list the concept several times, once per registry: `openmethod_vptr`. -An `any` that carries the concept cannot be wrapped in a `virtual_any` - and -does not need to be: both fill the same goal, constant-time access to the -v-table pointer. Wrapping one is rejected at compile time. - This concept is based on a design contributed by Steven Watanabe in link:https://github.com/boostorg/openmethod/issues/21[issue #21]. diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp index 29929ee0..1dcfd25b 100644 --- a/test/test_dispatch_type_erasure.cpp +++ b/test/test_dispatch_type_erasure.cpp @@ -224,41 +224,6 @@ BOOST_AUTO_TEST_CASE(type_erasure_cref_wrapper_by_value) { namespace BOOST_OPENMETHOD_GENSYM { -// ----------------------------------------------------------------------------- -// virtual_any over a type_erasure any: the v-table pointer is looked up -// once, at construction - or set statically when the contained type is -// known - and dispatch does not hash typeid_of on every call - -MAKE_CLASSES(); - -BOOST_OPENMETHOD(name, (const virtual_any&), std::string); - -BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { - return dog.name + " the dog"; -} - -BOOST_AUTO_TEST_CASE(type_erasure_virtual_any) { - initialize(trace()); - - // from an `any`: runtime lookup via typeid_of - erased spot_any(Dog{"Spot"}); - virtual_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_any rex = Dog{"Rex"}; - BOOST_TEST(rex.vptr() == default_registry::static_vptr); - BOOST_TEST(name(rex) == "Rex the dog"); - - auto snoopy = make_any_virtual(Dog{"Snoopy"}); - BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - BOOST_TEST(name(snoopy) == "Snoopy the dog"); -} -} // namespace BOOST_OPENMETHOD_GENSYM - -namespace BOOST_OPENMETHOD_GENSYM { - // ----------------------------------------------------------------------------- // indirect vptrs @@ -283,10 +248,6 @@ BOOST_AUTO_TEST_CASE(type_erasure_indirect_vptr) { const erased spot(Dog{"Spot"}); BOOST_TEST(name_method::fn(spot) == "Spot the dog"); - - virtual_any rex = Dog{"Rex"}; - BOOST_TEST(rex.vptr() == indirect_registry::static_vptr); - BOOST_TEST(name_method::fn(rex.get()) == "Rex the dog"); } } // namespace BOOST_OPENMETHOD_GENSYM From cb44af63e6038a42fa3e045bd9ae048979b15fe9 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 9 Aug 2026 20:04:40 -0400 Subject: [PATCH 78/85] test: exercise virtual_any_ref with boost::any Co-Authored-By: Claude Fable 5 --- test/test_virtual_any_ref.cpp | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/test_virtual_any_ref.cpp b/test/test_virtual_any_ref.cpp index ac72afbd..70967ee2 100644 --- a/test/test_virtual_any_ref.cpp +++ b/test/test_virtual_any_ref.cpp @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include @@ -145,6 +147,65 @@ BOOST_AUTO_TEST_CASE(virtual_any_ref_mutable) { 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 From 8845d1c3284f05640652b8c520d89e6349cc9f22 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 10 Aug 2026 01:44:20 -0400 Subject: [PATCH 79/85] doc: point the header links at GitHub A relative path cannot reach a header from any deployed site: the PR previews publish libs/openmethod/doc alone, and boost.org serves the headers from doc/libs//boost, not from libs/openmethod/include. Link to the sources on GitHub instead, through a `headers-url` attribute in antora.yml, as Boost.Test does. Co-Authored-By: Claude Opus 5 --- doc/antora.yml | 6 ++++ doc/modules/ROOT/pages/ref_headers.adoc | 44 +++++++++++++------------ 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/doc/antora.yml b/doc/antora.yml index dfcfac86..71112caf 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -15,6 +15,12 @@ asciidoc: attributes: source-language: asciidoc@ table-caption: false + # Base of the links to the header sources in ref_headers.adoc. It must be + # an absolute url: neither the PR previews nor boost.org serve the library + # sources next to the docs. The previews publish libs/openmethod/doc only, + # and boost.org serves the headers from doc/libs//boost, not from + # libs/openmethod/include. + headers-url: https://github.com/boostorg/openmethod/blob/develop/include nav: - modules/ROOT/nav.adoc ext: diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 8a8b7416..ba911432 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -1,10 +1,12 @@ [#ref_headers] = xref:ref_headers.adoc[Headers] -// The links to the headers are relative to the built page, which lands in -// doc/html/openmethod/, so that they follow the deployment: the local tree, a PR -// preview, or a boost.org version. They must be `link:`, not `xref:` -- Antora -// resolves an `xref:` target as a resource id, and rejects this one. +// The links to the headers go to GitHub, via `headers-url` in antora.yml. A +// relative path would be nicer -- it would follow the deployment -- but it +// cannot work: the PR previews publish libs/openmethod/doc alone, and +// boost.org serves the headers from doc/libs//boost, not from +// libs/openmethod/include. They must also be `link:`, not `xref:` -- Antora +// resolves an `xref:` target as a resource id, and rejects an absolute url. {empty} @@ -30,14 +32,14 @@ parameters: ## High-level Headers [#core] -### link:../../../include/boost/openmethod/core.hpp[] +### link:{headers-url}/boost/openmethod/core.hpp[] Defines the main constructs of the library: methods, overriders and virtual pointers, and mechanisms to implement them. Does not define any public macros apart from `BOOST_OPENMETHOD_DEFAULT_REGISTRY`, if it is not defined already. [#macros] -### link:../../../include/boost/openmethod/macros.hpp[] +### link:{headers-url}/boost/openmethod/macros.hpp[] Defines the public macros of the library, such as `BOOST_OPENMETHOD`, `BOOST_OPENMETHOD_CLASSES`, etc. @@ -46,12 +48,12 @@ There is little point in including this header directly, as this has the same effect as including `boost/openmethod.hpp`, which is shorter. [#openmethod] -### link:../../../include/boost/openmethod.hpp[] +### link:{headers-url}/boost/openmethod.hpp[] Includes `core.hpp` and `macros.hpp`. [#initialize] -### link:../../../include/boost/openmethod/initialize.hpp[] +### link:{headers-url}/boost/openmethod/initialize.hpp[] Provides the cpp:initialize[] and cpp:finalize[] functions. This header is typically included in the translation unit containing `main`. Translation units @@ -59,19 +61,19 @@ that dynamically load or unload shared libraries may also need to call those functions. [#std_shared_ptr] -### link:../../../include/boost/openmethod/interop/std_shared_ptr.hpp[] +### link:{headers-url}/boost/openmethod/interop/std_shared_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::shared_ptr` in place of a raw pointer or reference in virtual parameters. [#std_unique_ptr] -### link:../../../include/boost/openmethod/interop/std_unique_ptr.hpp[] +### link:{headers-url}/boost/openmethod/interop/std_unique_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::unique_ptr` in place of a raw pointer or reference in virtual parameters. [#boost_intrusive_ptr] -### link:../../../include/boost/openmethod/interop/boost_intrusive_ptr.hpp[] +### link:{headers-url}/boost/openmethod/interop/boost_intrusive_ptr.hpp[] 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. @@ -84,52 +86,52 @@ The following headers can be included before `core.hpp` to define custom registries and policies, and override the default registry by defining xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. -### link:../../../include/boost/openmethod/preamble.hpp[] +### link:{headers-url}/boost/openmethod/preamble.hpp[] Defines `registry` and stock policy categories. Also defines all types and functions necessary for the definition of `registry`. -### link:../../../include/boost/openmethod/policies/std_rtti.hpp[] +### link:{headers-url}/boost/openmethod/policies/std_rtti.hpp[] Provides an implementation of the `rtti` policy using standard RTTI. -### link:../../../include/boost/openmethod/policies/fast_perfect_hash.hpp[] +### link:{headers-url}/boost/openmethod/policies/fast_perfect_hash.hpp[] Provides an implementation of the `hash` policy using a fast perfect hash function. -### link:../../../include/boost/openmethod/policies/vptr_vector.hpp[] +### link:{headers-url}/boost/openmethod/policies/vptr_vector.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a `std::vector` indexed by type ids, possibly hashed. -### link:../../../include/boost/openmethod/policies/default_error_handler.hpp[] +### link:{headers-url}/boost/openmethod/policies/default_error_handler.hpp[] Provides an implementation of the `error_handler` policy that calls a `std::function` when an error is encountered, and before the library aborts the program. -### link:../../../include/boost/openmethod/policies/stderr_output.hpp[] +### link:{headers-url}/boost/openmethod/policies/stderr_output.hpp[] Provides an implementation of the `output` policy that writes diagnostics to the C standard error stream (not using iostreams). -### link:../../../include/boost/openmethod/default_registry.hpp[] +### link:{headers-url}/boost/openmethod/default_registry.hpp[] Defines the default registry, which contains all the stock policies listed above. Includes all the headers listed in this section so far. -### link:../../../include/boost/openmethod/policies/static_rtti.hpp[] +### link:{headers-url}/boost/openmethod/policies/static_rtti.hpp[] Provides a minimal implementation of the `rtti` policy that does not depend on standard RTTI. -### link:../../../include/boost/openmethod/policies/throw_error_handler.hpp[] +### link:{headers-url}/boost/openmethod/policies/throw_error_handler.hpp[] Provides an implementation of the `error_handler` policy that throws errors as exceptions. -### link:../../../include/boost/openmethod/policies/vptr_map.hpp[] +### link:{headers-url}/boost/openmethod/policies/vptr_map.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a map (by default a `std::map`) indexed by type ids. From 9221d1501e01a58fa64b00a20c6926708998cb70 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 10 Aug 2026 01:44:33 -0400 Subject: [PATCH 80/85] doc: point the `any` header links at GitHub too Co-Authored-By: Claude Opus 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index b01c59d6..07309b7a 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -79,7 +79,7 @@ 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:../../../include/boost/openmethod/interop/virtual_any.hpp[] +### 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`. @@ -87,13 +87,13 @@ Also provides `virtual_any_ref`, a non-owning counterpart that borrows an existing `any`. [#std_any] -### link:../../../include/boost/openmethod/interop/std_any.hpp[] +### 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:../../../include/boost/openmethod/interop/boost_any.hpp[] +### 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. From 32e064c33d44de9eab209ea031d16794acb30fbb Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 10 Aug 2026 01:44:44 -0400 Subject: [PATCH 81/85] doc: point the type_erasure header link at GitHub too Co-Authored-By: Claude Opus 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 1c7d9c7e..65edc610 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -99,7 +99,7 @@ Provides `virtual_traits` specializations that make it possible to use a `boost::any` in virtual parameters. [#boost_type_erasure] -### link:../../../include/boost/openmethod/interop/boost_type_erasure.hpp[] +### link:{headers-url}/boost/openmethod/interop/boost_type_erasure.hpp[] Provides specializations for using a `boost::type_erasure::any` in virtual parameters. From a131c7f173eef7b4c4e5197d608e4a0714755341 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 10 Aug 2026 16:32:40 -0400 Subject: [PATCH 82/85] fix MSVC: constrain the type_erasure boost_openmethod_vptr friend MSVC's /std:c++17 does not imply /permissive-, so it injects hidden friends into the enclosing namespace, where ordinary lookup finds them. The unconstrained `const derived::type&` parameter then made the hook a candidate for an `any` over an unrelated Concept: MSVC tried to convert one flavor to the other, instantiating TypeErasure's binding converting constructor, which fails outside the immediate context -- an error, not a substitution failure. boost/type_erasure/any.hpp(2054): error C2661: 'binding::binding': no overloaded function takes 2 arguments Deduce the parameter and require an exact match, as virtual_any already does. Co-Authored-By: Claude Opus 5 --- .../openmethod/interop/boost_type_erasure.hpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index bfe8abbc..6345e226 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -567,9 +567,19 @@ namespace boost::type_erasure { template struct concept_interface< boost::openmethod::openmethod_vptr, Base, T> : Base { - friend auto boost_openmethod_vptr( - const typename derived::type& arg, - Registry*) -> boost::openmethod::vptr_type { + // The parameter is a deduced `Self`, constrained to the exact any + // flavor, because MSVC's `/std:c++17` does not imply `/permissive-`: + // it injects hidden friends into the enclosing namespace, where + // ordinary lookup finds them. A `const derived::type&` + // parameter would then make this a candidate for an any over an + // unrelated Concept, which MSVC tries to convert to this one - and + // the conversion fails outside the immediate context, so it is an + // error, not a substitution failure. + template + friend auto boost_openmethod_vptr(const Self& arg, Registry*) + -> std::enable_if_t< + std::is_same_v::type>, + boost::openmethod::vptr_type> { return call( boost::openmethod::openmethod_vptr(), arg); } From 47c80a855b437678f0027ab43aab92c635d95bd4 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 10 Aug 2026 16:36:43 -0400 Subject: [PATCH 83/85] test: make the type_erasure by-value guard fire on every compiler The test declared the method but never called it. GCC, clang and msvc-14.3 instantiate `method<...>` at the declaration, so the guard fired; MSVC v18 defers, and the test compiled clean - a compile-fail test that no longer fails. Call the method, as the virtual_any by-value test does. Co-Authored-By: Claude Opus 5 --- test/compile_fail_type_erasure_by_value.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/compile_fail_type_erasure_by_value.cpp b/test/compile_fail_type_erasure_by_value.cpp index 9efe2b26..c1c498c9 100644 --- a/test/compile_fail_type_erasure_by_value.cpp +++ b/test/compile_fail_type_erasure_by_value.cpp @@ -30,5 +30,8 @@ BOOST_OPENMETHOD_REGISTER(use_type_erasure_types); BOOST_OPENMETHOD(name, (virtual_), std::string); int main() { - return 0; + // Call the method: declaring it is not enough to instantiate it on + // every compiler, and the guard lives in the method's body. + erased dog = Dog{"Snoopy"}; + return name(dog).size(); } From 34a2a3fbc69dac7da57526c3b4566a9c2ecd37d1 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 10 Aug 2026 18:48:08 -0400 Subject: [PATCH 84/85] doc: drop $meta when copying doc nodes in the include extension MrDocs develop a51d1621 validates the nodes assigned back to symbol.doc.document and rejects `$meta`, which the deep copy carried over from the proxy it read: extension transform 'include': include.lua:233: field 'document': unknown sub-field '$meta' for kind 'paragraph' `$meta` is MrDocs' own metadata, not content, so add it to UNWRITABLE next to `level`. Verified against 0.8.0+a51d1621bd13: the generated adoc is byte-identical to what 0.8.0+14a36c8132df produced. Co-Authored-By: Claude Opus 5 --- doc/mrdocs-addons/extensions/include.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/mrdocs-addons/extensions/include.lua b/doc/mrdocs-addons/extensions/include.lua index dea625e2..0a001c97 100644 --- a/doc/mrdocs-addons/extensions/include.lua +++ b/doc/mrdocs-addons/extensions/include.lua @@ -37,8 +37,10 @@ -- Fields the generic setter cannot write. `level` is a heading's depth; MrDocs -- does not parse markdown `##` headings in doc comments, so heading blocks only -- ever come from `@par` at level 1 -- which is the default -- and dropping it --- round-trips. -local UNWRITABLE = { level = true } +-- round-trips. `$meta` is metadata MrDocs attaches to a node, not content: it +-- reads back from the proxy, but since a51d1621 the setter rejects it as an +-- unknown sub-field of the node's kind, so the deep copy must leave it out. +local UNWRITABLE = { level = true, ["$meta"] = true } local function dirname(path) return path:match("^(.*)/[^/]*$") or "." From f3440dbc6699608ede814cf4f81c87651f798a25 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 11 Aug 2026 20:10:29 -0400 Subject: [PATCH 85/85] ci: split the macOS 10.15 Drone stages by C++ standard The Catalina runner is the slowest in the fleet: at the observed rate the UBSAN and ASAN stages needed ~105 minutes for 17,2a, and both were killed (exit 137) at Drone's 60-minute step timeout partway into the second standard. Give each standard its own stage, as the GCC 13 Linux stages already do for the same reason. Co-Authored-By: Claude Opus 5 --- .drone.jsonnet | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index c4d88b0a..8acdabdd 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -297,14 +297,27 @@ local windows_pipeline(name, image, environment, arch = "amd64") = "clang-18", ), + # One C++ standard per stage: the Catalina runner is the slowest in the + # fleet, and 17,2a in a single stage was killed at Drone's 60-minute step + # timeout, halfway into the second standard. macos_pipeline( - "MacOS 10.15 Xcode 12.2 UBSAN", - { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '17,2a' } + ubsan, + "MacOS 10.15 Xcode 12.2 UBSAN C++17", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '17' } + ubsan, ), macos_pipeline( - "MacOS 10.15 Xcode 12.2 ASAN", - { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '17,2a' } + asan, + "MacOS 10.15 Xcode 12.2 UBSAN C++2a", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '2a' } + ubsan, + ), + + macos_pipeline( + "MacOS 10.15 Xcode 12.2 ASAN C++17", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '17' } + asan, + ), + + macos_pipeline( + "MacOS 10.15 Xcode 12.2 ASAN C++2a", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '2a' } + asan, ), macos_pipeline(