From b5d48df29c83407129e83b0c3a46264cc500118e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 1 Sep 2026 15:59:01 -0400 Subject: [PATCH 1/4] virtual parameters obtain their registry by ADL A class can now name the registry it belongs to, once, next to itself: class Animal { friend auto boost_openmethod_registry(Animal*) -> zoo_registry; }; The class then has an *affinity* for that registry, inherited by its derived classes, and everything that mentions it finds it: `virtual_ptr`, its deduction guides, `final_virtual_ptr`, the smart pointer aliases and factories, and any method that takes the class as a virtual parameter. A method declared without a registry argument takes the affinity its virtual parameters agree on. `inplace_vptr.hpp` already had this hook, privately, returning `void` to mean "no customization". Making the catch-all return BOOST_OPENMETHOD_DEFAULT_REGISTRY instead lets it serve as a default template argument directly, and makes backward compatibility structural: with no overload anywhere, every construct resolves to what it resolved to before. `.text` for test_virtual_ptr_dispatch.cpp is byte-identical, and the 530 defined symbols are unchanged. Having *no* affinity is not the same as an affinity for the default registry - only the former yields. That is what lets a method mix a class that has one with a class that has none, so a first affinity does not cascade errors through a codebase. Two conflicting affinities are diagnosed, as is a registry named on a method that contradicts one of its parameters. Deliberately out of scope, and documented as such: `virtual_` keeps its single template parameter; `use_classes` still registers into the macro default unless a registry is listed last; the `any` and `type_erasure` interop headers are untouched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1 --- CLAUDE.md | 26 +++ doc/modules/ROOT/examples/adl_registry.cpp | 71 ++++++ .../ROOT/pages/registries_and_policies.adoc | 55 +++++ doc/modules/ROOT/pages/virtual_ptr_alt.adoc | 6 + doc/mrdocs.yml | 1 - include/boost/openmethod/core.hpp | 216 ++++++++++++++++-- include/boost/openmethod/inplace_vptr.hpp | 16 +- .../interop/boost_intrusive_ptr.hpp | 4 +- .../openmethod/interop/std_shared_ptr.hpp | 4 +- .../openmethod/interop/std_unique_ptr.hpp | 4 +- include/boost/openmethod/macros.hpp | 14 +- ...pile_fail_adl_registry_ambiguous_bases.cpp | 32 +++ ...il_adl_registry_conflicting_affinities.cpp | 34 +++ ...le_fail_adl_registry_declared_mismatch.cpp | 29 +++ ...compile_fail_adl_registry_private_base.cpp | 28 +++ test/test_adl_registry.cpp | 90 ++++++++ test/test_adl_registry_hidden_friend.cpp | 57 +++++ test/test_adl_registry_inheritance.cpp | 66 ++++++ test/test_adl_registry_inplace.cpp | 60 +++++ test/test_adl_registry_scan.cpp | 90 ++++++++ test/test_adl_registry_smart_ptr.cpp | 79 +++++++ test/test_adl_registry_static_rtti.cpp | 61 +++++ test/test_adl_registry_two_registries.cpp | 83 +++++++ 23 files changed, 1101 insertions(+), 25 deletions(-) create mode 100644 doc/modules/ROOT/examples/adl_registry.cpp create mode 100644 test/compile_fail_adl_registry_ambiguous_bases.cpp create mode 100644 test/compile_fail_adl_registry_conflicting_affinities.cpp create mode 100644 test/compile_fail_adl_registry_declared_mismatch.cpp create mode 100644 test/compile_fail_adl_registry_private_base.cpp create mode 100644 test/test_adl_registry.cpp create mode 100644 test/test_adl_registry_hidden_friend.cpp create mode 100644 test/test_adl_registry_inheritance.cpp create mode 100644 test/test_adl_registry_inplace.cpp create mode 100644 test/test_adl_registry_scan.cpp create mode 100644 test/test_adl_registry_smart_ptr.cpp create mode 100644 test/test_adl_registry_static_rtti.cpp create mode 100644 test/test_adl_registry_two_registries.cpp diff --git a/CLAUDE.md b/CLAUDE.md index 690de73e..68896707 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -484,6 +484,32 @@ rules: `missing template arguments` - but a name that *does* resolve would bind to the wrong type silently. `::registry` works. +### Registry affinity + +A class can name its registry instead of the program naming one for every class: declare +`auto boost_openmethod_registry(Class*) -> Registry;`, preferably as a hidden friend. The class +then has an *affinity* for that registry, inherited by its derived classes, and +`default_registry_of` reads it back. `virtual_ptr` and the smart pointer aliases default to it, +and a method declared without a registry argument takes the affinity its virtual parameters agree +on (`detail::method_registry`, driven by `detail::param_affinity` / `agreed_affinity`). + +Having *no* affinity is not the same as an affinity for the default registry: only the former +yields, which is what lets a method mix a class that has one with a class that has none. +`detail::affinity_of` is where that mapping happens; nothing public returns an affinity. + +Two things deliberately do **not** participate, and both are documented as such: + +- `use_classes` / `BOOST_OPENMETHOD_CLASSES` still register into + `BOOST_OPENMETHOD_DEFAULT_REGISTRY` unless a registry is listed last. Registering a class that + has an affinity without naming its registry is a run-time `missing_class`, not a compile error. +- The `any` and `type_erasure` interop headers are untouched. `virtual_any&` contributes no + affinity, so a method over one behaves exactly as before. + +**A test that selects a registry through an affinity needs no PCH marker.** The scan below exists +because `BOOST_OPENMETHOD_DEFAULT_REGISTRY` must be defined before `core.hpp` is parsed, and a +force-included PCH parses it first. An affinity has no such ordering relation to the library +headers, so those tests can share the PCH - do not add a fourth marker for them. + `test/CMakeLists.txt` withholds the shared PCH from any `test_*.cpp` that overrides the registry - a force-included PCH would still precede the `#define`. It detects them by scanning for the token `BOOST_OPENMETHOD_DEFAULT_REGISTRY` **or** for an include of a header that diff --git a/doc/modules/ROOT/examples/adl_registry.cpp b/doc/modules/ROOT/examples/adl_registry.cpp new file mode 100644 index 00000000..03d6bcc1 --- /dev/null +++ b/doc/modules/ROOT/examples/adl_registry.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2017-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 + +#define BOOST_TEST_MODULE adl_registry +#include + +// tag::registry[] +struct zoo_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks> {}; +// end::registry[] + +using namespace boost::openmethod; + +// tag::affinity[] +namespace zoo { + +class Animal { + public: + virtual ~Animal() = default; + + private: + // Animal - and every class derived from it - belongs to zoo_registry + friend auto boost_openmethod_registry(Animal*) -> zoo_registry; +}; + +class Dog : public Animal {}; +class Cat : public Animal {}; + +} // namespace zoo +// end::affinity[] + +// tag::methods[] +BOOST_OPENMETHOD_CLASSES(zoo::Animal, zoo::Dog, zoo::Cat, zoo_registry); + +// no registry argument: speak follows Animal +BOOST_OPENMETHOD(speak, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(speak, (const zoo::Dog&), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (const zoo::Cat&), std::string) { + return "meow"; +} +// end::methods[] + +// tag::virtual_ptr[] +// ...and so does virtual_ptr +static_assert( + std::is_same_v, virtual_ptr>); +// end::virtual_ptr[] + +BOOST_AUTO_TEST_CASE(adl_registry) { + // tag::call[] + initialize(); + + zoo::Dog spot; + zoo::Cat felix; + // end::call[] + + BOOST_TEST(speak(spot) == "bark"); + BOOST_TEST(speak(felix) == "meow"); +} diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index 01d158b5..a913150f 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -50,6 +50,61 @@ declaration must use the same class-key as the definition. Qualify the name if it could also be found in namespace `boost::openmethod` - `registry` in particular. +### Registry affinity + +`BOOST_OPENMETHOD_DEFAULT_REGISTRY` is a whole-program answer: one registry, for +every class. A class can instead name its own, by declaring a +cpp:boost_openmethod_registry[] function that takes a pointer to it and returns +the registry. The function is never called - only its return type is used - so +it needs no definition: + +[source,c++] +---- +include::example$adl_registry.cpp[tag=affinity] +---- + +The class then has an _affinity_ for that registry, and everything that mentions +the class finds it. A method declared without a registry argument takes the +affinity of its virtual parameters: + +[source,c++] +---- +include::example$adl_registry.cpp[tag=methods] +---- + +...and so does cpp:virtual_ptr[], along with the smart pointer aliases and the +`make_*_virtual` factories: + +[source,c++] +---- +include::example$adl_registry.cpp[tag=virtual_ptr] +---- + +An affinity is inherited: declaring it for the root of a hierarchy covers every +class derived from it, because the derived-to-base pointer conversion makes the +root's declaration viable. A declaration for a derived class is a better match, +and wins. + +A class that declares nothing has *no* affinity, which is not the same as an +affinity for the default registry. Only the first yields: a method may mix a +class that has an affinity with one that has none, and lands in the former's +registry. Two virtual parameters with *different* affinities are an error, as is +a registry named on the method that contradicts one of its parameters. + +A hidden friend, as above, is the spelling to prefer. The declaration must +precede every use of the class in a method, a `virtual_ptr` or a class +registration - declaring it later makes the program ill-formed with no +diagnostic required, and compilers disagree silently about which registry the +earlier use got. Being part of the class, a hidden friend cannot be late. + +WARNING: An affinity does not reach +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], which +still registers into `BOOST_OPENMETHOD_DEFAULT_REGISTRY` unless a registry is +listed last. Registering a class with an affinity, without naming its registry, +puts the class in one registry and its methods in another - and that shows up as +a `missing_class` error at run time, not as a compile error. List the registry: +`BOOST_OPENMETHOD_CLASSES(Animal, Dog, zoo_registry)`. + A registry has a collection of _policies_. Each policy belongs to a policy category. A registry may contain at most one policy of each category. Policies control how type information is obtained, how vptrs are acquired, how errors are diff --git a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc index 29b45cdb..5798b7f3 100644 --- a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc +++ b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc @@ -108,6 +108,12 @@ 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`. +`inplace_vptr_base` also declares the class's +xref:ROOT:registries_and_policies.adoc#registries_and_policies[registry affinity], +as a hidden friend - it is told the registry, and every other construct can then +find it. A method over such a hierarchy needs no registry argument, and the +program needs no `BOOST_OPENMETHOD_DEFAULT_REGISTRY` override. + An object that embeds its v-table pointer does not need to be wrapped in a `virtual_ptr` - the two fill the same goal, fast access to the v-table pointer - and wrapping one is rejected at compile time. diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 3e4382f5..4edb9450 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -22,7 +22,6 @@ exclude-symbols: - 'boost::openmethod::registry::initialize' - 'boost::openmethod::registry::finalize' - 'boost::openmethod::boost_openmethod_bases' - - 'boost::openmethod::boost_openmethod_registry' - 'boost::openmethod::registry_state::st' # Macros. Only the public macros carry a doc comment, and with diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index cad8a165..dbcecdf1 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -129,11 +129,130 @@ namespace boost::openmethod { #endif namespace detail { + using sfinae = void; -} + +using macro_default_registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY; + +template +constexpr bool false_t = false; // workaround before CWG2518/P2593R1 + +} // namespace detail + +//! Return the registry a class belongs to (ADL customization point). +//! +//! This declaration is a catch-all that matches any argument list and returns +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, denoting the absence of +//! customization. If an overload beats it for a given class, that class has an +//! *affinity* for the overload's return type, and that registry becomes the +//! default for every construct that mentions the class: @ref virtual_ptr and +//! the smart pointer aliases, and the methods that take it as a virtual +//! parameter. +//! +//! An affinity declared for a class extends to its derived classes, because +//! the derived-to-base pointer conversion makes the base's overload viable. An +//! overload on the derived class itself is a better match, and wins. +//! +//! @par Requirements +//! +//! The library uses argument-dependent lookup to find an overload that +//! satisfies the following requirements: +//! +//! @li The single parameter is a pointer to the class. Its role is to carry the +//! class to the overload. It must not be dereferenced - the function is never +//! called, only its return type is used. +//! +//! @li The return type is a @ref registry. +//! +//! The overload must be declared before the first construct that mentions the +//! class. Declaring it afterwards makes the program ill-formed, no diagnostic +//! required - compilers disagree silently on which registry the earlier +//! construct used. Declaring it as a hidden friend, as below, makes that +//! impossible. +//! +//! @par Example +//! +//! include:../examples/adl_registry.cpp#affinity +//! +//! @see @ref default_registry_of +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) +auto boost_openmethod_registry(...) -> BOOST_OPENMETHOD_DEFAULT_REGISTRY; + +namespace detail { + +// The catch-all is viable for every class, so the primary template is reached +// only when the call to `boost_openmethod_registry` is ill-formed rather than +// unmatched. Three ways to get there, all of them a base class whose overload +// cannot be used: two base classes with different affinities (ambiguous +// overload), a private base (inaccessible conversion), and a repeated +// non-virtual base (ambiguous conversion). Declaring the overload for the +// class itself resolves all three. +template +struct adl_registry_aux { + static_assert( + false_t, + "cannot tell which registry this class belongs to: " + "boost_openmethod_registry is ambiguous or inaccessible for it - " + "declare one for the class itself"); + using type = macro_default_registry; +}; + +template +struct adl_registry_aux< + Class, + std::void_t()))>> { + using type = decltype(boost_openmethod_registry(std::declval())); +}; + +// The type whose namespace and base classes are consulted. `virtual_traits::virtual_type` would be the exact answer, but it needs the very +// `Registry` being computed. Probing for `element_type` covers `std:: +// shared_ptr`, `std::unique_ptr` and `boost::intrusive_ptr` without depending +// on their headers. +template +struct registry_anchor { + using type = T; +}; + +template +struct registry_anchor> { + using type = typename T::element_type; +}; + +template +using unadorned = std::remove_cv_t< + std::remove_pointer_t>>>; + +template +using adl_registry = + typename adl_registry_aux>::type>>::type; + +} // namespace detail + +//! The registry a class belongs to by default. +//! +//! Evaluates to the return type of the @ref boost_openmethod_registry overload +//! found for `T` by argument-dependent lookup, after removing cv-qualifiers, +//! references and pointers, and unwrapping one level of smart pointer - so +//! `Class`, `const Class&`, `Class*` and `std::shared_ptr` all yield the +//! same registry. +//! +//! If `T`\'s class has no affinity, this is @ref +//! BOOST_OPENMETHOD_DEFAULT_REGISTRY. Note that this is a query, and always +//! answers; having *no* affinity is not the same as having an affinity for the +//! default registry, a distinction that matters when a method draws its +//! registry from its virtual parameters. +//! +//! @tparam T A class, or a reference, pointer or smart pointer to one. +//! +//! @see @ref boost_openmethod_registry +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) +template +using default_registry_of = detail::adl_registry; template< - class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + class Class, class Registry = default_registry_of, typename = detail::sfinae> class virtual_ptr; @@ -142,8 +261,6 @@ class virtual_ptr; namespace detail { -using macro_default_registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY; - template struct extract_registry; @@ -818,7 +935,7 @@ inline auto final_virtual_ptr(Arg&& obj) { // doesn't like it. template inline auto final_virtual_ptr(Arg&& obj) { - return final_virtual_ptr( + return final_virtual_ptr, Arg>( std::forward(obj)); } //! Wide pointer combining pointers to an object and its v-table @@ -1712,7 +1829,7 @@ class virtual_ptr< //! @return A `virtual_ptr`. template virtual_ptr(Class& obj) - -> virtual_ptr; + -> virtual_ptr>; //! Construct a `virtual_ptr` from a xvalue reference. //! @@ -1721,7 +1838,7 @@ virtual_ptr(Class& obj) //! @return A `virtual_ptr`. template virtual_ptr(Class&& obj) - -> virtual_ptr; + -> virtual_ptr>; // Alas this is not allowed: // template @@ -1946,9 +2063,6 @@ template struct parameter_traits&, Registry> : virtual_traits&, Registry> {}; -template -constexpr bool false_t = false; // workaround before CWG2518/P2593R1 - template struct validate_method_parameter : std::true_type {}; @@ -2058,14 +2172,88 @@ struct validate_method_parameter< //! selected is not specified, but it is the same across calls with the //! same arguments types. //! +namespace detail { + +// A class that never declared `boost_openmethod_registry` has no affinity for +// any registry, which is not the same as an affinity for the default one. Only +// the former yields to a parameter that does have one, which is what lets a +// method mix a class that has an affinity with one that has none. +struct no_affinity; + +template +using affinity_of = std::conditional_t< + std::is_same_v, no_affinity, Registry>; + +template +struct param_affinity { + using type = no_affinity; +}; + +template +struct param_affinity> { + using type = affinity_of>; +}; + +template +struct param_affinity> { + using type = affinity_of; +}; + +template +struct param_affinity&> { + using type = affinity_of; +}; + +template +struct param_affinity&> { + using type = affinity_of; +}; + +// The first affinity in the parameter list wins; every other one must agree. +template +struct agreed_affinity { + using type = no_affinity; +}; + +template +struct agreed_affinity { + using rest = typename agreed_affinity::type; + static_assert( + std::is_same_v || + std::is_same_v || + std::is_same_v, + "virtual parameters have conflicting registry affinities"); + using type = std:: + conditional_t, rest, Affinity>; +}; + +// The registry a method takes when its declaration does not name one. +template +struct method_registry_aux { + using type = macro_default_registry; +}; + +template +struct method_registry_aux { + using found = typename agreed_affinity< + typename param_affinity::type...>::type; + using type = std::conditional_t< + std::is_same_v, macro_default_registry, found>; +}; + +template +using method_registry = typename method_registry_aux::type; + +} // namespace detail + //! @tparam Id A type //! @tparam Fn A function type -//! @tparam Registry The registry in which the method is defined +//! @tparam Registry The registry in which the method is defined. Defaults to +//! the registry that `Fn`\'s virtual parameters have an affinity for, and to +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY if none of them has one. //! //! @see [Core API](xref:ROOT:core_api.adoc) -template< - typename Id, typename Fn, - class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY> +template> class method; //! Method with a specific id, signature and return type diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index f874c0d5..51b2a75b 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -10,12 +10,14 @@ namespace boost::openmethod { namespace detail { -void boost_openmethod_registry(...); void boost_openmethod_bases(...); +// `inplace_vptr_base` declares the affinity that `default_registry_of` reads +// back, so the two are the same question. There is deliberately no catch-all +// here: the one in core.hpp serves both, and a second one returning `void` +// would shadow it for lookups from this namespace. template -using inplace_vptr_registry = - decltype(boost_openmethod_registry(std::declval())); +using inplace_vptr_registry = default_registry_of; template struct update_vptr_bases; @@ -216,6 +218,14 @@ class inplace_vptr_derived { (!detail::is_registry && ...), "registry can be specified only for root classes"); + // Without this, a `Base1` that is not an inplace_vptr root would silently + // acquire the default registry from the catch-all - before the hook was + // unified, `inplace_vptr_registry` was `void` and the mistake was a + // hard error. The single-base specialization asserts the same thing. + static_assert( + std::is_base_of_v, + "class must inherit from inplace_vptr_base"); + friend auto boost_openmethod_registry(Class*) -> detail::inplace_vptr_registry; friend auto boost_openmethod_bases(Class*) diff --git a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp index 1cb36db2..4e7e9c57 100644 --- a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp +++ b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp @@ -129,7 +129,7 @@ struct virtual_traits&, Registry> { //! include:intrusive_ptr.cpp#boost_intrusive_virtual_ptr_alias //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template +template> using boost_intrusive_virtual_ptr = virtual_ptr, Registry>; @@ -153,7 +153,7 @@ using boost_intrusive_virtual_ptr = //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< - class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + class Class, class Registry = default_registry_of, typename... T> inline auto make_boost_intrusive_virtual(T&&... args) { return final_virtual_ptr(intrusive_ptr( diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index 49f69a84..02b35306 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -202,7 +202,7 @@ struct virtual_traits&, Registry> { //! include:smart_pointers.cpp#shared_virtual_ptr_alias //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template +template> using shared_virtual_ptr = virtual_ptr, Registry>; //! Create a new object and return a `shared_virtual_ptr` to it. @@ -225,7 +225,7 @@ using shared_virtual_ptr = virtual_ptr, Registry>; //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< - class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + class Class, class Registry = default_registry_of, typename... T> inline auto make_shared_virtual(T&&... args) { return final_virtual_ptr( diff --git a/include/boost/openmethod/interop/std_unique_ptr.hpp b/include/boost/openmethod/interop/std_unique_ptr.hpp index a6128723..b99a6e52 100644 --- a/include/boost/openmethod/interop/std_unique_ptr.hpp +++ b/include/boost/openmethod/interop/std_unique_ptr.hpp @@ -72,7 +72,7 @@ struct virtual_traits, Registry> { //! include:smart_pointers.cpp#unique_virtual_ptr_alias //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template +template> using unique_virtual_ptr = virtual_ptr, Registry>; //! Create a new object and return a `unique_virtual_ptr` to it. @@ -95,7 +95,7 @@ using unique_virtual_ptr = virtual_ptr, Registry>; //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< - class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, + class Class, class Registry = default_registry_of, typename... T> inline auto make_unique_virtual(T&&... args) { return final_virtual_ptr( diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index c489c398..76a35b62 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -25,16 +25,26 @@ struct enable_forwarder< template struct va_args; +// `registry_for` is an alias template, not a typedef, so that the scan for an +// affinity among the virtual parameters does not run for a declaration that +// names a registry. `registry` is retained: it is the registry a declaration +// *names*, which is no longer the same question. template struct va_args { using return_type = ReturnType; using registry = macro_default_registry; + + template + using registry_for = method_registry; }; template struct va_args { using return_type = ReturnType; using registry = Registry; + + template + using registry_for = Registry; }; template @@ -119,7 +129,9 @@ inline constexpr bool method_not_found = false; BOOST_OPENMETHOD_ID(ID), \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type \ PARAMETERS, \ - ::boost::openmethod::detail::va_args<__VA_ARGS__>::registry> + ::boost::openmethod::detail::va_args<__VA_ARGS__>::registry_for< \ + ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type \ + PARAMETERS>> //! Declare a method. //! diff --git a/test/compile_fail_adl_registry_ambiguous_bases.cpp b/test/compile_fail_adl_registry_ambiguous_bases.cpp new file mode 100644 index 00000000..7222ee4f --- /dev/null +++ b/test/compile_fail_adl_registry_ambiguous_bases.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2017-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) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: ambiguous or inaccessible + +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; +struct kennel_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Pet { + virtual ~Pet() = default; +}; + +auto boost_openmethod_registry(Animal*) -> zoo_registry; +auto boost_openmethod_registry(Pet*) -> kennel_registry; + +// Inherits two different affinities, and says nothing itself. +struct Dog : Animal, Pet {}; + +int main() { + (void)sizeof(virtual_ptr); +} diff --git a/test/compile_fail_adl_registry_conflicting_affinities.cpp b/test/compile_fail_adl_registry_conflicting_affinities.cpp new file mode 100644 index 00000000..49ccba8c --- /dev/null +++ b/test/compile_fail_adl_registry_conflicting_affinities.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2017-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) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: conflicting registry affinities + +#include + +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; +struct garage_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; + friend auto boost_openmethod_registry(Animal*) -> zoo_registry; +}; + +struct Vehicle { + virtual ~Vehicle() = default; + friend auto boost_openmethod_registry(Vehicle*) -> garage_registry; +}; + +// A method cannot span two registries, and its parameters say two different +// things. Naming one on the declaration is the way to settle it. +BOOST_OPENMETHOD( + collide, (virtual_, virtual_), std::string); + +int main() { +} diff --git a/test/compile_fail_adl_registry_declared_mismatch.cpp b/test/compile_fail_adl_registry_declared_mismatch.cpp new file mode 100644 index 00000000..411e33ee --- /dev/null +++ b/test/compile_fail_adl_registry_declared_mismatch.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2017-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) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: registry mismatch + +#include + +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; +struct other_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; + friend auto boost_openmethod_registry(Animal*) -> zoo_registry; +}; + +// `virtual_ptr` carries `zoo_registry`, because that is what `Animal` +// says. Declaring the method in another registry contradicts the parameter - +// the check that already guarded an explicitly spelled `virtual_ptr`. +BOOST_OPENMETHOD(poke, (virtual_ptr), std::string, other_registry); + +int main() { +} diff --git a/test/compile_fail_adl_registry_private_base.cpp b/test/compile_fail_adl_registry_private_base.cpp new file mode 100644 index 00000000..392f85df --- /dev/null +++ b/test/compile_fail_adl_registry_private_base.cpp @@ -0,0 +1,28 @@ +// Copyright (c) 2017-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) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: ambiguous or inaccessible + +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; +}; + +auto boost_openmethod_registry(Animal*) -> zoo_registry; + +// The conversion to `Animal*` is inaccessible, so the base's overload cannot +// answer for `Vault` - and neither can the catch-all, which is a worse match. +// The class has to declare its own. +struct Vault : private Animal {}; + +int main() { + (void)sizeof(virtual_ptr); +} diff --git a/test/test_adl_registry.cpp b/test/test_adl_registry.cpp new file mode 100644 index 00000000..36861404 --- /dev/null +++ b/test/test_adl_registry.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2017-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) + +// A class declares an affinity for a registry once, next to itself. Everything +// that mentions the class then finds that registry on its own: `virtual_ptr`, +// and the methods that take the class as a virtual parameter. + +#include + +#include +#include + +#define BOOST_TEST_MODULE adl_registry +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry::with {}; + +namespace zoo { + +struct Animal { + virtual ~Animal() = default; +}; + +auto boost_openmethod_registry(Animal*) -> zoo_registry; + +struct Dog : Animal {}; +struct Cat : Animal {}; + +} // namespace zoo + +// A class that never declared one keeps the default registry. +struct Widget { + virtual ~Widget() = default; +}; + +using zoo::Animal, zoo::Dog, zoo::Cat; + +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, default_registry>); + +// `virtual_ptr` picks it up, so `virtual_ptr` is not a `virtual_ptr` in +// the default registry. +static_assert(std::is_same_v, virtual_ptr>); +static_assert( + std::is_same_v, virtual_ptr>); + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, zoo_registry); + +// Neither declaration names a registry; both land in `zoo_registry`. +BOOST_OPENMETHOD(speak, (virtual_), std::string); +BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE(speak, (virtual_), std::string), + method< + BOOST_OPENMETHOD_ID(speak), std::string(virtual_), + zoo_registry>>); + +BOOST_OPENMETHOD_OVERRIDE(speak, (const Dog&), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (const Cat&), std::string) { + return "meow"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), std::string) { + return "woof"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), std::string) { + return "hiss"; +} + +BOOST_AUTO_TEST_CASE(dispatch_in_the_registry_the_class_names) { + initialize(); + + Dog spot; + Cat felix; + + BOOST_TEST(speak(spot) == "bark"); + BOOST_TEST(speak(felix) == "meow"); + BOOST_TEST(poke(virtual_ptr(spot)) == "woof"); + BOOST_TEST(poke(virtual_ptr(felix)) == "hiss"); +} diff --git a/test/test_adl_registry_hidden_friend.cpp b/test/test_adl_registry_hidden_friend.cpp new file mode 100644 index 00000000..0efb6858 --- /dev/null +++ b/test/test_adl_registry_hidden_friend.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2017-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) + +// The recommended spelling is a hidden friend: it is part of the class, so no +// use of the class can precede it, and the declaration-order rule cannot be +// broken. Argument-dependent lookup finds it from anywhere. + +#include + +#include +#include + +#define BOOST_TEST_MODULE adl_registry_hidden_friend +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry::with {}; + +namespace zoo { + +class Animal { + public: + virtual ~Animal() = default; + + private: + friend auto boost_openmethod_registry(Animal*) -> zoo_registry; +}; + +class Dog : public Animal {}; + +} // namespace zoo + +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); + +// The method lives in a third namespace, and still finds the affinity. +namespace vet { + +BOOST_OPENMETHOD_CLASSES(zoo::Animal, zoo::Dog, zoo_registry); + +BOOST_OPENMETHOD(examine, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(examine, (const zoo::Dog&), std::string) { + return "healthy dog"; +} + +} // namespace vet + +BOOST_AUTO_TEST_CASE(a_hidden_friend_cannot_be_declared_late) { + initialize(); + + zoo::Dog rex; + BOOST_TEST(vet::examine(rex) == "healthy dog"); +} diff --git a/test/test_adl_registry_inheritance.cpp b/test/test_adl_registry_inheritance.cpp new file mode 100644 index 00000000..1e57f7f0 --- /dev/null +++ b/test/test_adl_registry_inheritance.cpp @@ -0,0 +1,66 @@ +// Copyright (c) 2017-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) + +// An affinity declared for a base class reaches its derived classes, because +// the derived-to-base pointer conversion makes the base's overload viable. A +// class whose base overload cannot be used - inaccessible or ambiguous - is +// diagnosed rather than defaulted: see compile_fail_adl_registry_*.cpp. + +#include + +#define BOOST_TEST_MODULE adl_registry_inheritance +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry::with {}; +struct kennel_registry : default_registry::with {}; + +namespace zoo { + +struct Animal { + virtual ~Animal() = default; +}; + +auto boost_openmethod_registry(Animal*) -> zoo_registry; + +struct Cat : Animal {}; +struct Persian : Cat {}; + +// An overload on the class itself is an exact match, and beats the base's. +struct Dog : Animal {}; +auto boost_openmethod_registry(Dog*) -> kennel_registry; +struct Poodle : Dog {}; + +// A virtual base is unambiguous, so it still reaches the affinity. +struct Left : virtual Animal {}; +struct Right : virtual Animal {}; +struct Chimera : Left, Right {}; + +} // namespace zoo + +using namespace zoo; + +// inherited, however deep +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); + +// the exact match wins, and is itself inherited +static_assert(std::is_same_v, kennel_registry>); +static_assert(std::is_same_v, kennel_registry>); + +// one Animal, so one affinity +static_assert(std::is_same_v, zoo_registry>); + +// An unrelated class is untouched by any of it. +struct Widget { + virtual ~Widget() = default; +}; + +static_assert(std::is_same_v, default_registry>); + +BOOST_AUTO_TEST_CASE(inheritance_is_compile_time_only) { + BOOST_TEST(true); +} diff --git a/test/test_adl_registry_inplace.cpp b/test/test_adl_registry_inplace.cpp new file mode 100644 index 00000000..98281244 --- /dev/null +++ b/test/test_adl_registry_inplace.cpp @@ -0,0 +1,60 @@ +// Copyright (c) 2017-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) + +// `inplace_vptr_base` declares the affinity itself, as a hidden friend. Since +// the hook is now the library's own, a method over such a class needs neither a +// registry argument nor a BOOST_OPENMETHOD_DEFAULT_REGISTRY override. + +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE adl_registry_inplace +#include + +namespace bom = boost::openmethod; + +// An inplace_vptr hierarchy needs neither a vptr policy nor a type hash. +struct zoo_registry + : bom::default_registry::without { +}; + +struct Animal : bom::inplace_vptr_base {}; +struct Dog : Animal, bom::inplace_vptr_derived {}; +struct Cat : Animal, bom::inplace_vptr_derived {}; + +// The mixin's hidden friend is what `default_registry_of` reads back. +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); + +// No registry named, and no #define: the method follows the class. +BOOST_OPENMETHOD(speak, (bom::virtual_), std::string); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + speak, (bom::virtual_), std::string), + bom::method< + BOOST_OPENMETHOD_ID(speak), + std::string(bom::virtual_), zoo_registry>>); + +BOOST_OPENMETHOD_OVERRIDE(speak, (const Dog&), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (const Cat&), std::string) { + return "meow"; +} + +BOOST_AUTO_TEST_CASE(inplace_vptr_supplies_the_affinity) { + bom::initialize(); + + Dog spot; + Cat felix; + + BOOST_TEST(speak(spot) == "bark"); + BOOST_TEST(speak(felix) == "meow"); +} diff --git a/test/test_adl_registry_scan.cpp b/test/test_adl_registry_scan.cpp new file mode 100644 index 00000000..8e865c37 --- /dev/null +++ b/test/test_adl_registry_scan.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2017-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) + +// How a method picks its registry: it takes the affinity its virtual +// parameters agree on. A class with no affinity contributes nothing, so it +// yields rather than conflicting. A registry named on the declaration wins +// outright, and no scan happens. + +#include + +#include + +#define BOOST_TEST_MODULE adl_registry_scan +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry::with {}; +struct other_registry : default_registry::with {}; + +namespace zoo { + +struct Animal { + virtual ~Animal() = default; +}; + +auto boost_openmethod_registry(Animal*) -> zoo_registry; + +struct Dog : Animal {}; + +} // namespace zoo + +struct Widget { + virtual ~Widget() = default; +}; + +using zoo::Animal, zoo::Dog; + +template +using scan = detail::method_registry; + +// One virtual parameter with an affinity, in either shape. +static_assert(std::is_same_v)>, zoo_registry>); +static_assert(std::is_same_v)>, zoo_registry>); +static_assert(std::is_same_v&)>, zoo_registry>); +static_assert( + std::is_same_v&)>, zoo_registry>); + +// The affinity is inherited, so a derived class carries it too. +static_assert(std::is_same_v)>, zoo_registry>); + +// No affinity anywhere: the macro default, exactly as before this feature. +static_assert( + std::is_same_v)>, default_registry>); +static_assert(std::is_same_v, default_registry>); + +// Mixing a class that has an affinity with one that has none: the one without +// yields. This is what keeps a first affinity from cascading errors. +static_assert(std::is_same_v< + scan, virtual_)>, + zoo_registry>); +static_assert(std::is_same_v< + scan, virtual_)>, + zoo_registry>); + +// Mixed shapes agreeing. +static_assert(std::is_same_v< + scan, virtual_)>, + zoo_registry>); + +// Non-virtual parameters are ignored. +static_assert(std::is_same_v< + scan, char*)>, zoo_registry>); + +// A registry named on the declaration wins, and the parameters are not +// consulted at all - the form that predates this feature. +BOOST_OPENMETHOD(ping, (virtual_), std::string, other_registry); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + ping, (virtual_), std::string, other_registry), + method< + BOOST_OPENMETHOD_ID(ping), std::string(virtual_), + other_registry>>); + +BOOST_AUTO_TEST_CASE(scan_is_compile_time_only) { + BOOST_TEST(true); +} diff --git a/test/test_adl_registry_smart_ptr.cpp b/test/test_adl_registry_smart_ptr.cpp new file mode 100644 index 00000000..7d66262a --- /dev/null +++ b/test/test_adl_registry_smart_ptr.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2017-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) + +// The affinity is found through a smart pointer, so the alias spellings and +// the `virtual_ptr` they stand for agree about the registry. + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define BOOST_TEST_MODULE adl_registry_smart_ptr +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry::with {}; + +namespace zoo { + +struct Animal : boost::intrusive_ref_counter { + virtual ~Animal() = default; +}; + +auto boost_openmethod_registry(Animal*) -> zoo_registry; + +struct Dog : Animal {}; + +} // namespace zoo + +using zoo::Animal, zoo::Dog; + +// the anchor is the pointee, whatever the wrapper +static_assert( + std::is_same_v>, zoo_registry>); +static_assert( + std::is_same_v>, zoo_registry>); +static_assert(std::is_same_v< + default_registry_of>, zoo_registry>); +static_assert(std::is_same_v< + default_registry_of&>, zoo_registry>); + +// so the alias and the type it stands for are the same type +static_assert(std::is_same_v< + shared_virtual_ptr, + virtual_ptr, zoo_registry>>); +static_assert(std::is_same_v< + unique_virtual_ptr, + virtual_ptr, zoo_registry>>); +static_assert(std::is_same_v< + boost_intrusive_virtual_ptr, + virtual_ptr, zoo_registry>>); + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, zoo_registry); + +BOOST_OPENMETHOD(name, (shared_virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (shared_virtual_ptr), std::string) { + return "dog"; +} + +BOOST_AUTO_TEST_CASE(factories_need_no_registry_argument) { + initialize(); + + auto dog = make_shared_virtual(); + static_assert(std::is_same_v>); + BOOST_TEST(name(dog) == "dog"); + + auto owned = make_unique_virtual(); + static_assert(std::is_same_v>); +} diff --git a/test/test_adl_registry_static_rtti.cpp b/test/test_adl_registry_static_rtti.cpp new file mode 100644 index 00000000..e4f7a136 --- /dev/null +++ b/test/test_adl_registry_static_rtti.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2017-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) + +// The ADL twin of test_static_rtti.cpp: the same registry, selected by an +// affinity instead of by BOOST_OPENMETHOD_DEFAULT_REGISTRY. Worth its own test +// because a `static_rtti` registry has no `vptr` policy, so every `virtual_ptr` +// has to be created where the exact class is known. + +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE adl_registry_static_rtti +#include + +using namespace boost::openmethod; + +struct static_registry : registry {}; + +namespace shapes { + +struct Shape { + friend auto boost_openmethod_registry(Shape*) -> static_registry; +}; + +struct Square : Shape {}; +struct Circle : Shape {}; + +} // namespace shapes + +using shapes::Shape, shapes::Square, shapes::Circle; + +static_assert(std::is_same_v, static_registry>); +static_assert( + std::is_same_v, virtual_ptr>); + +BOOST_OPENMETHOD_CLASSES(Shape, Square, Circle, static_registry); + +BOOST_OPENMETHOD(name, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr), std::string) { + return "square"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr), std::string) { + return "circle"; +} + +BOOST_AUTO_TEST_CASE(affinity_works_without_a_vptr_policy) { + initialize(); + + Square square; + Circle circle; + + BOOST_TEST(name(final_virtual_ptr(square)) == "square"); + BOOST_TEST(name(final_virtual_ptr(circle)) == "circle"); +} diff --git a/test/test_adl_registry_two_registries.cpp b/test/test_adl_registry_two_registries.cpp new file mode 100644 index 00000000..d14ce80f --- /dev/null +++ b/test/test_adl_registry_two_registries.cpp @@ -0,0 +1,83 @@ +// Copyright (c) 2017-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) + +// Two hierarchies with different affinities in one translation unit. Neither +// method names a registry; each lands in its own, and the two initialize and +// dispatch independently. + +#include + +#include +#include + +#define BOOST_TEST_MODULE adl_registry_two_registries +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry::with {}; +struct garage_registry : default_registry::with {}; + +namespace zoo { + +struct Animal { + virtual ~Animal() = default; + friend auto boost_openmethod_registry(Animal*) -> zoo_registry; +}; + +struct Dog : Animal {}; + +} // namespace zoo + +namespace garage { + +struct Vehicle { + virtual ~Vehicle() = default; + friend auto boost_openmethod_registry(Vehicle*) -> garage_registry; +}; + +struct Truck : Vehicle {}; + +} // namespace garage + +BOOST_OPENMETHOD_CLASSES(zoo::Animal, zoo::Dog, zoo_registry); +BOOST_OPENMETHOD_CLASSES(garage::Vehicle, garage::Truck, garage_registry); + +BOOST_OPENMETHOD(describe, (virtual_), std::string); +BOOST_OPENMETHOD(inspect, (virtual_), std::string); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + describe, (virtual_), std::string), + method< + BOOST_OPENMETHOD_ID(describe), + std::string(virtual_), zoo_registry>>); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + inspect, (virtual_), std::string), + method< + BOOST_OPENMETHOD_ID(inspect), + std::string(virtual_), + garage_registry>>); + +BOOST_OPENMETHOD_OVERRIDE(describe, (const zoo::Dog&), std::string) { + return "a dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(inspect, (const garage::Truck&), std::string) { + return "a truck"; +} + +BOOST_AUTO_TEST_CASE(two_registries_side_by_side) { + initialize(); + initialize(); + + zoo::Dog rex; + garage::Truck lorry; + + BOOST_TEST(describe(rex) == "a dog"); + BOOST_TEST(inspect(lorry) == "a truck"); +} From 2665b360bc61ccebe234184995f659e0117e52c3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 1 Sep 2026 21:59:25 -0400 Subject: [PATCH 2/4] test: force the instantiation the registry-mismatch check needs compile_fail_adl_registry_declared_mismatch only declared the method. The "registry mismatch" static_assert is a fold in `method`'s class body, so it fires when the class is instantiated - and declaring the method is not enough. gcc and clang instantiate it anyway through the static registrar; MSVC does not, so the file compiled and the compile-fail test failed on every Windows job. Call the method in main(), the way compile_fail_virtual_ptr_different_registries.cpp already does. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1 --- test/compile_fail_adl_registry_declared_mismatch.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/compile_fail_adl_registry_declared_mismatch.cpp b/test/compile_fail_adl_registry_declared_mismatch.cpp index 411e33ee..511da69f 100644 --- a/test/compile_fail_adl_registry_declared_mismatch.cpp +++ b/test/compile_fail_adl_registry_declared_mismatch.cpp @@ -26,4 +26,13 @@ struct Animal { BOOST_OPENMETHOD(poke, (virtual_ptr), std::string, other_registry); int main() { + // The check is a fold in `method`'s class body, so it needs the class to be + // instantiated. Declaring the method is not enough: gcc and clang + // instantiate it anyway through the registrar, but MSVC does not, and the + // file then compiles. Calling it forces the point, on every compiler - like + // compile_fail_virtual_ptr_different_registries.cpp does. + Animal animal; + poke(animal); + + return 0; } From 277173e4cf2d13400d08a3f5cff2de9350bbd138 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 1 Sep 2026 22:33:12 -0400 Subject: [PATCH 3/4] rename default_registry_of to registry_affinity "Affinity" is the term the documentation uses for the relation, so the query that reads it back should carry it too. `default_registry_of` also read badly where it mattered most: "Registry defaults to the default registry of Class". `affine_registry` was the other candidate and is worse - it predicates "affine" of the registry, when it is the class that has the affinity, and "affine" reads as affine geometry in a library whose flagship example dispatches on matrix types. The concept is adjusted to match the name. Every class now *has* a registry affinity: a declared one if it declares `boost_openmethod_registry`, the default affinity otherwise. A declared affinity wins over a default one, which is the same rule as before - a method may mix a class that declares an affinity with one that does not - but stated without the awkward "no affinity, which is not the same as an affinity for the default registry". It also removes a corner that framing had: a class declared explicitly to the default registry is no longer a special case, it simply has the default affinity like any other. detail::affinity_of becomes declared_affinity, and no_affinity becomes default_affinity, so the internals read the same way as the prose. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1 --- CLAUDE.md | 12 +-- .../ROOT/pages/registries_and_policies.adoc | 19 ++--- include/boost/openmethod/core.hpp | 78 +++++++++---------- include/boost/openmethod/inplace_vptr.hpp | 4 +- .../interop/boost_intrusive_ptr.hpp | 4 +- .../openmethod/interop/std_shared_ptr.hpp | 4 +- .../openmethod/interop/std_unique_ptr.hpp | 4 +- test/test_adl_registry.cpp | 6 +- test/test_adl_registry_hidden_friend.cpp | 4 +- test/test_adl_registry_inheritance.cpp | 12 +-- test/test_adl_registry_inplace.cpp | 6 +- test/test_adl_registry_scan.cpp | 8 +- test/test_adl_registry_smart_ptr.cpp | 8 +- test/test_adl_registry_static_rtti.cpp | 2 +- 14 files changed, 87 insertions(+), 84 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 68896707..8ebc81a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -488,14 +488,16 @@ rules: A class can name its registry instead of the program naming one for every class: declare `auto boost_openmethod_registry(Class*) -> Registry;`, preferably as a hidden friend. The class -then has an *affinity* for that registry, inherited by its derived classes, and -`default_registry_of` reads it back. `virtual_ptr` and the smart pointer aliases default to it, +then *declares* an affinity for that registry, inherited by its derived classes, and +`registry_affinity` reads it back. `virtual_ptr` and the smart pointer aliases default to it, and a method declared without a registry argument takes the affinity its virtual parameters agree on (`detail::method_registry`, driven by `detail::param_affinity` / `agreed_affinity`). -Having *no* affinity is not the same as an affinity for the default registry: only the former -yields, which is what lets a method mix a class that has one with a class that has none. -`detail::affinity_of` is where that mapping happens; nothing public returns an affinity. +Every class has an affinity; one that declares none has the *default* affinity. Only a *declared* +affinity constrains a method, so a method may mix a class that declares one with a class that does +not - the latter yields. `detail::declared_affinity` maps the default registry to +`detail::default_affinity` to express that; `registry_affinity` itself is a query and always +answers. Two things deliberately do **not** participate, and both are documented as such: diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index a913150f..6d763a72 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -63,8 +63,8 @@ it needs no definition: include::example$adl_registry.cpp[tag=affinity] ---- -The class then has an _affinity_ for that registry, and everything that mentions -the class finds it. A method declared without a registry argument takes the +The class then _declares_ an affinity for that registry, and everything that +mentions the class finds it. A method declared without a registry argument takes the affinity of its virtual parameters: [source,c++] @@ -85,11 +85,12 @@ class derived from it, because the derived-to-base pointer conversion makes the root's declaration viable. A declaration for a derived class is a better match, and wins. -A class that declares nothing has *no* affinity, which is not the same as an -affinity for the default registry. Only the first yields: a method may mix a -class that has an affinity with one that has none, and lands in the former's -registry. Two virtual parameters with *different* affinities are an error, as is -a registry named on the method that contradicts one of its parameters. +Every class has a registry affinity; a class that declares none has the +_default_ affinity, `BOOST_OPENMETHOD_DEFAULT_REGISTRY`. A _declared_ affinity +wins over a default one, so a method may mix a class that declares one with a +class that does not, and lands in the declared registry. Two virtual parameters +with *different* declared affinities are an error, as is a registry named on the +method that contradicts one of its parameters. A hidden friend, as above, is the spelling to prefer. The declaration must precede every use of the class in a method, a `virtual_ptr` or a class @@ -97,10 +98,10 @@ registration - declaring it later makes the program ill-formed with no diagnostic required, and compilers disagree silently about which registry the earlier use got. Being part of the class, a hidden friend cannot be late. -WARNING: An affinity does not reach +WARNING: A declared affinity does not reach xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], which still registers into `BOOST_OPENMETHOD_DEFAULT_REGISTRY` unless a registry is -listed last. Registering a class with an affinity, without naming its registry, +listed last. Registering a class that declares an affinity, without naming its registry, puts the class in one registry and its methods in another - and that shows up as a `missing_class` error at run time, not as a compile error. List the registry: `BOOST_OPENMETHOD_CLASSES(Animal, Dog, zoo_registry)`. diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index dbcecdf1..7da173b6 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -143,11 +143,11 @@ constexpr bool false_t = false; // workaround before CWG2518/P2593R1 //! //! This declaration is a catch-all that matches any argument list and returns //! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, denoting the absence of -//! customization. If an overload beats it for a given class, that class has an -//! *affinity* for the overload's return type, and that registry becomes the -//! default for every construct that mentions the class: @ref virtual_ptr and -//! the smart pointer aliases, and the methods that take it as a virtual -//! parameter. +//! customization. If an overload beats it for a given class, that class +//! *declares* an affinity for the overload's return type, and that registry +//! becomes the default for every construct that mentions the class: @ref +//! virtual_ptr and the smart pointer aliases, and the methods that take it as a +//! virtual parameter. //! //! An affinity declared for a class extends to its derived classes, because //! the derived-to-base pointer conversion makes the base's overload viable. An @@ -174,7 +174,7 @@ constexpr bool false_t = false; // workaround before CWG2518/P2593R1 //! //! include:../examples/adl_registry.cpp#affinity //! -//! @see @ref default_registry_of +//! @see @ref registry_affinity //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) auto boost_openmethod_registry(...) -> BOOST_OPENMETHOD_DEFAULT_REGISTRY; @@ -230,29 +230,29 @@ using adl_registry = } // namespace detail -//! The registry a class belongs to by default. +//! The registry a class has an affinity for. //! -//! Evaluates to the return type of the @ref boost_openmethod_registry overload -//! found for `T` by argument-dependent lookup, after removing cv-qualifiers, -//! references and pointers, and unwrapping one level of smart pointer - so -//! `Class`, `const Class&`, `Class*` and `std::shared_ptr` all yield the -//! same registry. +//! Every class has a registry affinity. A class *declares* one with a @ref +//! boost_openmethod_registry overload; one that declares none has the *default* +//! affinity, @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. //! -//! If `T`\'s class has no affinity, this is @ref -//! BOOST_OPENMETHOD_DEFAULT_REGISTRY. Note that this is a query, and always -//! answers; having *no* affinity is not the same as having an affinity for the -//! default registry, a distinction that matters when a method draws its -//! registry from its virtual parameters. +//! Evaluates to the return type of the overload found for `T` by +//! argument-dependent lookup, after removing cv-qualifiers, references and +//! pointers, and unwrapping one level of smart pointer - so `Class`, `const +//! Class&`, `Class*` and `std::shared_ptr` all yield the same registry. +//! +//! A declared affinity wins over a default one. That is what lets a method mix +//! a class that declares an affinity with one that does not: the latter yields. //! //! @tparam T A class, or a reference, pointer or smart pointer to one. //! //! @see @ref boost_openmethod_registry //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template -using default_registry_of = detail::adl_registry; +using registry_affinity = detail::adl_registry; template< - class Class, class Registry = default_registry_of, + class Class, class Registry = registry_affinity, typename = detail::sfinae> class virtual_ptr; @@ -935,7 +935,7 @@ inline auto final_virtual_ptr(Arg&& obj) { // doesn't like it. template inline auto final_virtual_ptr(Arg&& obj) { - return final_virtual_ptr, Arg>( + return final_virtual_ptr, Arg>( std::forward(obj)); } //! Wide pointer combining pointers to an object and its v-table @@ -1829,7 +1829,7 @@ class virtual_ptr< //! @return A `virtual_ptr`. template virtual_ptr(Class& obj) - -> virtual_ptr>; + -> virtual_ptr>; //! Construct a `virtual_ptr` from a xvalue reference. //! @@ -1838,7 +1838,7 @@ virtual_ptr(Class& obj) //! @return A `virtual_ptr`. template virtual_ptr(Class&& obj) - -> virtual_ptr>; + -> virtual_ptr>; // Alas this is not allowed: // template @@ -2174,57 +2174,57 @@ struct validate_method_parameter< //! namespace detail { -// A class that never declared `boost_openmethod_registry` has no affinity for -// any registry, which is not the same as an affinity for the default one. Only -// the former yields to a parameter that does have one, which is what lets a -// method mix a class that has an affinity with one that has none. -struct no_affinity; +// Every class has an affinity, but only a *declared* one constrains a method. +// A class that never declared `boost_openmethod_registry` has the default +// affinity, and yields to a parameter that declares one - which is what lets a +// method mix the two. +struct default_affinity; template -using affinity_of = std::conditional_t< - std::is_same_v, no_affinity, Registry>; +using declared_affinity = std::conditional_t< + std::is_same_v, default_affinity, Registry>; template struct param_affinity { - using type = no_affinity; + using type = default_affinity; }; template struct param_affinity> { - using type = affinity_of>; + using type = declared_affinity>; }; template struct param_affinity> { - using type = affinity_of; + using type = declared_affinity; }; template struct param_affinity&> { - using type = affinity_of; + using type = declared_affinity; }; template struct param_affinity&> { - using type = affinity_of; + using type = declared_affinity; }; // The first affinity in the parameter list wins; every other one must agree. template struct agreed_affinity { - using type = no_affinity; + using type = default_affinity; }; template struct agreed_affinity { using rest = typename agreed_affinity::type; static_assert( - std::is_same_v || - std::is_same_v || + std::is_same_v || + std::is_same_v || std::is_same_v, "virtual parameters have conflicting registry affinities"); using type = std:: - conditional_t, rest, Affinity>; + conditional_t, rest, Affinity>; }; // The registry a method takes when its declaration does not name one. @@ -2238,7 +2238,7 @@ struct method_registry_aux { using found = typename agreed_affinity< typename param_affinity::type...>::type; using type = std::conditional_t< - std::is_same_v, macro_default_registry, found>; + std::is_same_v, macro_default_registry, found>; }; template diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index 51b2a75b..d37d152a 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -12,12 +12,12 @@ namespace detail { void boost_openmethod_bases(...); -// `inplace_vptr_base` declares the affinity that `default_registry_of` reads +// `inplace_vptr_base` declares the affinity that `registry_affinity` reads // back, so the two are the same question. There is deliberately no catch-all // here: the one in core.hpp serves both, and a second one returning `void` // would shadow it for lookups from this namespace. template -using inplace_vptr_registry = default_registry_of; +using inplace_vptr_registry = registry_affinity; template struct update_vptr_bases; diff --git a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp index 4e7e9c57..e530c9ee 100644 --- a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp +++ b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp @@ -129,7 +129,7 @@ struct virtual_traits&, Registry> { //! include:intrusive_ptr.cpp#boost_intrusive_virtual_ptr_alias //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template> +template> using boost_intrusive_virtual_ptr = virtual_ptr, Registry>; @@ -153,7 +153,7 @@ using boost_intrusive_virtual_ptr = //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< - class Class, class Registry = default_registry_of, + class Class, class Registry = registry_affinity, typename... T> inline auto make_boost_intrusive_virtual(T&&... args) { return final_virtual_ptr(intrusive_ptr( diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index 02b35306..799c835c 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -202,7 +202,7 @@ struct virtual_traits&, Registry> { //! include:smart_pointers.cpp#shared_virtual_ptr_alias //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template> +template> using shared_virtual_ptr = virtual_ptr, Registry>; //! Create a new object and return a `shared_virtual_ptr` to it. @@ -225,7 +225,7 @@ using shared_virtual_ptr = virtual_ptr, Registry>; //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< - class Class, class Registry = default_registry_of, + class Class, class Registry = registry_affinity, typename... T> inline auto make_shared_virtual(T&&... args) { return final_virtual_ptr( diff --git a/include/boost/openmethod/interop/std_unique_ptr.hpp b/include/boost/openmethod/interop/std_unique_ptr.hpp index b99a6e52..f816a336 100644 --- a/include/boost/openmethod/interop/std_unique_ptr.hpp +++ b/include/boost/openmethod/interop/std_unique_ptr.hpp @@ -72,7 +72,7 @@ struct virtual_traits, Registry> { //! include:smart_pointers.cpp#unique_virtual_ptr_alias //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template> +template> using unique_virtual_ptr = virtual_ptr, Registry>; //! Create a new object and return a `unique_virtual_ptr` to it. @@ -95,7 +95,7 @@ using unique_virtual_ptr = virtual_ptr, Registry>; //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< - class Class, class Registry = default_registry_of, + class Class, class Registry = registry_affinity, typename... T> inline auto make_unique_virtual(T&&... args) { return final_virtual_ptr( diff --git a/test/test_adl_registry.cpp b/test/test_adl_registry.cpp index 36861404..874b0186 100644 --- a/test/test_adl_registry.cpp +++ b/test/test_adl_registry.cpp @@ -39,9 +39,9 @@ struct Widget { using zoo::Animal, zoo::Dog, zoo::Cat; -static_assert(std::is_same_v, zoo_registry>); -static_assert(std::is_same_v, zoo_registry>); -static_assert(std::is_same_v, default_registry>); +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, default_registry>); // `virtual_ptr` picks it up, so `virtual_ptr` is not a `virtual_ptr` in // the default registry. diff --git a/test/test_adl_registry_hidden_friend.cpp b/test/test_adl_registry_hidden_friend.cpp index 0efb6858..a7971f57 100644 --- a/test/test_adl_registry_hidden_friend.cpp +++ b/test/test_adl_registry_hidden_friend.cpp @@ -33,8 +33,8 @@ class Dog : public Animal {}; } // namespace zoo -static_assert(std::is_same_v, zoo_registry>); -static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); // The method lives in a third namespace, and still finds the affinity. namespace vet { diff --git a/test/test_adl_registry_inheritance.cpp b/test/test_adl_registry_inheritance.cpp index 1e57f7f0..5212b33d 100644 --- a/test/test_adl_registry_inheritance.cpp +++ b/test/test_adl_registry_inheritance.cpp @@ -44,22 +44,22 @@ struct Chimera : Left, Right {}; using namespace zoo; // inherited, however deep -static_assert(std::is_same_v, zoo_registry>); -static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); // the exact match wins, and is itself inherited -static_assert(std::is_same_v, kennel_registry>); -static_assert(std::is_same_v, kennel_registry>); +static_assert(std::is_same_v, kennel_registry>); +static_assert(std::is_same_v, kennel_registry>); // one Animal, so one affinity -static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); // An unrelated class is untouched by any of it. struct Widget { virtual ~Widget() = default; }; -static_assert(std::is_same_v, default_registry>); +static_assert(std::is_same_v, default_registry>); BOOST_AUTO_TEST_CASE(inheritance_is_compile_time_only) { BOOST_TEST(true); diff --git a/test/test_adl_registry_inplace.cpp b/test/test_adl_registry_inplace.cpp index 98281244..0436237e 100644 --- a/test/test_adl_registry_inplace.cpp +++ b/test/test_adl_registry_inplace.cpp @@ -27,9 +27,9 @@ struct Animal : bom::inplace_vptr_base {}; struct Dog : Animal, bom::inplace_vptr_derived {}; struct Cat : Animal, bom::inplace_vptr_derived {}; -// The mixin's hidden friend is what `default_registry_of` reads back. -static_assert(std::is_same_v, zoo_registry>); -static_assert(std::is_same_v, zoo_registry>); +// The mixin's hidden friend is what `registry_affinity` reads back. +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); // No registry named, and no #define: the method follows the class. BOOST_OPENMETHOD(speak, (bom::virtual_), std::string); diff --git a/test/test_adl_registry_scan.cpp b/test/test_adl_registry_scan.cpp index 8e865c37..4f7e3789 100644 --- a/test/test_adl_registry_scan.cpp +++ b/test/test_adl_registry_scan.cpp @@ -4,9 +4,9 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) // How a method picks its registry: it takes the affinity its virtual -// parameters agree on. A class with no affinity contributes nothing, so it -// yields rather than conflicting. A registry named on the declaration wins -// outright, and no scan happens. +// parameters agree on. Only a *declared* affinity constrains it, so a class +// with the default affinity yields rather than conflicting. A registry named on +// the declaration wins outright, and no scan happens. #include @@ -56,7 +56,7 @@ static_assert( std::is_same_v)>, default_registry>); static_assert(std::is_same_v, default_registry>); -// Mixing a class that has an affinity with one that has none: the one without +// Mixing a class that declares an affinity with one that does not: the latter // yields. This is what keeps a first affinity from cascading errors. static_assert(std::is_same_v< scan, virtual_)>, diff --git a/test/test_adl_registry_smart_ptr.cpp b/test/test_adl_registry_smart_ptr.cpp index 7d66262a..c668abdd 100644 --- a/test/test_adl_registry_smart_ptr.cpp +++ b/test/test_adl_registry_smart_ptr.cpp @@ -40,13 +40,13 @@ using zoo::Animal, zoo::Dog; // the anchor is the pointee, whatever the wrapper static_assert( - std::is_same_v>, zoo_registry>); + std::is_same_v>, zoo_registry>); static_assert( - std::is_same_v>, zoo_registry>); + std::is_same_v>, zoo_registry>); static_assert(std::is_same_v< - default_registry_of>, zoo_registry>); + registry_affinity>, zoo_registry>); static_assert(std::is_same_v< - default_registry_of&>, zoo_registry>); + registry_affinity&>, zoo_registry>); // so the alias and the type it stands for are the same type static_assert(std::is_same_v< diff --git a/test/test_adl_registry_static_rtti.cpp b/test/test_adl_registry_static_rtti.cpp index e4f7a136..de29dfe2 100644 --- a/test/test_adl_registry_static_rtti.cpp +++ b/test/test_adl_registry_static_rtti.cpp @@ -34,7 +34,7 @@ struct Circle : Shape {}; using shapes::Shape, shapes::Square, shapes::Circle; -static_assert(std::is_same_v, static_registry>); +static_assert(std::is_same_v, static_registry>); static_assert( std::is_same_v, virtual_ptr>); From 55b444629a0996d4b44c78bc4e2e71a3f46041a7 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Wed, 2 Sep 2026 09:50:37 -0400 Subject: [PATCH 4/4] chore: conform to clang-format 22 develop adopted clang-format 22 in 6b02978; reflow the files this branch touches to match, so the diff carries no formatting noise. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QoMTqq3duJAptRAbCXgNh1 --- doc/modules/ROOT/examples/adl_registry.cpp | 6 +++--- include/boost/openmethod/core.hpp | 18 ++++++++---------- .../openmethod/interop/boost_intrusive_ptr.hpp | 4 +--- .../openmethod/interop/std_shared_ptr.hpp | 4 +--- .../openmethod/interop/std_unique_ptr.hpp | 4 +--- test/test_adl_registry.cpp | 11 ++++++----- test/test_adl_registry_inplace.cpp | 6 +++--- test/test_adl_registry_scan.cpp | 13 +++++++------ test/test_adl_registry_smart_ptr.cpp | 4 ++-- test/test_adl_registry_two_registries.cpp | 14 +++++++------- 10 files changed, 39 insertions(+), 45 deletions(-) diff --git a/doc/modules/ROOT/examples/adl_registry.cpp b/doc/modules/ROOT/examples/adl_registry.cpp index 03d6bcc1..feb3a888 100644 --- a/doc/modules/ROOT/examples/adl_registry.cpp +++ b/doc/modules/ROOT/examples/adl_registry.cpp @@ -12,9 +12,9 @@ #include // tag::registry[] -struct zoo_registry - : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks> {}; +struct zoo_registry : + boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks> {}; // end::registry[] using namespace boost::openmethod; diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 7da173b6..6be984af 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -224,9 +224,8 @@ using unadorned = std::remove_cv_t< std::remove_pointer_t>>>; template -using adl_registry = - typename adl_registry_aux>::type>>::type; +using adl_registry = typename adl_registry_aux< + unadorned>::type>>::type; } // namespace detail @@ -1828,8 +1827,7 @@ class virtual_ptr< //! @param obj A lvalue reference to an object. //! @return A `virtual_ptr`. template -virtual_ptr(Class& obj) - -> virtual_ptr>; +virtual_ptr(Class& obj) -> virtual_ptr>; //! Construct a `virtual_ptr` from a xvalue reference. //! @@ -1837,8 +1835,7 @@ virtual_ptr(Class& obj) //! @param obj A xvalue reference to an object. //! @return A `virtual_ptr`. template -virtual_ptr(Class&& obj) - -> virtual_ptr>; +virtual_ptr(Class&& obj) -> virtual_ptr>; // Alas this is not allowed: // template @@ -2182,7 +2179,8 @@ struct default_affinity; template using declared_affinity = std::conditional_t< - std::is_same_v, default_affinity, Registry>; + std::is_same_v, default_affinity, + Registry>; template struct param_affinity { @@ -2223,8 +2221,8 @@ struct agreed_affinity { std::is_same_v || std::is_same_v, "virtual parameters have conflicting registry affinities"); - using type = std:: - conditional_t, rest, Affinity>; + using type = std::conditional_t< + std::is_same_v, rest, Affinity>; }; // The registry a method takes when its declaration does not name one. diff --git a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp index e530c9ee..16dd5b91 100644 --- a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp +++ b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp @@ -152,9 +152,7 @@ using boost_intrusive_virtual_ptr = //! include:intrusive_ptr.cpp#make_boost_intrusive_virtual //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template< - class Class, class Registry = registry_affinity, - typename... T> +template, typename... T> inline auto make_boost_intrusive_virtual(T&&... args) { return final_virtual_ptr(intrusive_ptr( new std::remove_cv_t(std::forward(args)...))); diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index 799c835c..4c9b67cb 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -224,9 +224,7 @@ using shared_virtual_ptr = virtual_ptr, Registry>; //! include:smart_pointers.cpp#make_shared_virtual //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template< - class Class, class Registry = registry_affinity, - typename... T> +template, typename... T> inline auto make_shared_virtual(T&&... args) { return final_virtual_ptr( std::make_shared(std::forward(args)...)); diff --git a/include/boost/openmethod/interop/std_unique_ptr.hpp b/include/boost/openmethod/interop/std_unique_ptr.hpp index f816a336..30f21571 100644 --- a/include/boost/openmethod/interop/std_unique_ptr.hpp +++ b/include/boost/openmethod/interop/std_unique_ptr.hpp @@ -94,9 +94,7 @@ using unique_virtual_ptr = virtual_ptr, Registry>; //! include:smart_pointers.cpp#make_unique_virtual //! //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template< - class Class, class Registry = registry_affinity, - typename... T> +template, typename... T> inline auto make_unique_virtual(T&&... args) { return final_virtual_ptr( std::make_unique(std::forward(args)...)); diff --git a/test/test_adl_registry.cpp b/test/test_adl_registry.cpp index 874b0186..edcef792 100644 --- a/test/test_adl_registry.cpp +++ b/test/test_adl_registry.cpp @@ -55,11 +55,12 @@ BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, zoo_registry); BOOST_OPENMETHOD(speak, (virtual_), std::string); BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); -static_assert(std::is_same_v< - BOOST_OPENMETHOD_TYPE(speak, (virtual_), std::string), - method< - BOOST_OPENMETHOD_ID(speak), std::string(virtual_), - zoo_registry>>); +static_assert( + std::is_same_v< + BOOST_OPENMETHOD_TYPE(speak, (virtual_), std::string), + method< + BOOST_OPENMETHOD_ID(speak), std::string(virtual_), + zoo_registry>>); BOOST_OPENMETHOD_OVERRIDE(speak, (const Dog&), std::string) { return "bark"; diff --git a/test/test_adl_registry_inplace.cpp b/test/test_adl_registry_inplace.cpp index 0436237e..1c6b4d92 100644 --- a/test/test_adl_registry_inplace.cpp +++ b/test/test_adl_registry_inplace.cpp @@ -19,9 +19,9 @@ namespace bom = boost::openmethod; // An inplace_vptr hierarchy needs neither a vptr policy nor a type hash. -struct zoo_registry - : bom::default_registry::without { -}; +struct zoo_registry : + bom::default_registry::without< + bom::policies::vptr, bom::policies::type_hash> {}; struct Animal : bom::inplace_vptr_base {}; struct Dog : Animal, bom::inplace_vptr_derived {}; diff --git a/test/test_adl_registry_scan.cpp b/test/test_adl_registry_scan.cpp index 4f7e3789..e8e764e8 100644 --- a/test/test_adl_registry_scan.cpp +++ b/test/test_adl_registry_scan.cpp @@ -42,7 +42,8 @@ template using scan = detail::method_registry; // One virtual parameter with an affinity, in either shape. -static_assert(std::is_same_v)>, zoo_registry>); +static_assert( + std::is_same_v)>, zoo_registry>); static_assert(std::is_same_v)>, zoo_registry>); static_assert(std::is_same_v&)>, zoo_registry>); static_assert( @@ -66,9 +67,9 @@ static_assert(std::is_same_v< zoo_registry>); // Mixed shapes agreeing. -static_assert(std::is_same_v< - scan, virtual_)>, - zoo_registry>); +static_assert( + std::is_same_v< + scan, virtual_)>, zoo_registry>); // Non-virtual parameters are ignored. static_assert(std::is_same_v< @@ -82,8 +83,8 @@ static_assert(std::is_same_v< BOOST_OPENMETHOD_TYPE( ping, (virtual_), std::string, other_registry), method< - BOOST_OPENMETHOD_ID(ping), std::string(virtual_), - other_registry>>); + BOOST_OPENMETHOD_ID(ping), + std::string(virtual_), other_registry>>); BOOST_AUTO_TEST_CASE(scan_is_compile_time_only) { BOOST_TEST(true); diff --git a/test/test_adl_registry_smart_ptr.cpp b/test/test_adl_registry_smart_ptr.cpp index c668abdd..2137433f 100644 --- a/test/test_adl_registry_smart_ptr.cpp +++ b/test/test_adl_registry_smart_ptr.cpp @@ -43,8 +43,8 @@ static_assert( std::is_same_v>, zoo_registry>); static_assert( std::is_same_v>, zoo_registry>); -static_assert(std::is_same_v< - registry_affinity>, zoo_registry>); +static_assert( + std::is_same_v>, zoo_registry>); static_assert(std::is_same_v< registry_affinity&>, zoo_registry>); diff --git a/test/test_adl_registry_two_registries.cpp b/test/test_adl_registry_two_registries.cpp index d14ce80f..66ffd276 100644 --- a/test/test_adl_registry_two_registries.cpp +++ b/test/test_adl_registry_two_registries.cpp @@ -55,13 +55,13 @@ static_assert(std::is_same_v< BOOST_OPENMETHOD_ID(describe), std::string(virtual_), zoo_registry>>); -static_assert(std::is_same_v< - BOOST_OPENMETHOD_TYPE( - inspect, (virtual_), std::string), - method< - BOOST_OPENMETHOD_ID(inspect), - std::string(virtual_), - garage_registry>>); +static_assert( + std::is_same_v< + BOOST_OPENMETHOD_TYPE( + inspect, (virtual_), std::string), + method< + BOOST_OPENMETHOD_ID(inspect), + std::string(virtual_), garage_registry>>); BOOST_OPENMETHOD_OVERRIDE(describe, (const zoo::Dog&), std::string) { return "a dog";