diff --git a/CLAUDE.md b/CLAUDE.md index 6013f039..24bbe7a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -490,25 +490,42 @@ rules: ### 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 *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`). - -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. The catch-all `boost_openmethod_registry(...)` returns the sentinel -`detail::default_affinity`, not a registry, so an affinity declared for the default registry -itself still counts as declared; `detail::registry_affinity_aux::declared` is the raw answer -(sentinel or registry) and `registry_affinity` the query, which always answers a registry. A -`virtual_ptr` parameter contributes its *class's* affinity, never the registry it spells; a -spelled registry must agree with the class (`validate_method_parameter`, all three shapes). - -Two spellings, looked up in this order: a member typedef `boost_openmethod_registry` -(`detail::member_registry_aux` - ordinary member lookup, so inherited, hidden by a derived class's -own, ambiguous between two bases) and the ADL overload. `inplace_vptr_base` provides the typedef. +A class can name its registry instead of the program naming one for every class. Two spellings, +looked up in this order: a member typedef `boost_openmethod_registry` +(`detail::member_affinity` - ordinary member lookup, so inherited, hidden by a derived class's +own, ambiguous between two bases that disagree) and an ADL overload +`auto boost_openmethod_registry(Class*) -> Registry;`, preferably a hidden friend. +`inplace_vptr_base` provides the typedef. The class 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. + +**The sentinel is `void`.** The catch-all `boost_openmethod_registry(...)` returns it, so an +affinity declared for the default registry itself still counts as declared. +`detail::registry_affinity_aux::declared` is the raw answer (registry or `void`), and +`registry_affinity` is the query, which maps `void` to `BOOST_OPENMETHOD_DEFAULT_REGISTRY` and +so always answers a registry. `using boost_openmethod_registry = void;` therefore means "declares +nothing". `void` rather than a dedicated struct because the sentinel appears in every +`virtual_` of every `method<...>`, and mangled names are long enough already. + +**Every virtual parameter carries a registry, or adopts.** `virtual_` takes a registry parameter +of its own - declared in `preamble.hpp`, defaulted in `core.hpp` where the affinity machinery +exists, since C++ merges default template arguments across declarations: + +| | carries | +|---|---| +| `virtual_` | its class's declared affinity; **adopts** if the class declares none | +| `virtual_` | `S` | +| `virtual_ptr` | `C`'s affinity, else the macro default | +| `virtual_ptr` | `S` | + +`virtual_ptr` never adopts - it is a type in its own right and names a registry whether or not the +class declares an affinity. `virtual_` can, because it appears only in a method signature, and +that is what lets a method mix a class that has an affinity with one that has not. +`detail::param_registry` says what a parameter carries - **what it carries, never its class's +affinity**: a spelled `virtual_ptr` decides the method's registry even though `B` +declares nothing. `detail::agreed_registry` folds them, adopters abstaining. A method that names a +registry requires every carrier to carry that one (`validate_method_parameter`, all four shapes); +one that names none takes what the carriers agree on, or the macro default. **The answer is memoized, so every question carries a `Question` tag.** A class mentioned before it is complete - `virtual_ptr` as a member of `Node`, or through a forward declaration - is @@ -526,13 +543,19 @@ Anchoring goes through `virtual_traits` (`detail::virtual_type&` contributes no - affinity, so a method over one behaves exactly as before. +**`use_classes` follows the affinities too, and is stricter than a method**, because a class list +has no parameter to adopt from and no spelling of its own to disambiguate with. A registry listed +last wins - a class declaring another is an error, one declaring none goes along. Without one, the +classes must be unanimous: all declaring the same registry, or none declaring one (then the macro +default). Mixing a declaring class with a non-declaring one is an error, where the same mixture +among a method's parameters is fine. `detail::class_list_registry` decides; `unanimous_registry` +is the strict fold, deliberately *not* `agreed_registry`. + +One thing deliberately does **not** participate, and is documented as such: the `any` and +`type_erasure` interop headers are untouched, and `virtual_any&` contributes no affinity, so +a method over one behaves exactly as before. The C++26 `register_classes` also still defaults to +the macro - its groups may name a namespace, whose classes are only known during the scan that the +choice of registry feeds. **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 diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index e6e1b858..7620cc89 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -122,13 +122,37 @@ registry, as it always has. Declaring one too late is an error - not at the mention, where nothing is wrong yet, but where the class is complete and its affinity matters: a virtual parameter of a method, or a class registration. -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 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)`. +A virtual parameter either _carries_ a registry or _adopts_ the method's. A +`virtual_ptr` always carries one: it is a type in its own right, and names a +registry whether or not its class declares an affinity. A `virtual_` carries +the registry its class declares, and adopts when the class declares none - +which is +what lets a method mix a class that has an affinity with one that has not. +Either can be spelled: `virtual_` and +`virtual_ptr` carry `zoo_registry` whatever `Animal` +declares. + +A method that names a registry requires every parameter that carries one to +carry that one; the parameters that adopt go along. A method that names none +requires the carriers to agree, and takes their registry - or +`BOOST_OPENMETHOD_DEFAULT_REGISTRY` if no parameter carries one. + +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] follows +the affinities too, and is stricter, because a class list has no parameter to +adopt from and no spelling of its own to disambiguate with. Listing a registry +settles it, and then a class that declares nothing goes along while one that +declares another registry is an error. Listing none, the classes must be +unanimous: all declaring the same registry, or none declaring one - in which +case they are registered into `BOOST_OPENMETHOD_DEFAULT_REGISTRY`. Mixing a +class that declares an affinity with one that does not is an error, where the +same mixture in a method's parameter list is fine. + +NOTE: The C++26 registrar +xref:reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc[BOOST_OPENMETHOD_REGISTER_CLASSES] +does not follow affinities: it registers into +`BOOST_OPENMETHOD_DEFAULT_REGISTRY` unless its groups name a registry. Its +groups may name a namespace, whose classes are only known during the scan that +the choice of registry feeds. 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 diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index ee21b262..685d0d57 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -144,11 +144,6 @@ constexpr bool false_t = false; // workaround before CWG2518/P2593R1 namespace detail { -// What the catch-all below returns: the absence of a declaration. Not a -// registry, so that an affinity declared for the default registry itself is -// still a *declared* one, and constrains a method like any other. -struct default_affinity {}; - template struct registry_affinity_aux; @@ -202,7 +197,7 @@ struct registry_affinity_aux; //! //! @see @ref registry_affinity //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) -auto boost_openmethod_registry(...) -> detail::default_affinity; +auto boost_openmethod_registry(...) -> void; //! The registry a class has an affinity for. //! @@ -232,6 +227,18 @@ auto boost_openmethod_registry(...) -> detail::default_affinity; template using registry_affinity = typename detail::registry_affinity_aux::type; +//! @see @ref virtual_ for documentation. +//! +//! The default for `Registry` is supplied here rather than on the declaration +//! in `preamble.hpp`, which comes before the affinity machinery; C++ merges +//! default template arguments across declarations. `::declared` is the +//! registry the class declares, or `void` when it declares none - which is +//! exactly "carries a registry, or leaves the choice to the method". +template< + typename T, + class Registry = typename detail::registry_affinity_aux::declared> +struct virtual_; + template< class Class, class Registry = registry_affinity, typename = detail::sfinae> @@ -245,16 +252,17 @@ namespace detail { template struct extract_registry; +// `registry` is the registry the list *names*, or `void` when it names none - +// which `class_list_registry` below then works out from the classes. template<> struct extract_registry<> { - using registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY; + using registry = void; using others = mp11::mp_list<>; }; template struct extract_registry { - using registry = std::conditional_t< - is_registry, Type, BOOST_OPENMETHOD_DEFAULT_REGISTRY>; + using registry = std::conditional_t, Type, void>; using others = std::conditional_t< is_registry, mp11::mp_list<>, mp11::mp_list>; }; @@ -267,6 +275,75 @@ struct extract_registry { typename extract_registry::others, Type1>; }; +// The registry a class list belongs to when it names none: the one every class +// declares an affinity for. Unanimity, not the give-and-take a method's +// parameters get - a `virtual_` parameter over a class that declares nothing +// adopts the method's registry, but a class list has no other parameter to +// adopt from, and no spelling of its own to disambiguate with. Mixing a class +// that declares an affinity with one that does not is therefore an error, and +// the program says which registry it means by listing it. +template +struct first_or_void { + using type = void; +}; + +template +struct first_or_void> { + using type = First; +}; + +template +struct unanimous_registry { + using declared = mp11::mp_unique< + mp11::mp_list::declared...>>; + + static_assert( + mp11::mp_size::value <= 1, + "the classes carry conflicting registry affinities - list the registry " + "to say which one is meant"); + + using found = typename first_or_void::type; + using type = std::conditional_t< + std::is_same_v, macro_default_registry, found>; +}; + +// A registry listed explicitly wins, and a class that declares nothing goes +// along with it; one that declares another registry does not. +template +struct classes_agree_with { + static_assert( + ((std::is_same_v< + typename registry_affinity_aux::declared, void> || + std::is_same_v< + typename registry_affinity_aux::declared, Registry>) && + ...), + "registry mismatch: a class declares an affinity for another registry"); + static constexpr bool value = true; +}; + +template +struct pick_class_registry; + +template +struct pick_class_registry< + Named, mp11::mp_list, + std::enable_if_t>> { + static_assert(classes_agree_with::value); + using type = Named; +}; + +template +struct pick_class_registry< + Named, mp11::mp_list, + std::enable_if_t>> { + using type = typename unanimous_registry::type; +}; + +template +using class_list_registry = typename pick_class_registry< + typename extract_registry::registry, + typename extract_registry::others>::type; + template struct init_type_ids; @@ -332,16 +409,16 @@ auto optimal_cast(B&& obj) -> decltype(auto) { template struct is_virtual : std::false_type {}; -template -struct is_virtual> : std::true_type {}; +template +struct is_virtual> : std::true_type {}; template struct remove_virtual_aux { using type = T; }; -template -struct remove_virtual_aux> { +template +struct remove_virtual_aux> { using type = T; }; @@ -386,7 +463,7 @@ struct rechecked; // a class mention `virtual_ptr` of itself. template struct member_affinity { - using type = default_affinity; + using type = void; }; template @@ -411,7 +488,7 @@ struct adl_affinity { "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 = default_affinity; + using type = void; }; template @@ -422,15 +499,14 @@ struct adl_affinity< std::declval()))>; }; -// What the class says: a registry, or `default_affinity` when it says nothing. +// What the class says: a registry, or `void` when it says nothing. template struct declared_affinity_aux { // conditional_t picks the struct, so the ADL path is not instantiated // when the typedef answers: its ambiguity diagnosis would fire for a class // whose typedef settles what two base classes dispute. using type = typename std::conditional_t< - std::is_same_v< - typename member_affinity::type, default_affinity>, + std::is_same_v::type, void>, adl_affinity, member_affinity>::type; }; @@ -465,12 +541,11 @@ struct registry_affinity_aux { using declared = declared_affinity; static_assert( - std::is_same_v || is_registry, + std::is_same_v || is_registry, "boost_openmethod_registry must return a registry"); using type = std::conditional_t< - std::is_same_v, macro_default_registry, - declared>; + std::is_same_v, macro_default_registry, declared>; }; // The answer above is remembered for the rest of the translation unit, and a @@ -519,8 +594,8 @@ struct StripVirtualDecorator { //! Provides a nested `type` equal to `T`. //! //! @tparam T A type. -template -struct StripVirtualDecorator> { +template +struct StripVirtualDecorator> { //! Same as `T`. using type = T; }; @@ -698,8 +773,7 @@ using use_classes_tuple_type = boost::mp11::mp_apply< detail::tuple, boost::mp11::mp_transform_q< boost::mp11::mp_bind_front< - detail::use_class_aux, - typename detail::extract_registry::registry>, + detail::use_class_aux, detail::class_list_registry>, boost::mp11::mp_apply< detail::inheritance_map, boost::mp11::mp_unique< @@ -728,6 +802,16 @@ using use_classes_tuple_type = boost::mp11::mp_apply< //! Virtual and multiple inheritance are supported, with the exclusion of //! repeated inheritance. //! +//! The registry is the one listed last, if the last argument is a registry. In +//! that case a class that declares an affinity for another registry is an +//! error, while a class that declares none goes along. Without a registry in +//! the list, the classes decide, and must be unanimous: all declaring the same +//! registry, or none declaring one, in which case they are registered into +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. Mixing a class that declares an +//! affinity with one that does not is an error - unlike a method's parameter +//! list, where a `virtual_` over a class that declares none adopts the +//! registry the other parameters carry. See @ref registry_affinity. +//! //! @see [Core API](xref:ROOT:core_api.adoc) //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template @@ -2093,8 +2177,9 @@ struct select_overrider_virtual_type_aux { using type = void; }; -template -struct select_overrider_virtual_type_aux, Q, Registry> { +template +struct select_overrider_virtual_type_aux< + virtual_, Q, Registry> { using type = virtual_type; }; @@ -2173,8 +2258,9 @@ struct parameter_traits { } }; -template -struct parameter_traits, Registry> : virtual_traits {}; +template +struct parameter_traits, Registry> : + virtual_traits {}; template struct parameter_traits, Registry> : @@ -2187,14 +2273,15 @@ struct parameter_traits&, Registry> : template struct validate_method_parameter : std::true_type {}; -template -struct validate_method_parameter, Registry, U> : std::false_type { +template +struct validate_method_parameter, Registry, U> : + std::false_type { static_assert(false_t, "virtual_traits not specialized for type"); }; -template +template struct validate_method_parameter< - virtual_, Registry, + virtual_, Registry, std::void_t::virtual_type>> : std::bool_constant< has_vptr_fn, Registry> || @@ -2208,14 +2295,14 @@ struct validate_method_parameter< // the checkpoints where its affinity is asked again. static_assert(check_affinity>::value); - // And a method that names a registry may not contradict it. + // `ParamRegistry` is what the parameter carries: the registry spelled on + // it, or the one its class declares an affinity for, or `void` when the + // class declares none - in which case the parameter adopts the method's. + // A carrier must agree with the method. static_assert( - std::is_same_v< - typename registry_affinity_aux::declared, default_affinity> || - std::is_same_v< - typename registry_affinity_aux::declared, Registry>, - "registry mismatch: the class declares an affinity for another " - "registry"); + std::is_same_v || + std::is_same_v, + "registry mismatch: the parameter belongs to another registry"); }; // A `virtual_ptr` parameter, in any of its three shapes, must name the @@ -2265,53 +2352,51 @@ struct validate_method_parameter< namespace detail { -// 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. A `virtual_ptr` parameter contributes its class's -// affinity, not the registry it names: the class decides, and a registry -// spelled on the parameter has to agree with it (validate_method_parameter). +// What a virtual parameter carries: a registry, or `void` when it leaves the +// choice to the method. `virtual_ptr` always carries one - it is a type of its +// own, and names a registry whether or not the class declares an affinity. +// `virtual_` carries what its class declares, and adopts when the class +// declares nothing, which is what lets a method mix the two. template -struct param_affinity { - using type = default_affinity; +struct param_registry { + using type = void; }; -template -struct param_affinity> { - using type = typename registry_affinity_aux::declared; +template +struct param_registry> { + using type = Registry; }; template -struct param_affinity> { - using type = typename registry_affinity_aux::declared; +struct param_registry> { + using type = Registry; }; template -struct param_affinity&> { - using type = typename registry_affinity_aux::declared; +struct param_registry&> { + using type = Registry; }; template -struct param_affinity&> { - using type = typename registry_affinity_aux::declared; +struct param_registry&> { + using type = Registry; }; -// The first affinity in the parameter list wins; every other one must agree. +// The carriers must agree; the parameters that adopt do not vote. template -struct agreed_affinity { - using type = default_affinity; +struct agreed_registry { + using type = void; }; -template -struct agreed_affinity { - using rest = typename agreed_affinity::type; +template +struct agreed_registry { + using rest = typename agreed_registry::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< - std::is_same_v, rest, Affinity>; + std::is_same_v || std::is_same_v || + std::is_same_v, + "virtual parameters carry conflicting registries"); + using type = + std::conditional_t, rest, Carried>; }; // The registry a method takes when its declaration does not name one. @@ -2322,10 +2407,10 @@ struct method_registry_aux { template struct method_registry_aux { - using found = typename agreed_affinity< - typename param_affinity::type...>::type; + using found = typename agreed_registry< + typename param_registry::type...>::type; using type = std::conditional_t< - std::is_same_v, macro_default_registry, found>; + std::is_same_v, macro_default_registry, found>; }; template @@ -2985,11 +3070,12 @@ struct validate_overrider_parameter< template struct validate_overrider_parameter : std::true_type {}; -template -struct validate_overrider_parameter, T2, void> : std::true_type {}; +template +struct validate_overrider_parameter, T2, void> : + std::true_type {}; -template -struct validate_overrider_parameter, virtual_, void> : +template +struct validate_overrider_parameter, virtual_, void> : std::false_type { static_assert(false_t, "virtual_<> is not allowed in overriders"); }; diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 887c7b9e..4e2401be 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -590,10 +590,12 @@ inline constexpr bool method_not_found = false; //! This macro is a wrapper around @ref boost::openmethod::use_classes; see its //! documentation for more details. //! -//! @note Unlike a method declaration, this macro does not consult the classes' -//! registry affinities: without a registry in the list it registers into -//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, whatever the classes declare. List -//! the registry last when they declare one. The symbol is read when +//! @note Without a registry in the list, the classes decide: they must be +//! unanimous - all declaring an affinity for the same registry, or none +//! declaring one, in which case they are registered into +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. A registry listed last wins, and +//! then a class that declares another one is an error. See +//! @ref boost::openmethod::registry_affinity. The macro symbol is read when //! `` is included, directly or through a header //! like ``; subsequently changing it has no retroactive //! effect. diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 1d31450c..5624d42e 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -81,9 +81,14 @@ using type_id = const void*; //! - @ref virtual_traits must be specialized for `T`. //! //! @tparam T A class. +//! @tparam Registry The registry the parameter belongs to. Defaults to the +//! registry `T`\'s class declares an affinity for - see +//! @ref boost::openmethod::registry_affinity - and, when it declares none, to +//! `void`, which leaves the choice to the method. The default is supplied in +//! ``, where the affinity is known. //! //! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) -template +template struct virtual_; template diff --git a/test/compile_fail_adl_registry_conflicting_affinities.cpp b/test/compile_fail_adl_registry_conflicting_affinities.cpp index 49ccba8c..84248853 100644 --- a/test/compile_fail_adl_registry_conflicting_affinities.cpp +++ b/test/compile_fail_adl_registry_conflicting_affinities.cpp @@ -4,7 +4,7 @@ // 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 +// expected-error: carry conflicting registries #include diff --git a/test/compile_fail_adl_registry_parameter_registry.cpp b/test/compile_fail_adl_registry_parameter_registry.cpp deleted file mode 100644 index 96dc6e03..00000000 --- a/test/compile_fail_adl_registry_parameter_registry.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// 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 - -using namespace boost::openmethod; - -struct other_registry : default_registry {}; - -struct Cat { - virtual ~Cat() = default; -}; - -// A registry spelled on a parameter is not the class's affinity. Cat declares -// none, so the method lands in the default registry, and the parameter then -// contradicts it - as it did before affinities existed. The method has to name -// its registry: `BOOST_OPENMETHOD(poke, (virtual_ptr), -// void, other_registry)`. -BOOST_OPENMETHOD(poke, (virtual_ptr), void); - -int main() { - // See compile_fail_adl_registry_declared_mismatch.cpp for why the call. - Cat felix; - poke(felix); - - return 0; -} diff --git a/test/compile_fail_adl_registry_pinned_default.cpp b/test/compile_fail_adl_registry_pinned_default.cpp index 7c74e49d..18188066 100644 --- a/test/compile_fail_adl_registry_pinned_default.cpp +++ b/test/compile_fail_adl_registry_pinned_default.cpp @@ -4,7 +4,7 @@ // 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 +// expected-error: carry conflicting registries #include @@ -18,8 +18,8 @@ struct Animal { }; // An affinity declared for the default registry is a declared affinity all the -// same: Widget does not yield to Animal the way a class that declares nothing -// would. +// same: the parameter carries `default_registry` rather than adopting, the way +// one over a class that declares nothing would. struct Widget { virtual ~Widget() = default; friend auto boost_openmethod_registry(Widget*) -> default_registry; diff --git a/test/compile_fail_classes_declared_mismatch.cpp b/test/compile_fail_classes_declared_mismatch.cpp new file mode 100644 index 00000000..b3783039 --- /dev/null +++ b/test/compile_fail_classes_declared_mismatch.cpp @@ -0,0 +1,26 @@ +// 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 + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; + using boost_openmethod_registry = zoo_registry; +}; + +// A registry listed explicitly wins, but not over a class that declares +// another one: that is a contradiction, not a choice. +BOOST_OPENMETHOD_CLASSES(Animal, default_registry); + +int main() { + return 0; +} diff --git a/test/compile_fail_classes_mixed_affinities.cpp b/test/compile_fail_classes_mixed_affinities.cpp new file mode 100644 index 00000000..c21cb93a --- /dev/null +++ b/test/compile_fail_classes_mixed_affinities.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: carry conflicting registry affinities + +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; + using boost_openmethod_registry = zoo_registry; +}; + +struct Widget { + virtual ~Widget() = default; +}; + +// Animal belongs to zoo_registry, Widget declares nothing and so belongs to +// the default one. A class list registers into a single registry and has no +// way to tell which is meant - unlike a method, where a `virtual_` parameter +// over an undeclared class adopts the registry the others agree on. List it. +BOOST_OPENMETHOD_CLASSES(Animal, Widget); + +int main() { + return 0; +} diff --git a/test/compile_fail_method_conflicting_carriers.cpp b/test/compile_fail_method_conflicting_carriers.cpp new file mode 100644 index 00000000..dbe2eb85 --- /dev/null +++ b/test/compile_fail_method_conflicting_carriers.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: carry conflicting registries + +#include + +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Widget { + virtual ~Widget() = default; +}; + +// One parameter carries zoo_registry, the other carries the default registry - +// `virtual_ptr` always carries one, since it is a type in its own right. A +// method lives in one registry; naming it on the declaration settles this. +BOOST_OPENMETHOD( + poke, (virtual_, virtual_ptr), + std::string); + +int main() { + return 0; +} diff --git a/test/compile_fail_virtual_registry_mismatch.cpp b/test/compile_fail_virtual_registry_mismatch.cpp new file mode 100644 index 00000000..6205bc28 --- /dev/null +++ b/test/compile_fail_virtual_registry_mismatch.cpp @@ -0,0 +1,35 @@ +// 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 Animal { + virtual ~Animal() = default; +}; + +// The parameter carries zoo_registry - spelled on it, since Animal declares +// nothing - and the method says another. A parameter that carries a registry +// must agree with its method; only one that adopts, `virtual_`, +// would go along. +BOOST_OPENMETHOD( + speak, (virtual_), std::string, + default_registry); + +int main() { + // See compile_fail_adl_registry_declared_mismatch.cpp for why the call. + Animal animal; + speak(animal); + + return 0; +} diff --git a/test/test_adl_registry.cpp b/test/test_adl_registry.cpp index c764c627..28da4755 100644 --- a/test/test_adl_registry.cpp +++ b/test/test_adl_registry.cpp @@ -109,7 +109,33 @@ static_assert(std::is_same_v, virtual_ptr>); static_assert( std::is_same_v, virtual_ptr>); -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, zoo_registry); +// The list names no registry: the classes agree on `zoo_registry`, so that is +// where they are registered. Listing it would say the same thing. +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +namespace { + +template +using picked = detail::class_list_registry; + +// Every class declares the same registry - directly, or by inheriting the +// declaration. +static_assert(std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, zoo_registry>); + +// None declares one. +static_assert(std::is_same_v, default_registry>); +static_assert(std::is_same_v, default_registry>); + +// A registry listed explicitly wins, and a class that declares nothing goes +// along with it. A class that declares another does not - see +// compile_fail_classes_declared_mismatch.cpp - and neither does a list that +// mixes the two, see compile_fail_classes_mixed_affinities.cpp. +static_assert( + std::is_same_v, zoo_registry>); +static_assert(std::is_same_v, kennel_registry>); + +} // namespace // Neither declaration names a registry; both land in `zoo_registry`. BOOST_OPENMETHOD(speak, (virtual_), std::string); diff --git a/test/test_adl_registry_scan.cpp b/test/test_adl_registry_scan.cpp index c2630d42..8779c425 100644 --- a/test/test_adl_registry_scan.cpp +++ b/test/test_adl_registry_scan.cpp @@ -75,13 +75,65 @@ static_assert( static_assert(std::is_same_v< scan, char*)>, zoo_registry>); -// A registry spelled on a `virtual_ptr` parameter is not the class's affinity: -// Widget declares none, so the method lands in the default registry - where -// the parameter then contradicts it, see -// compile_fail_adl_registry_parameter_registry.cpp. +// A registry spelled on a parameter is what that parameter carries, whatever +// its class declares - so it decides the method's registry on its own. +static_assert(std::is_same_v< + scan)>, other_registry>); static_assert( std::is_same_v< - scan)>, default_registry>); + scan)>, other_registry>); +static_assert(std::is_same_v< + scan)>, + default_registry>); + +// Two carriers that disagree are an error; see +// compile_fail_method_conflicting_carriers.cpp. + +// The whole rule, in one place: every virtual parameter either *carries* a +// registry - the one spelled on it, or the one its class declares - or +// *adopts*, which only a `virtual_` over a class that declares nothing does. +// `virtual_ptr` never adopts: it is a type of its own, and names a registry +// whether or not the class declares an affinity. + +// Carriers. +static_assert( + std::is_same_v)>, zoo_registry>); +static_assert(std::is_same_v)>, zoo_registry>); +static_assert(std::is_same_v< + scan)>, zoo_registry>); +static_assert(std::is_same_v< + scan)>, zoo_registry>); +static_assert( + std::is_same_v)>, default_registry>); + +// The only adopter, alone: nothing carries, so the macro default. +static_assert( + std::is_same_v)>, default_registry>); + +// An adopter and a carrier: the carrier decides, whichever order. +static_assert(std::is_same_v< + scan, virtual_ptr)>, + zoo_registry>); +static_assert(std::is_same_v< + scan, virtual_)>, + zoo_registry>); +static_assert(std::is_same_v< + scan, virtual_ptr)>, + default_registry>); + +// Two carriers that agree, in either shape. +static_assert( + std::is_same_v< + scan, virtual_ptr)>, zoo_registry>); +static_assert( + std::is_same_v< + scan, virtual_)>, + zoo_registry>); + +// Two adopters: still the macro default. +static_assert(std::is_same_v< + scan, virtual_)>, + default_registry>); // An affinity declared for the default registry itself is declared all the // same: it constrains, see compile_fail_adl_registry_pinned_default.cpp. @@ -93,9 +145,13 @@ struct Pinned { static_assert( std::is_same_v< detail::registry_affinity_aux::declared, default_registry>); + +// A class that declares nothing carries nothing: `void`, the sentinel that +// makes a `virtual_` parameter adopt the method's registry. +static_assert( + std::is_same_v::declared, void>); static_assert(std::is_same_v< - detail::registry_affinity_aux::declared, - detail::default_affinity>); + detail::param_registry>::type, void>); // A registry named on the declaration wins, and the parameters are not // consulted at all - the form that predates this feature.