diff --git a/CLAUDE.md b/CLAUDE.md index ea80ae0c..b1abf55d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -590,8 +590,10 @@ that is what lets a method mix a class that has an affinity with one that has no `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. +registry accepts any carrier - a parameter carrying another registry dispatches in it, see *Mixed +registries* below; one that names none takes what the carriers agree on, or the macro default, and +carriers that disagree are an error there ("carry conflicting registries"), which is what forces +a mixed method to name its registry. **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 @@ -620,11 +622,11 @@ is the strict fold, deliberately *not* `agreed_registry`. Two things declare no affinity - but declaring none is not the same as contributing none, and the interop shapes differ from one another. What each does, with the method on another registry: -| parameter spelling `R` | decides an unannotated method's registry | checked against a method naming another | +| parameter spelling `R` | decides an unannotated method's registry | with a method naming another | |---|---|---| -| `virtual_` (ordinary carrier) | yes | yes - `core.hpp`, "the parameter belongs to another registry" | -| `virtual_` (interop) | yes | **no - nothing checks it** (#123) | -| `virtual_any&` | no, abstains | yes - `virtual_any.hpp`, "registry mismatch" | +| `virtual_` (ordinary carrier) | yes | dispatches in `R` (mixed registries) | +| `virtual_` (interop) | yes | dispatches in `R` - probed with `std::any`, **untested** | +| `virtual_any&` | no, abstains | error - `virtual_any.hpp`, "registry mismatch" | - `interop/std_any.hpp`, `interop/boost_any.hpp`, `interop/boost_type_erasure.hpp` and `interop/virtual_any.hpp` declare no affinity for any class, so a class reached through them @@ -632,9 +634,13 @@ interop shapes differ from one another. What each does, with the method on anoth different thing from declaring an affinity, and the reason #116 gave the `validate_method_parameter` specializations the registry parameter `virtual_` gained in #113. A specialization there must never go back to the bare `virtual_` spelling. Those - specializations accept any `ParamRegistry` without ever comparing it, which is the middle row - above and a bug rather than a decision (#123); `virtual_any`'s are a different shape - (`virtual_any&`) and do compare. None of this is covered in `doc/`. + specializations accept any `ParamRegistry` without comparing it. Before mixed registries that + was a bug (#123): the spelled registry was ignored. Now `parameter_traits` routes the parameter + through `virtual_traits`, so the held types register in `R` and dispatch + there - a mixed `std::any` parameter works in a probe, but no test covers it and the other + interop headers were not tried. `virtual_any`'s specializations are a different shape + (`virtual_any&`) and still require the method's registry. None of this is covered + in `doc/`. - The C++26 `register_classes` 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. @@ -650,6 +656,88 @@ carries the override on the file's behalf (`test_capture_errors.hpp` and `test_checked_registry.hpp`). Add another such header and the scan has to learn about it: miss one and the file still compiles, binds to `default_registry`, and fails at run time. +### Mixed registries + +A virtual parameter dispatches in `detail::dispatch_registry` - what it carries, or +the method's registry `R` when it carries nothing - and everything per-parameter goes through that +registry: `parameter_traits`, `method::vptr` (the declared parameter is the template argument, not +`remove_virtual_<>` of it), the type ids (`init_type_ids`, one rtti +per position), `init_bad_call` (one rtti per argument). "Another registry" means another *state*: +`detail::same_registry` compares `registry_type`, so `struct zoo : default_registry {}` is the +default registry under a second name, and a parameter carrying it is native. + +**Type ids never cross registries.** Two registries may give the same id to different classes, or +use rtti policies whose ids are not comparable at all (std_rtti's `type_index` on a `static_rtti` +id is UB). So a foreign parameter is described to the method's registry by *position*, never by id: + +- The method owns one `detail::foreign_parameter_info` per foreign parameter + (`method::foreign_parameters`, a `std::array`), pushed onto + `registry_state_type::foreign_parameters` of the parameter's registry S by the method's + constructor. `method_info::foreign_begin/end` point at them. +- `S::initialize()` (`augment_foreign_parameters`) maps the method's and overriders' ids for that + parameter through its own `class_map` - `missing_class` and `missing_base` are raised there, + by S - gives the parameter a slot like any other (`generic_compiler::parameter::slot` points at + it), leaves the entries empty (`vtbl_entry::method_index == no_method`, written as 0), and + publishes at commit: the slot, the cone of the parameter's class (the class first, then + `classes` order, so every module's copy of the method gets the same layout), each class's entry + address in the new dispatch data, the transitive-derived sets as cone positions, and each + overrider's class as a cone position. +- `R::initialize()` builds proxy `class_`es for the cone (`method::foreign_classes`, empty `ci`, + `is_foreign()`), so `build_dispatch_tables`, `is_more_specific` and the inline-overrider dedup + (which now compares `class_*` vectors, not ids) run unchanged. The entries for the proxies are + collected in `foreign_writes` and written in `commit_global_data`, like everything else. + **`foreign_class_of(method, param, position)` indexes from `foreign_first[param]`, a fixed + base** - an earlier draft indexed from the iterator the loop advanced, and dispatched the wrong + overriders without crashing. +- `registry_state_type::generation` counts S's commits (and `finalize`s). A record remembers the + generation it was published at (`generation`) and the one R built from (`installed_generation`). + R's `initialize` refuses a record that is unpublished or stale; under `runtime_checks`, + `method::check_foreign_parameters` refuses a call once S has been initialized again. Both raise + `parameter_registry_not_initialized`. Consequence: S before R, R again after S, and a cycle + (an R method with an S parameter, an S method with an R parameter) cannot be initialized. +- Each module's copy of a method registers its own record. S groups them into one + `foreign_parameter` with several `copies`, sharing a slot, by the key (`method_state`, method + type id, `param`). The method's state address is one symbol per registry; the type ids are + compared by `same_method`, which the method template builds from *its* registry's + `rtti::type_index` - S cannot compare them itself, and a raw comparison would fail in exactly + the multi-module case (one `type_info` per module). Each copy still gets its own overrider + positions, since its overrider list is its own. `copies_share_the_slot` in + `test_mixed_registries.cpp` fakes a second copy. +- **Each parameter's type ids follow the deferral of the registry it dispatches in**, never the + method's. `method::resolve_type_ids()` (R's, at construction or in R's `initialize()`) sets the + method's own ids and the *native* positions only (`NativeParameters`). A foreign position whose + registry does not defer is set at construction (`EagerForeignParameters`); one whose registry + defers is set by S's `initialize()`, through `foreign_parameter_info::resolve_vp` for the method + and `overrider_info::resolve_vp` for each overrider - function pointers built by the templates, + since S knows neither type. Neither registry calls the other's rtti during static construction. + The slot-sharing key calls `method_type()` rather than reading `method_type_id`, which a + deferring R has not resolved yet when S initializes; the `deferred_static_rtti` contract only + promises ids from the first `initialize()` on, which S's is. `test_mixed_registries_deferred.cpp` + assigns its ids at the start of the test, so an early read yields 0 and fails. +- The C++26 scan (`method_traits_aux`) registers only the classes of the parameters that dispatch + in the method's registry (`detail::dispatches_in`). +- `rebind_parameter_registry` still rebinds every `virtual_ptr` to the method's registry, so an + overrider that spells a third registry on a foreign parameter gets "cannot find method" rather + than "registry mismatch". + +**The trace follows the handshake across both registries** - `trace()` on each `initialize()` +shows it end to end, and the entry addresses match between the two halves. S prints the overrider +classes of each foreign parameter, says which parameter an entry it leaves empty belongs to rather +than `empty`, and ends with `Publishing to methods of other registries` (generation, slot, number +of module copies, the cone with each class and entry address, the overrider positions). R prints, +under the method, what it received per foreign parameter, names the cone classes `foreign#k` +(`foreign#0` is the parameter's class) wherever a `class_` is printed, and ends with +`Entries in the v-tables of other registries, written at commit`. **A registry with no `output` +policy cannot be traced at all** (`Registry::output::stream()` is `void`) - that is existing +behaviour, but it bites here, because a custom-rtti registry written for an example often has no +`output`. + +Tests: `test_mixed_registries.cpp` (two number-based rtti policies that collide on purpose, plus +std_rtti; foreign first, second and only parameter; `next`; errors; re-initialization; copies +sharing a slot), `test_mixed_registries_affinity.cpp` (the affinity and spelling shapes that used +to be "registry mismatch" compile-fail tests) and `test_mixed_registries_deferred.cpp` (deferred +and eager registries, both ways round). + ### Flattened headers for Compiler Explorer `dev/flatten.py` rewrites every public header into a self-sufficient file under `flat/`; the @@ -707,7 +795,9 @@ Registries are completely independent. Use separate registries to: - Apply different policies to different method families - Enable coexistence of incompatible configurations -Registry type must be specified consistently across related methods and classes. +Registry type must be specified consistently across related methods and classes. A method's +virtual parameters may dispatch in other registries, with other rtti policies - see *Mixed +registries*. ## File Organization diff --git a/doc/modules/ROOT/examples/mixed_registries.cpp b/doc/modules/ROOT/examples/mixed_registries.cpp new file mode 100644 index 00000000..8b6db982 --- /dev/null +++ b/doc/modules/ROOT/examples/mixed_registries.cpp @@ -0,0 +1,171 @@ +// 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 +#include +#include +#include + +#include +#include + +#define BOOST_TEST_MODULE mixed_registries +#include + +using namespace boost::openmethod; + +// tag::nodes[] +struct Node { + explicit Node(unsigned type) : type(type) { + } + + unsigned type; + static constexpr unsigned static_type = 1; +}; + +struct Number : Node { + static constexpr unsigned static_type = 2; + + explicit Number(int value) : Node(static_type), value(value) { + } + + int value; +}; + +struct Plus : Node { + static constexpr unsigned static_type = 3; + + Plus(const Node& left, const Node& right) : + Node(static_type), left(left), right(right) { + } + + const Node& left; + const Node& right; +}; + +auto next_non_node_type_id() -> type_id { + static auto next = std::numeric_limits::max(); + + return reinterpret_cast(next--); +} + +auto non_node_type_index(type_id type) -> std::size_t { + return std::numeric_limits::max() - + reinterpret_cast(type) + 1; +} + +struct node_rtti : policies::rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = std::is_base_of_v; + + template + static auto static_type() -> type_id { + if constexpr (is_polymorphic) { + return reinterpret_cast(T::static_type); + } else { + static const auto id = next_non_node_type_id(); + + return id; + } + } + + template + static auto dynamic_type(const T& obj) -> type_id { + if constexpr (is_polymorphic) { + return reinterpret_cast(obj.type); + } else { + return nullptr; + } + } + + template + static void type_name(type_id type, Stream& stream) { + static const char* const names[] = {"Node", "Number", "Plus"}; + auto id = reinterpret_cast(type); + + if (id >= 1 && id <= 3) { + stream << names[id - 1]; + } else { + stream << "<" << non_node_type_index(type) << ">"; + } + } + }; +}; + +struct node_registry : registry {}; + +BOOST_OPENMETHOD_CLASSES(Node, Number, Plus, node_registry); +// end::nodes[] + +// tag::value[] +BOOST_OPENMETHOD(value, (virtual_), int, node_registry); + +BOOST_OPENMETHOD_OVERRIDE(value, (const Number& number), int) { + return number.value; +} + +BOOST_OPENMETHOD_OVERRIDE(value, (const Plus& plus), int) { + return value(plus.left) + value(plus.right); +} +// end::value[] + +// tag::formats[] +struct Format { + virtual ~Format() = default; +}; + +struct Postfix : Format {}; +struct Infix : Format {}; + +BOOST_OPENMETHOD_CLASSES(Format, Postfix, Infix); +// end::formats[] + +// tag::method[] +BOOST_OPENMETHOD( + render, (virtual_, virtual_), + std::string, default_registry); + +BOOST_OPENMETHOD_OVERRIDE( + render, (const Number& number, const Format&), std::string) { + return std::to_string(number.value); +} + +BOOST_OPENMETHOD_OVERRIDE( + render, (const Plus& plus, const Postfix& format), std::string) { + return render(plus.left, format) + " " + render(plus.right, format) + " +"; +} + +BOOST_OPENMETHOD_OVERRIDE( + render, (const Plus& plus, const Infix& format), std::string) { + return "(" + render(plus.left, format) + " + " + + render(plus.right, format) + ")"; +} +// end::method[] + +BOOST_AUTO_TEST_CASE(mixed_registries) { + // tag::initialize[] + initialize(); + initialize(); + // end::initialize[] + + std::ostringstream captured; + auto* cout_buf = std::cout.rdbuf(captured.rdbuf()); + + // tag::call[] + Number one(1), two(2), three(3); + Plus sum(one, two), total(sum, three); + + std::cout << render(total, Postfix()) << " = " << value(total) << "\n"; + std::cout << render(total, Infix()) << " = " << value(total) << "\n"; + // end::call[] + + std::cout.rdbuf(cout_buf); + + BOOST_TEST(captured.str() == "1 2 + 3 + = 6\n((1 + 2) + 3) = 6\n"); +} diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index 534863a8..7183ab90 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -1,9 +1,11 @@ [#registries_and_policies] -Methods are scoped in a registry. A method can only reference classes in the -same registry. If a class is used as a virtual parameter in methods using -different registries, it must be registered with each of them. +Methods are scoped in a registry. A virtual parameter dispatches in the +method's registry, unless it carries another one - see <> +below - and its classes must be registered in the registry it dispatches in. If +a class is used as a virtual parameter in methods using different registries, +it must be registered with each of them. Class templates cpp:use_classes[], cpp:method[], cpp:virtual_ptr[], and macros xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] and @@ -92,10 +94,10 @@ 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, whatever its shape. A -`virtual_ptr` parameter contributes the affinity of its class, not the registry -it spells; the two must agree. +with *different* declared affinities do not settle the method's registry +between themselves: the method must name one. A `virtual_ptr` parameter +contributes the affinity of its class, not the registry it spells; the two must +agree. A class can also declare its affinity with a member typedef: @@ -133,8 +135,9 @@ 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 +A method that names a registry may name any registry: a parameter that +carries another one dispatches in it (see <>), and the +parameters that adopt go along with the method. A method that names none requires the carriers to agree, and takes their registry - or `BOOST_OPENMETHOD_DEFAULT_REGISTRY` if no parameter carries one. @@ -155,6 +158,92 @@ does not follow affinities: it registers into groups may name a namespace, whose classes are only known during the scan that the choice of registry feeds. +[#mixing_registries] +### Mixing registries + +The virtual parameters of a method may belong to different registries. Each +parameter dispatches in the registry it carries, and that registry identifies +the classes with its own `rtti` policy: this is how a method dispatches on +classes that use different RTTI systems. + +In the following example, the nodes of an expression tree carry their own type +information, and use a custom RTTI policy, as described in +xref:custom_rtti.adoc[Custom RTTI]. They are registered in a registry of their +own: + +[source,c++] +---- +include::example$mixed_registries.cpp[tag=nodes] +---- + +That registry has methods of its own. `value` evaluates a node, and takes +nothing but nodes, so it is an ordinary single-registry method: + +[source,c++] +---- +include::example$mixed_registries.cpp[tag=value] +---- + +The output formats use the standard RTTI, and are registered in the default +registry: + +[source,c++] +---- +include::example$mixed_registries.cpp[tag=formats] +---- + +`render` dispatches on both. Its first parameter carries `node_registry`, +spelled on it; the second carries nothing, and adopts the method's registry: + +[source,c++] +---- +include::example$mixed_registries.cpp[tag=method] +---- + +The method must name its registry here, even though it is the default one: +without it, the method would take the registry of the only parameter that +carries one, `node_registry`, and `Format` would adopt it. The method's +registry may be any registry - it holds the method and its dispatch table, and +need not be one that its parameters carry. + +A parameter that dispatches in another registry takes a slot in the v-tables of +that registry, beside the slots of the methods that registry holds itself, and +the method's registry fills it. The parameter's registry must therefore be +initialized first: + +[source,c++] +---- +include::example$mixed_registries.cpp[tag=initialize] +---- + +Initializing the method's registry first raises a +cpp:parameter_registry_not_initialized[] error. So does calling the method +after the parameter's registry has been initialized again, if the method's +registry has the `runtime_checks` policy: the parameter's registry rebuilds its +v-tables, and the method's registry has to be initialized again as well. +Consequently, two registries cannot each have a method with a parameter that +dispatches in the other: neither can be initialized first. + +Calls work as usual - `render` dispatching in both registries at once, `value` +in `node_registry` alone: + +[source,c++] +---- +include::example$mixed_registries.cpp[tag=call] +---- + +This prints `1 2 + 3 + = 6`, then `((1 + 2) + 3) = 6`. + +The type ids of different registries cannot be compared. Two registries may +give the same id to different classes, or, with different `rtti` policies, ids +of different kinds. The type ids in a cpp:bad_call[] error come each from the +registry of its parameter, so an error handler that prints them must use that +registry's `rtti` policy for each of them. + +Registries whose `rtti` policy derives from `deferred_static_rtti` mix with +those whose policy does not: the type ids of a parameter are resolved when its +own registry resolves them. + 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/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index e363db1a..d673a23a 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -351,18 +352,35 @@ using class_list_registry = typename pick_class_registry< typename extract_registry::registry, typename extract_registry::others>::type; -template +// Set `ids[Index]`, for each `Index` in `Positions`, to the type_id of the class +// in that position, computed by the rtti policy of the registry in the same +// position: the virtual parameters of a method may belong to different +// registries, and their type ids may be resolved at different times. +template struct init_type_ids; -template -struct init_type_ids> { - static auto fn(type_id* ids) { - (..., (*ids++ = Registry::rtti::template static_type())); - - return ids; +template +struct init_type_ids> { + // `Positions` is empty for a method whose virtual parameters all belong to + // other registries, and are all resolved elsewhere. + static void fn([[maybe_unused]] type_id* ids) { + (..., + (ids[Index::value] = + mp11::mp_at::rtti::template static_type< + mp11::mp_at>())); } }; +// The same, for one position, known at run time. +template +void init_type_id(type_id* ids, std::size_t position) { + mp11::mp_with_index::value>( + position, [ids](auto index) { + ids[index] = mp11::mp_at::rtti:: + template static_type>(); + }); +} + template struct is_unambiguous_accessible_base_of : std::is_base_of { static_assert( @@ -414,6 +432,12 @@ auto optimal_cast(B&& obj) -> decltype(auto) { // ============================================================================= // Common details +// The registry a virtual parameter dispatches in: the one it carries, or the +// method's when it carries none (`void`). +template +using registry_or = + std::conditional_t, Registry, Carried>; + template struct is_virtual : std::false_type {}; @@ -2226,22 +2250,22 @@ struct select_overrider_virtual_type_aux { template struct select_overrider_virtual_type_aux< virtual_, Q, Registry> { - using type = virtual_type; + using type = virtual_type>; }; -template +template struct select_overrider_virtual_type_aux< - virtual_ptr, virtual_ptr, Registry> { + virtual_ptr, virtual_ptr, Registry> { using type = typename virtual_traits< - virtual_ptr, Registry>::virtual_type; + virtual_ptr, ParamRegistry>::virtual_type; }; -template +template struct select_overrider_virtual_type_aux< - const virtual_ptr&, const virtual_ptr&, + const virtual_ptr&, const virtual_ptr&, Registry> { using type = typename virtual_traits< - const virtual_ptr&, Registry>::virtual_type; + const virtual_ptr&, ParamRegistry>::virtual_type; }; template @@ -2256,35 +2280,30 @@ using overrider_virtual_types = boost::mp11::mp_remove< MethodParameters, OverriderParameters>, void>; -template +// `Rtti` identifies the method; each argument is identified by the rtti policy +// in the same position in `ArgRtti`, that of the registry its parameter +// dispatches in. +template struct init_bad_call { - template - static auto fn(bad_call& error, const Arg& arg, const Args&... args) { - if constexpr (Index == 0u) { - error.method = Rtti::template static_type(); - error.arity = sizeof...(args) + 1; - } + template + static auto fn(bad_call& error, const Args&... args) { + error.method = Rtti::template static_type(); + error.arity = sizeof...(args); + std::size_t index = 0; + (..., set_type(error, index++, args)); + } - type_id arg_type_id; + template + static auto set_type(bad_call& error, std::size_t index, const Arg& arg) { + if (index >= bad_call::max_types) { + return; + } if constexpr (is_virtual_ptr) { - arg_type_id = Rtti::dynamic_type(*arg); + error.types[index] = ArgRttiType::dynamic_type(*arg); } else { - arg_type_id = Rtti::dynamic_type(arg); + error.types[index] = ArgRttiType::dynamic_type(arg); } - - error.types[Index] = arg_type_id; - - init_bad_call::fn(error, args...); - } - - static auto fn(bad_call&) { - } -}; - -template -struct init_bad_call { - static auto fn(bad_call&) { } }; @@ -2304,17 +2323,21 @@ struct parameter_traits { } }; +// A virtual parameter is handled by the traits of the registry it carries, +// which is not necessarily the method's. template struct parameter_traits, Registry> : - virtual_traits {}; + virtual_traits> {}; -template -struct parameter_traits, Registry> : - virtual_traits, Registry> {}; +template +struct parameter_traits, Registry> : + virtual_traits, ParamRegistry> {}; -template -struct parameter_traits&, Registry> : - virtual_traits&, Registry> {}; +template +struct parameter_traits< + const virtual_ptr&, Registry> : + virtual_traits< + const virtual_ptr&, ParamRegistry> {}; template struct validate_method_parameter : std::true_type {}; @@ -2325,13 +2348,22 @@ struct validate_method_parameter, Registry, U> : static_assert(false_t, "virtual_traits not specialized for type"); }; +// `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. The +// parameter dispatches in that registry, which need not be the method's, so +// the class is checked against it. template struct validate_method_parameter< virtual_, Registry, - std::void_t::virtual_type>> : + std::void_t>::virtual_type>> : std::bool_constant< - has_vptr_fn, Registry> || - Registry::rtti::template is_polymorphic>> { + has_vptr_fn< + virtual_type>, + registry_or> || + registry_or::rtti::template is_polymorphic< + virtual_type>>> { static_assert( validate_method_parameter::value, "virtual_<> parameter is not a polymorphic class and no " @@ -2339,60 +2371,31 @@ struct validate_method_parameter< // The class is complete here (is_polymorphic needs it), so this is one of // the checkpoints where its affinity is asked again. - static_assert(check_affinity>::value); - - // `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 || - 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 -// method's registry. The scan that picks a registry for a method that names -// none takes the class's affinity, so this is where a registry spelled on the -// parameter is held to agree with the class. -template -struct validate_method_parameter, Registry, void> : - std::true_type { - static_assert(check_affinity::value); -}; - -template -struct validate_method_parameter< - virtual_ptr, MethodRegistry, void> : std::false_type { - static_assert( - false_t, "registry mismatch"); + static_assert(check_affinity< + virtual_type>>::value); }; -template +// A `virtual_ptr` parameter, in any of its three shapes, dispatches in the +// registry it names, whether or not it is the method's. The scan that picks a +// registry for a method that names none takes the class's affinity, so this +// is where a registry spelled on the parameter is held to agree with the +// class. +template struct validate_method_parameter< - virtual_ptr&, Registry, void> : std::true_type { + virtual_ptr, Registry, void> : std::true_type { static_assert(check_affinity::value); }; -template +template struct validate_method_parameter< - virtual_ptr&, MethodRegistry, void> : std::false_type { - static_assert( - false_t, "registry mismatch"); -}; - -template -struct validate_method_parameter< - const virtual_ptr&, Registry, void> : std::true_type { + virtual_ptr&, Registry, void> : std::true_type { static_assert(check_affinity::value); }; -template +template struct validate_method_parameter< - const virtual_ptr&, MethodRegistry, void> : - std::false_type { - static_assert( - false_t, "registry mismatch"); + const virtual_ptr&, Registry, void> : std::true_type { + static_assert(check_affinity::value); }; } // namespace detail @@ -2462,6 +2465,28 @@ struct method_registry_aux { template using method_registry = typename method_registry_aux::type; +// The registry a parameter of a method in `Registry` dispatches in: the one it +// carries, or the method's. `Registry` for a non-virtual parameter. +template +using dispatch_registry = + registry_or::type, Registry>; + +// Whether two registries are the same, i.e. share their state. A registry +// derived from another without adding policies is the same registry. +template +using same_registry = std::is_same< + typename Registry::registry_type, typename Other::registry_type>; + +// Whether a parameter of a method in `Registry` dispatches in it. +template +using dispatches_in = + same_registry, Registry>; + +// The class of a virtual parameter of a method in `Registry`. +template +using parameter_class = virtual_type< + remove_virtual_, dispatch_registry>; + } // namespace detail //! Implement a method @@ -2475,17 +2500,29 @@ using method_registry = typename method_registry_aux::type; //! //! `Fn` is a function type, i.e. a type in the form `ReturnType(Parameters...)`. //! -//! `Registry` is an instantiation of class template @ref registry. Methods may -//! use only classes that have been registered in the same registry as virtual -//! parameters and arguments. The registry also contains a set of policies that -//! influence several aspects of the dispatch mechanism - for example, how to -//! acquire a v-table pointer for an object, how to report errors, whether to +//! `Registry` is an instantiation of class template @ref registry. It holds the +//! method and its dispatch data. The registry also contains a set of policies +//! that influence several aspects of the dispatch mechanism - for example, how +//! to acquire a v-table pointer for an object, how to report errors, whether to //! perform sanity checks, etc. //! -//! `Registry` defaults to the registry the virtual parameters of `Fn` have an -//! affinity for - see @ref registry_affinity - and to -//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when none of them declares one. -//! Parameters that declare different registries are an error. +//! Each virtual parameter dispatches in a registry: the one it carries, spelled +//! on it or declared by its class (see @ref registry_affinity), or `Registry` +//! if it carries none. The classes of a virtual parameter, and the arguments +//! passed for it, must be registered in that registry, which identifies them +//! with its own `rtti` policy. This is how a method dispatches on classes +//! that use different RTTI systems. +//! +//! `Registry` defaults to the registry the virtual parameters of `Fn` carry, +//! and to @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when none of them carries +//! one. Parameters that carry different registries are an error, unless +//! `Registry` is specified; it may then be any registry. +//! +//! A registry in which a parameter of a method of another registry dispatches +//! must be initialized before the method's registry, and the method's registry +//! must be initialized again whenever that registry is. Each parameter's type +//! ids follow the registry it dispatches in: a registry with +//! @ref policies::deferred_static_rtti may be mixed with one without. //! //! Specializations of `method` have a single instance: the static member `fn`, //! which has an `operator()` that forwards to the appropriate overrider. It is @@ -2542,8 +2579,8 @@ using method_registry = typename method_registry_aux::type; //! @tparam Id A type //! @tparam Fn A function type //! @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. +//! the registry that `Fn`\'s virtual parameters carry, and to +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY if none of them carries one. //! //! @see [Core API](xref:ROOT:core_api.adoc) template> @@ -2586,6 +2623,12 @@ class method : boost::mp11::mp_transform; using VirtualParameters = typename detail::virtual_types; + // The registry each virtual parameter dispatches in, and its class. + using VirtualRegistries = mp11::mp_transform_q< + mp11::mp_bind_back, + mp11::mp_filter>; + using VirtualClasses = mp11::mp_transform< + detail::virtual_type, VirtualParameters, VirtualRegistries>; using Signature = auto(Parameters...) -> ReturnType; using FunctionPointer = auto (*)(detail::remove_virtual_...) -> ReturnType; @@ -2724,7 +2767,33 @@ class method : detail::validate_method_parameter::value && ...)); static_assert(Arity > 0, "method has no virtual parameters"); + // The virtual parameters that dispatch in another registry, by position. + template + using is_foreign_parameter = mp11::mp_not< + detail::same_registry, Registry>>; + using ForeignParameters = + mp11::mp_copy_if, is_foreign_parameter>; + static constexpr auto ForeignCount = + mp11::mp_size::value; + + // A parameter's type ids follow the deferral of the registry it dispatches + // in. Those of the parameters in the method's registry are resolved with + // the method's own, by `resolve_type_ids`; those of a foreign parameter, + // at construction, or - if its registry defers them - by that registry's + // initialize(), through `resolve_type_id`. + template + using is_deferred_parameter = mp11::mp_bool< + mp11::mp_at::has_deferred_static_rtti>; + using NativeParameters = + mp11::mp_remove_if, is_foreign_parameter>; + using EagerForeignParameters = + mp11::mp_remove_if; + + static auto method_type() -> type_id; + static void resolve_type_id(type_id* ids, std::size_t param); + type_id vp_type_ids[Arity]; + std::array foreign_parameters; std::size_t slots_strides[2 * Arity - 1]; // Slots followed by strides. No stride for first virtual argument. @@ -2737,9 +2806,11 @@ class method : void resolve_type_ids(); - template + template auto vptr(const ArgType& arg) const -> vptr_type; + void check_foreign_parameters() const; + template auto resolve_uni(const ArgType& arg, const MoreArgTypes&... more_args) const -> detail::word; @@ -2795,6 +2866,7 @@ class method : detail::overrider_info> { explicit override_impl(FunctionPointer* next = nullptr); void resolve_type_ids(); + static void resolve_type_id(type_id* ids, std::size_t param); static type_id vp_type_ids[Arity]; }; @@ -2851,33 +2923,74 @@ method::method() { resolve_type_ids(); } + detail::init_type_ids< + VirtualClasses, VirtualRegistries, + EagerForeignParameters>::fn(vp_type_ids); + this->vp_begin = vp_type_ids; this->vp_end = vp_type_ids + Arity; this->not_implemented = reinterpret_cast(fn_not_implemented); this->ambiguous = reinterpret_cast(fn_ambiguous); + this->foreign_begin = foreign_parameters.data(); + this->foreign_end = foreign_parameters.data() + ForeignCount; // zero-initalized static variable // coverity[uninit_use] Registry::static_::st.methods.push_back(*this); + + // Each foreign parameter goes to the registry it dispatches in. + mp11::mp_for_each>([this](auto index) { + using Param = mp11::mp_at; + using ParamRegistry = mp11::mp_at; + auto& param = foreign_parameters[index]; + param.method = this; + param.param = Param::value; + param.host_generation = &ParamRegistry::static_::st.generation; + param.method_state = &Registry::static_::st; + param.same_method = [](type_id a, type_id b) { + return Registry::rtti::type_index(a) == + Registry::rtti::type_index(b); + }; + param.method_type = method_type; + param.resolve_vp = resolve_type_id; + ParamRegistry::static_::st.foreign_parameters.push_back(param); + }); } template< typename Id, typename... Parameters, typename ReturnType, class Registry> void method::resolve_type_ids() { using namespace detail; - this->method_type_id = rtti::template static_type(); + this->method_type_id = method_type(); this->return_type_id = rtti::template static_type>(); - init_type_ids< - Registry, - mp11::mp_transform_q< - mp11::mp_bind_back, - VirtualParameters>>::fn(this->vp_type_ids); + init_type_ids::fn( + this->vp_type_ids); +} + +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +auto method::method_type() -> type_id { + return rtti::template static_type(); +} + +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +void method::resolve_type_id( + type_id* ids, std::size_t param) { + detail::init_type_id(ids, param); } template< typename Id, typename... Parameters, typename ReturnType, class Registry> method::~method() { + mp11::mp_for_each>([this](auto index) { + using Param = mp11::mp_at; + using ParamRegistry = mp11::mp_at; + ParamRegistry::static_::st.foreign_parameters.remove( + foreign_parameters[index]); + }); + Registry::static_::st.methods.remove(*this); } @@ -2909,6 +3022,10 @@ BOOST_FORCEINLINE Registry::require_initialized(); + if constexpr (Registry::has_runtime_checks && ForeignCount > 0) { + check_foreign_parameters(); + } + void (*pf)(); if constexpr (Arity == 1) { @@ -2922,24 +3039,51 @@ BOOST_FORCEINLINE return reinterpret_cast(pf); } +// A foreign parameter's entries live in the v-tables of its registry, which +// clears them when it is initialized again: then the method's registry must be +// initialized again too. +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +void method::check_foreign_parameters() + const { + for (auto& param : foreign_parameters) { + if (param.installed_generation != *param.host_generation) { + if constexpr (Registry::has_error_handler) { + parameter_registry_not_initialized error; + error.method = this->method_type_id; + error.param = param.param; + Registry::error_handler::error(error); + } + + abort(); + } + } +} + +// The v-table pointer comes from the registry the parameter dispatches in. template< typename Id, typename... Parameters, typename ReturnType, class Registry> -template +template BOOST_FORCEINLINE auto method::vptr( const ArgType& arg) const -> vptr_type { - if constexpr (detail::is_virtual_ptr) { + using namespace detail; + + if constexpr (is_virtual_ptr) { return arg.vptr(); } else { - decltype(auto) obj = virtual_traits::peek(arg); - - if constexpr (detail::has_vptr_fn) { - return boost_openmethod_vptr(obj, static_cast(nullptr)); - } else if constexpr ( - detail::has_vptr< - virtual_traits, decltype(obj)>) { - return virtual_traits::vptr(obj); + using ParamRegistry = dispatch_registry; + using Traits = + virtual_traits, ParamRegistry>; + decltype(auto) obj = Traits::peek(arg); + + if constexpr (has_vptr_fn) { + return boost_openmethod_vptr( + obj, static_cast(nullptr)); + } else if constexpr (has_vptr) { + return Traits::vptr(obj); } else { - return Registry::template policy::dynamic_vptr(obj); + return ParamRegistry::template policy::dynamic_vptr( + obj); } } } @@ -2957,7 +3101,7 @@ method::resolve_uni( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr>>(arg); + vptr_type vtbl = vptr>(arg); return vtbl[this->slots_strides[0]]; } else { @@ -2977,7 +3121,7 @@ method::resolve_multi_first( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr>>(arg); + vptr_type vtbl = vptr>(arg); std::size_t slot = this->slots_strides[0]; // The first virtual parameter is special. Since its stride is @@ -3008,7 +3152,7 @@ method::resolve_multi_next( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr>>(arg); + vptr_type vtbl = vptr>(arg); std::size_t slot = this->slots_strides[VirtualArg]; std::size_t stride = this->slots_strides[Arity + VirtualArg - 1]; dispatch = dispatch + vtbl[slot].i * stride; @@ -3047,13 +3191,15 @@ template< BOOST_NORETURN auto method::fn_not_implemented( detail::remove_virtual_... args) -> ReturnType { + using namespace detail; using namespace policies; if constexpr (Registry::has_error_handler) { no_overrider error; - detail::init_bad_call::fn( - error, - detail::parameter_traits::peek(args)...); + init_bad_call< + method, rtti, + typename dispatch_registry::rtti...>:: + fn(error, parameter_traits::peek(args)...); Registry::error_handler::error(error); } @@ -3065,13 +3211,15 @@ template< BOOST_NORETURN auto method::fn_ambiguous( detail::remove_virtual_... args) -> ReturnType { + using namespace detail; using namespace policies; if constexpr (Registry::has_error_handler) { ambiguous_call error; - detail::init_bad_call::fn( - error, - detail::parameter_traits::peek(args)...); + init_bad_call< + method, rtti, + typename dispatch_registry::rtti...>:: + fn(error, parameter_traits::peek(args)...); Registry::error_handler::error(error); } @@ -3127,10 +3275,22 @@ struct validate_overrider_parameter, virtual_, void> : static_assert(false_t, "virtual_<> is not allowed in overriders"); }; +// The same `virtual_ptr` in the method and the overrider matches both the +// generic `` and the shape-specific specializations below; these are +// more specialized than either. template struct validate_overrider_parameter< virtual_ptr, virtual_ptr, void> : std::true_type {}; +template +struct validate_overrider_parameter< + const virtual_ptr&, const virtual_ptr&, void> : + std::true_type {}; + +template +struct validate_overrider_parameter< + virtual_ptr&&, virtual_ptr&&, void> : std::true_type {}; + template struct validate_overrider_parameter< virtual_ptr, virtual_ptr, void> : std::true_type { @@ -3307,10 +3467,15 @@ method::override_impl< resolve_type_ids(); } + using Thunk = thunk; + init_type_ids< + typename Thunk::OverriderVirtualParameters, VirtualRegistries, + EagerForeignParameters>::fn(vp_type_ids); + this->resolve_vp = resolve_type_id; + this->next = reinterpret_cast( p_next ? p_next : &method::next); - using Thunk = thunk; this->pf = reinterpret_cast(Thunk::fn); this->vp_begin = vp_type_ids; @@ -3330,9 +3495,21 @@ void method::override_impl< virtual_type>(); this->type = Registry::rtti::template static_type(); using Thunk = thunk; - detail::init_type_ids< - Registry, typename Thunk::OverriderVirtualParameters>::fn(this - ->vp_type_ids); + init_type_ids< + typename Thunk::OverriderVirtualParameters, VirtualRegistries, + NativeParameters>::fn(vp_type_ids); +} + +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +template +void method:: + override_impl::resolve_type_id( + type_id* ids, std::size_t param) { + using Thunk = thunk; + detail::init_type_id< + typename Thunk::OverriderVirtualParameters, VirtualRegistries>( + ids, param); } // ============================================================================= @@ -3352,10 +3529,14 @@ struct method_traits_aux> { // return type is registered when the scan finds it deriving from one of // these, or when it is listed - as in C++17 - not as a root of its own. // A root return type would make `initialize` demand a registration for - // the return type of every overrider, `std::ostringstream` included. + // the return type of every overrider, `std::ostringstream` included. Nor + // the classes of the parameters that dispatch in another registry: they + // are registered there. using type = mp11::mp_transform_q< - mp11::mp_bind_back, - virtual_types>>; + mp11::mp_bind_back, + mp11::mp_filter_q< + mp11::mp_bind_back, + mp11::mp_filter>>>; }; // Read from reflection, by `substitute`-ing a method into it and taking the diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 73aa6d64..9d4a5ac6 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -263,13 +263,22 @@ struct generic_compiler { struct method; + // A virtual parameter that takes a slot in the v-tables of this registry. struct parameter { + // Where the slot goes. + std::size_t* slot; + // The method, or null if it is in another registry. struct method* method; std::size_t param; }; + static constexpr auto no_method = (std::numeric_limits::max)(); + struct vtbl_entry { - std::size_t method_index, vp_index, group_index; + // `no_method` for an entry that this registry does not write: a hole, + // or the slot of a parameter of another registry's method, which that + // registry writes. + std::size_t method_index = no_method, vp_index = 0, group_index = 0; }; struct class_ { @@ -301,11 +310,53 @@ struct generic_compiler { return transitive_derived.find(other) != transitive_derived.end(); } + // A class of another registry, in the cone of a foreign parameter, + // has no class_info; it is known by its entry in that registry's + // v-table (see foreign_class). + detail::word* foreign_entry = nullptr; + bool foreign_abstract = false; + // The position of the class in the cone, for the trace. + std::size_t foreign_position = 0; + + auto is_foreign() const -> bool { + return ci.empty(); + } + auto is_abstract() const -> bool { - return ci[0]->is_abstract; + return is_foreign() ? foreign_abstract : ci[0]->is_abstract; } }; + // A parameter of a method of another registry, dispatched through the + // v-tables of this one. Each module may register a copy of the method, + // and with it a record of the parameter; the copies share the slot. + struct foreign_parameter { + struct copy { + detail::foreign_parameter_info* info; + // The class of each overrider of this copy, for the parameter. + std::vector overriders; + // Staged by write_global_data(), published by + // commit_global_data(). + std::vector cone; + std::vector overrider_positions; + }; + + class_* vp; + std::vector copies; + std::size_t slot = 0; + }; + + // An entry in the v-table of another registry, filled for one of this + // registry's methods. + struct foreign_write { + detail::word* entry; + // The class, in the trace. + const class_* cls; + struct method* method; + std::size_t dim, group; + detail::word value; + }; + struct slot_choice { std::size_t slot, cost; }; @@ -361,6 +412,12 @@ struct generic_compiler { overrider not_implemented; overrider ambiguous; vptr_type gv_dispatch_table = nullptr; + // For each virtual parameter, the position of its proxies in + // `foreign_classes` if it dispatches in another registry, else + // `no_method`. + std::vector foreign_first; + // Proxies for the classes of the foreign parameters' cones. + std::deque foreign_classes; auto arity() const { return vp.size(); } @@ -514,7 +571,30 @@ struct generic_compiler { return const_class_iterator(classes.end(), classes.end()); } + // The record of a method's virtual parameter, if it dispatches in another + // registry. + static auto foreign_parameter_of( + const detail::method_info& info, std::size_t param) + -> const detail::foreign_parameter_info* { + for (auto iter = info.foreign_begin; iter != info.foreign_end; ++iter) { + if (iter->param == param) { + return iter; + } + } + + return nullptr; + } + + // The proxy for the class at `position` in the cone of a foreign + // parameter. + static auto foreign_class_of( + method& m, std::size_t param, std::size_t position) -> class_* { + return &m.foreign_classes[m.foreign_first[param] + position]; + } + std::deque methods; + std::deque foreign_parameters; + std::vector foreign_writes; std::size_t class_mark = 0; bool compilation_done = false; }; @@ -625,7 +705,11 @@ template auto operator<<(trace_stream& tr, const generic_compiler::class_& cls) -> trace_stream& { if constexpr (Compiler::has_trace) { - tr << type_name(cls.ci[0]->type); + if (cls.is_foreign()) { + tr << "foreign#" << cls.foreign_position; + } else { + tr << type_name(cls.ci[0]->type); + } } return tr; @@ -634,7 +718,7 @@ auto operator<<(trace_stream& tr, const generic_compiler::class_& cls) template class Container, typename... T> auto operator<<( trace_stream& tr, - Container& classes) + [[maybe_unused]] Container& classes) -> trace_stream& { if constexpr (Compiler::has_trace) { tr << "("; @@ -770,6 +854,10 @@ auto operator<<(trace_stream& tr, const range& range) -> auto& { return write_range(tr, range, [](auto value) { return value; }); } +struct parameter_name { + const generic_compiler::parameter& param; +}; + template auto operator<<(trace_stream& tr, const type_name& manip) -> auto& { if constexpr (Compiler::has_trace) { @@ -778,6 +866,24 @@ auto operator<<(trace_stream& tr, const type_name& manip) -> auto& { return tr; } + +// The method of a parameter of another registry's method is not named: its +// type_id is meaningful only to that registry's rtti policy. +template +auto operator<<(trace_stream& tr, const parameter_name& manip) + -> auto& { + if constexpr (Compiler::has_trace) { + if (manip.param.method) { + tr << type_name(manip.param.method->infos[0]->method_type_id); + } else { + tr << "(method of another registry)"; + } + + tr << " parameter " << manip.param.param; + } + + return tr; +} } // namespace detail // Definition of the nested template struct outside the registry class @@ -803,6 +909,10 @@ struct registry::compiler : detail::generic_compiler { void calculate_transitive_bases(class_& cls); void calculate_transitive_derived(class_& cls); void augment_methods(); + void augment_foreign_parameters(); + void trace_foreign_parameters(const detail::method_info& info); + void trace_unfilled_entry( + class_& cls, std::size_t slot, const detail::word* address); void assign_slots(); void assign_tree_slots(class_& cls, std::size_t base_slot); void assign_slots(class_& cls); @@ -1204,12 +1314,33 @@ void registry::compiler::augment_methods() { (void)info; // unused when BOOST_ASSERT compiles out (NDEBUG) BOOST_ASSERT(info->arity() == first_info->arity()); } - method.vp.reserve(first_info->arity()); - method.slots.resize(first_info->arity()); + auto arity = first_info->arity(); + method.vp.reserve(arity); + method.slots.resize(arity); + method.foreign_first.assign(arity, no_method); - ++tr << type_name(first_info->method_type_id) << " " - << range{first_info->vp_begin, first_info->vp_end} << "\n"; - indent _(tr); + // A parameter that dispatches in another registry was prepared by that + // registry's initialize(), which must have run since every module's + // copy of the method registered it. + for (auto info : method.infos) { + for (auto& param : range{info->foreign_begin, info->foreign_end}) { + if (param.generation == 0 || + param.generation != *param.host_generation || + param.overriders.size() != info->overriders.size()) { + ++tr << "registry of parameter #" << param.param + << " not initialized\n"; + + if constexpr (has_error_handler) { + parameter_registry_not_initialized error; + error.method = info->method_type_id; + error.param = param.param; + error_handler::error(error); + } + + abort(); + } + } + } // Build the virtual-parameter classes once, from the local // method_info. All copies describe the same method, so any one of them @@ -1218,6 +1349,40 @@ void registry::compiler::augment_methods() { std::size_t param_index = 0; for (auto ti : range{first_info->vp_begin, first_info->vp_end}) { + if (auto param = + foreign_parameter_of(*first_info, param_index)) { + // The type_id means nothing here: the parameter's registry + // described the cone of its class, by position. + method.slots[param_index] = param->slot; + method.foreign_first[param_index] = + method.foreign_classes.size(); + + for (auto& cls : param->cone) { + auto& proxy = method.foreign_classes.emplace_back(); + proxy.foreign_position = &cls - param->cone.data(); + proxy.foreign_entry = cls.entry; + proxy.foreign_abstract = cls.is_abstract; + } + + std::size_t position = 0; + + for (auto& cls : param->cone) { + auto proxy = + foreign_class_of(method, param_index, position++); + + for (auto derived : cls.transitive_derived) { + proxy->transitive_derived.insert( + foreign_class_of(method, param_index, derived)); + } + } + + method.vp.push_back( + foreign_class_of(method, param_index, 0)); + ++param_index; + + continue; + } + auto class_ = class_map[rtti::type_index(ti)]; if (!class_) { ++tr << "unknown class " << ti << "(" << type_name(ti) @@ -1237,6 +1402,14 @@ void registry::compiler::augment_methods() { } } + ++tr << type_name(first_info->method_type_id) << " " << method.vp + << "\n"; + indent _(tr); + + if constexpr (has_trace) { + trace_foreign_parameters(*first_info); + } + if (rtti::type_index(first_info->return_type_id) != rtti::type_index(rtti::template static_type())) { auto covariant_return_iter = @@ -1273,42 +1446,69 @@ void registry::compiler::augment_methods() { // method<...>::override and method<...>::override, both // non-inline by default) are always genuinely distinct and must // remain ambiguous. - std::vector all_specs; + // + // Overriders are compared by the classes of their virtual parameters, + // not by type_id: the type_id of a parameter that dispatches in + // another registry means nothing to this one. + struct candidate { + detail::overrider_info* info; + std::vector vp; + }; + + std::vector all_specs; std::size_t module_index = 0; for (auto info : method.infos) { indent _(tr); ++tr << "module " << module_index++ << "\n"; + std::size_t spec_position = 0; for (auto& spec : info->overriders) { - auto same = [&](const detail::overrider_info* kept) { - if (!kept->inline_ || !spec.inline_) { - return false; - } + std::vector vp; + vp.reserve(arity); + std::size_t param_index = 0; + + for (auto type : range{spec.vp_begin, spec.vp_end}) { + if (auto param = foreign_parameter_of(*info, param_index)) { + vp.push_back(foreign_class_of( + method, param_index, + param->overriders[spec_position])); + ++param_index; - if (rtti::type_index(kept->type) != - rtti::type_index(spec.type)) { - return false; + continue; } - // Same function type id: confirm the virtual-parameter - // signatures match too, so two genuinely different - // overriders that happen to share a function type id are - // never merged. - auto a = kept->vp_begin, ae = kept->vp_end, - b = spec.vp_begin; + auto class_ = class_map[rtti::type_index(type)]; - for (; a != ae; ++a, ++b) { - if (rtti::type_index(*a) != rtti::type_index(*b)) { - return false; + if (!class_) { + indent _(tr); + ++tr << "unknown class error for *virtual* parameter #" + << (param_index + 1) << "\n"; + missing_class error; + error.type = type; + + if constexpr (has_error_handler) { + error_handler::error(error); } + + abort(); } - return true; + vp.push_back(class_); + ++param_index; + } + + ++spec_position; + + auto same = [&](const candidate& kept) { + return kept.info->inline_ && spec.inline_ && + rtti::type_index(kept.info->type) == + rtti::type_index(spec.type) && + kept.vp == vp; }; if (std::none_of(all_specs.begin(), all_specs.end(), same)) { - all_specs.push_back(&spec); + all_specs.push_back({&spec, std::move(vp)}); } } } @@ -1326,43 +1526,16 @@ void registry::compiler::augment_methods() { method.overriders.resize(spec_size); auto spec_iter = method.overriders.begin(); - for (auto overrider_info : all_specs) { - if constexpr (has_deferred_static_rtti) { - static_cast(*overrider_info) - .resolve_type_ids(); - } - + for (auto& spec : all_specs) { + auto overrider_info = spec.info; spec_iter->method_index = method.index; spec_iter->spec_index = spec_iter - method.overriders.begin(); ++tr << type_name(overrider_info->type) << " (" << overrider_info->pf << ")\n"; spec_iter->info = overrider_info; - spec_iter->vp.reserve(first_info->arity()); - std::size_t param_index = 0; - - for ( - auto type : - range{overrider_info->vp_begin, overrider_info->vp_end}) { - indent _(tr); - auto class_ = class_map[rtti::type_index(type)]; - - if (!class_) { - ++tr << "unknown class error for *virtual* parameter #" - << (param_index + 1) << "\n"; - missing_class error; - error.type = type; - - if constexpr (has_error_handler) { - error_handler::error(error); - } - - abort(); - } - - spec_iter->pf = spec_iter->info->pf; - spec_iter->vp.push_back(class_); - } + spec_iter->pf = overrider_info->pf; + spec_iter->vp = std::move(spec.vp); if (method.covariant_return_type) { auto covariant_return_iter = class_map.find( @@ -1391,6 +1564,14 @@ void registry::compiler::augment_methods() { std::size_t param_index = 0; for (auto vp : method.vp) { + // The parameter's registry checked the classes of a foreign + // parameter, and allocated its slot. + if (vp->is_foreign()) { + ++param_index; + + continue; + } + for (auto& overrider : method.overriders) { if (overrider.vp[param_index] == vp) { continue; @@ -1409,11 +1590,167 @@ void registry::compiler::augment_methods() { } } - vp->used_by_vp.push_back({&method, param_index++}); + vp->used_by_vp.push_back( + {&method.slots[param_index], &method, param_index}); + ++param_index; + } + } + + augment_foreign_parameters(); +} + +// The parameters of other registries' methods that dispatch through this +// registry's v-tables. Their type_ids were computed by this registry's rtti +// policy, so this is where they are mapped to classes and checked. Each gets a +// slot like a parameter of this registry's own methods, but the entries are +// left empty: the method's registry fills them. +template +template +void registry::compiler::augment_foreign_parameters() { + using namespace detail; + + if (registry::static_::st.foreign_parameters.empty()) { + return; + } + + ++tr << "Parameters of methods of other registries:\n"; + indent _(tr); + + auto find_class = [this](type_id type) { + auto iter = class_map.find(rtti::type_index(type)); + + if (iter == class_map.end()) { + ++tr << "unknown class " << type_name(type) << "\n"; + missing_class error; + error.type = type; + + if constexpr (has_error_handler) { + error_handler::error(error); + } + + abort(); + } + + return iter->second; + }; + + for (auto& info : registry::static_::st.foreign_parameters) { + // The type ids of the parameter are this registry's to resolve: the + // method, in another registry, resolved them at construction unless + // this one defers them. Only this position - the others follow the + // deferral of their own registries. + if constexpr (has_deferred_static_rtti) { + info.resolve_vp(info.method->vp_begin, info.param); + + for (auto& spec : info.method->overriders) { + spec.resolve_vp(spec.vp_begin, info.param); + } + } + + // The same parameter of another copy of the same method. + auto same = std::find_if( + foreign_parameters.begin(), foreign_parameters.end(), + [&info](const foreign_parameter& param) { + auto other = param.copies.front().info; + + return other->method_state == info.method_state && + other->param == info.param && + info.same_method(other->method_type(), info.method_type()); + }); + + auto& param = same != foreign_parameters.end() + ? *same + : foreign_parameters.emplace_back(); + auto is_new = param.copies.empty(); + auto& copy = param.copies.emplace_back(); + copy.info = &info; + + if (is_new) { + param.vp = find_class(info.method->vp_begin[info.param]); + param.vp->used_by_vp.push_back({¶m.slot, nullptr, info.param}); + } + + ++tr << "parameter " << info.param << ": " << *param.vp + << (is_new ? "\n" : " (another copy)\n"); + + for (auto& spec : info.method->overriders) { + auto cls = find_class(spec.vp_begin[info.param]); + + if (cls != param.vp && !param.vp->is_base_of(cls)) { + missing_base error; + error.base = param.vp->ci[0]->type; + error.derived = cls->ci[0]->type; + + if constexpr (has_error_handler) { + error_handler::error(error); + } + + abort(); + } + + copy.overriders.push_back(cls); + } + + { + indent _(tr); + ++tr << "overriders: " << copy.overriders << "\n"; + } + } +} + +// What the registries of a method's foreign parameters published for them. +// Their classes are known here only by position in the cone: `foreign#0` is +// the parameter's class. The registry that published them names them in its +// own trace. +template +template +void registry::compiler::trace_foreign_parameters( + const detail::method_info& info) { + using namespace detail; + + for (auto& param : range{info.foreign_begin, info.foreign_end}) { + ++tr << "parameter " << param.param + << " dispatches in another registry, published at generation " + << param.generation << ": slot " << param.slot << "\n"; + indent _(tr); + + for (auto& cls : param.cone) { + ++tr << "foreign#" << (&cls - param.cone.data()) << ": entry at " + << cls.entry << (cls.is_abstract ? ", abstract" : "") + << ", derived:"; + + auto derived = cls.transitive_derived; + std::sort(derived.begin(), derived.end()); + + for (auto position : derived) { + tr << " foreign#" << position; + } + + tr << "\n"; } } } +// An entry of this registry's v-tables that it leaves empty: a hole, or the +// slot of a parameter of another registry's method, which that registry fills. +template +template +void registry::compiler::trace_unfilled_entry( + class_& cls, std::size_t slot, const detail::word* address) { + for (auto& param : foreign_parameters) { + if (param.slot == slot && + (param.vp == &cls || param.vp->is_base_of(&cls))) { + ++tr << address << " for (method of another registry) parameter " + << param.copies.front().info->param + << ", filled by its registry\n"; + + return; + } + } + + ++tr << "empty\n"; +} + // Slot allocation. The scheme follows the remarks Steven Watanabe made on the // previous allocator, recorded in issue #19: pull the slots a class may not // take from the classes deriving from it, instead of pushing reservations up @@ -1519,10 +1856,9 @@ void registry::compiler::assign_tree_slots( auto next_slot = base_slot; for (const auto& mp : cls.used_by_vp) { - ++tr << "in " << cls << " for " - << type_name(mp.method->infos[0]->method_type_id) << " parameter " - << mp.param << ": slot " << next_slot << "\n"; - mp.method->slots[mp.param] = next_slot++; + ++tr << "in " << cls << " for " << parameter_name{mp} << ": slot " + << next_slot << "\n"; + *mp.slot = next_slot++; } // `used_slots` stays empty: the v-table is sized here, and nothing in a @@ -1557,11 +1893,10 @@ void registry::compiler::assign_slots(class_& cls) { for (const auto& mp : cls.used_by_vp) { auto choice = pick_slot(cls.transitive_derived, unavailable); - ++tr << "in " << cls << " for " - << type_name(mp.method->infos[0]->method_type_id) << " parameter " - << mp.param << ": slot " << choice.slot << ", cost " << choice.cost - << ", unavailable " << unavailable << "\n"; - mp.method->slots[mp.param] = choice.slot; + ++tr << "in " << cls << " for " << parameter_name{mp} << ": slot " + << choice.slot << ", cost " << choice.cost << ", unavailable " + << unavailable << "\n"; + *mp.slot = choice.slot; set_bit(unavailable, choice.slot); // One bit, to the whole cone: `cls` is in it. @@ -1649,7 +1984,15 @@ void registry::compiler::build_dispatch_tables() { for (auto cls : group.classes) { indent _(tr); - ++tr << type_name(cls->ci[0]->type) << "\n"; + ++tr << *cls << "\n"; + + if (cls->is_foreign()) { + // Written at commit, see write_global_data(). + foreign_writes.push_back( + {cls->foreign_entry, cls, &m, dim, group_num, {}}); + continue; + } + auto& entry = cls->vtbl[m.slots[dim] - cls->first_slot]; entry.method_index = m.index; entry.vp_index = dim; @@ -1718,7 +2061,7 @@ void registry::compiler::build_dispatch_table( << "\n"; indent _(tr); for (auto cls : range{group.classes.begin(), group.classes.end()}) { - ++tr << type_name(cls->ci[0]->type) << "\n"; + ++tr << *cls << "\n"; } } @@ -1948,6 +2291,19 @@ void registry::compiler::write_global_data() { indent _(tr); for (auto& entry : cls.vtbl) { + if (entry.method_index == no_method) { + if constexpr (has_trace) { + trace_unfilled_entry( + cls, cls.first_slot + (&entry - cls.vtbl.data()), + gv_iter); + } + + BOOST_ASSERT(gv_iter + 1 <= gv_last); + *gv_iter++ = std::size_t(0); + + continue; + } + ++tr << "method #" << entry.method_index << " "; auto& method = methods[entry.method_index]; @@ -1980,6 +2336,112 @@ void registry::compiler::write_global_data() { ++tr << rflush(4, dispatch_data_size) << " " << gv_iter << " end\n"; + // The entries of this registry's methods in other registries' v-tables. + // They are written at commit, like everything else. + if (!foreign_writes.empty()) { + ++tr << "Entries in the v-tables of other registries, written at " + "commit:\n"; + } + + for (auto& write : foreign_writes) { + indent _(tr); + auto& m = *write.method; + ++tr << write.entry << " " << *write.cls << " method #" << m.index + << " "; + + if (m.arity() == 1) { + auto spec = m.dispatch_table[write.group]; + write.value = spec->pf; + tr << "spec #" << spec->spec_index << "\n"; + indent _2(tr); + ++tr << type_name(m.infos[0]->method_type_id) << "\n"; + ++tr << spec_name(m, spec) << "\n"; + } else { + if (write.dim == 0) { + write.value = std::uintptr_t(m.gv_dispatch_table + write.group); + } else { + write.value = write.group; + } + + tr << "vp #" << write.dim << " group #" << write.group << "\n"; + indent _2(tr); + ++tr << type_name(m.infos[0]->method_type_id) << "\n"; + } + } + + // What this registry publishes for other registries' parameters: the + // cone of the parameter's class - the class first, then the others in + // `classes` order, which is the same for every module's copy of the + // method - with each class's entry in the new v-tables. The dispatch data + // is swapped into place at commit, which keeps the addresses. + for (auto& param : foreign_parameters) { + std::vector cone{param.vp}; + std::unordered_map position{{param.vp, 0}}; + + for (auto& cls : classes) { + if (&cls != param.vp && param.vp->is_base_of(&cls)) { + position.emplace(&cls, cone.size()); + cone.push_back(&cls); + } + } + + auto& first = param.copies.front(); + first.cone.reserve(cone.size()); + + for (auto cls : cone) { + auto& foreign = first.cone.emplace_back(); + // The dispatch data is ours; `vptr` is const only for the users of + // the v-tables. + foreign.entry = const_cast(cls->vptr) + param.slot; + foreign.is_abstract = cls->is_abstract(); + + for (auto derived : cls->transitive_derived) { + foreign.transitive_derived.push_back(position.at(derived)); + } + } + + // Each copy gets its own, to swap in at commit, which cannot throw. + for (auto& copy : param.copies) { + if (© != &first) { + copy.cone = first.cone; + } + + for (auto cls : copy.overriders) { + copy.overrider_positions.push_back(position.at(cls)); + } + } + + if constexpr (has_trace) { + if (¶m == &foreign_parameters.front()) { + ++tr << "Publishing to methods of other registries, " + "generation " + << (static_::st.generation + 1) << ":\n"; + } + + indent _(tr); + ++tr << "parameter " << first.info->param << ", slot " << param.slot + << ", " << param.copies.size() + << (param.copies.size() == 1 ? " copy" : " copies") + << " of the method\n"; + indent _2(tr); + + for (auto cls : cone) { + auto& foreign = first.cone[position.at(cls)]; + ++tr << "foreign#" << position.at(cls) << " is " << *cls + << ": entry at " << foreign.entry + << (foreign.is_abstract ? ", abstract" : "") << "\n"; + } + + for (auto& copy : param.copies) { + ++tr << "overriders at positions " + << range{ + copy.overrider_positions.begin(), + copy.overrider_positions.end()} + << "\n"; + } + } + } + detail::registry_state_transaction< registry, compiler, std::tuple> transaction; @@ -2045,6 +2507,34 @@ void registry::compiler::commit_global_data( } new_dispatch_data.swap(static_::st.dispatch_data); + + // After the swap: the entries below are in the new dispatch data. A + // method of another registry dispatching through the old one learns that + // its entries are gone from the generation. + auto generation = ++static_::st.generation; + + for (auto& param : foreign_parameters) { + for (auto& copy : param.copies) { + copy.info->slot = param.slot; + copy.info->cone.swap(copy.cone); + copy.info->overriders.swap(copy.overrider_positions); + copy.info->generation = generation; + } + } + + for (auto& write : foreign_writes) { + *write.entry = write.value; + } + + for (auto& m : methods) { + for (auto info : m.infos) { + for ( + auto& param : + detail::range{info->foreign_begin, info->foreign_end}) { + param.installed_generation = param.generation; + } + } + } } template @@ -2248,7 +2738,7 @@ void registry::compiler::print_slots() { for (auto cls : lattice) { for (const auto& mp : cls->used_by_vp) { - slots.push_back({mp.method->slots[mp.param], cls, mp}); + slots.push_back({*mp.slot, cls, mp}); } } @@ -2263,8 +2753,7 @@ void registry::compiler::print_slots() { for (const auto& e : slots) { ++tr << e.slot << ": in " << *e.cls << " for " - << type_name(e.mp.method->infos[0]->method_type_id) - << " parameter " << e.mp.param << "\n"; + << parameter_name{e.mp} << "\n"; } } @@ -2420,6 +2909,7 @@ auto registry::finalize(Options... opts) -> void { std::tuple options(opts...); detail::finalize_policies::fn(options); static_::st.dispatch_data.clear(); + ++static_::st.generation; static_::st.initialized = false; } diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index b9706e0c..5cc87461 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -78,8 +78,9 @@ struct default_error_handler : error_handler { using error_variant = typename error_variant_aux< void, std::variant< - not_initialized, no_overrider, ambiguous_call, missing_class, - missing_base, odr_violation, final_error>, + not_initialized, parameter_registry_not_initialized, + no_overrider, ambiguous_call, missing_class, missing_base, + odr_violation, final_error>, typename Registry::policy_list>::type; //! The type of the error handler function object. diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 8528ee82..0daad718 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -178,6 +178,34 @@ struct not_initialized : openmethod_error { } }; +//! Registry of a virtual parameter not initialized +//! +//! A method's virtual parameter carries a registry other than the method's, and +//! that registry was not initialized before the method's, or was initialized +//! again since. The parameter's registry allocates the parameter's slot in its +//! v-tables, and the method's registry fills it, so the parameter's registry +//! must be initialized first, and the method's again whenever the parameter's +//! is. +//! +//! The error is raised by the method's registry: by @ref initialize, and, if +//! runtime checks are enabled, by a call to the method. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) +struct parameter_registry_not_initialized : openmethod_error { + //! The type_id of the method, in the method's registry. + type_id method; + //! The position of the parameter among the method's virtual parameters, + //! starting from zero. + std::size_t param; + + //! Write a short description to an output stream + //! @param os The output stream + //! @tparam Registry The registry + //! @tparam Stream A @ref LightweightOutputStream + template + auto write(Stream& os) const; +}; + //! Missing class. //! //! A class used as a virtual parameter in a method, an overrider or a method @@ -273,7 +301,9 @@ struct bad_call : openmethod_error { std::size_t arity; //! The maximum size of `types` static constexpr std::size_t max_types = 16; - //! The type_ids of the arguments. + //! The type_ids of the arguments, each obtained from the `rtti` policy of + //! the registry its parameter dispatches in. They may come from different + //! registries, see @ref method. type_id types[max_types]; }; @@ -362,8 +392,12 @@ struct deferred_class_info : class_info { // method info struct overrider_info; +struct foreign_parameter_info; struct method_info : static_list::static_link { + // The type ids of the virtual parameters, each computed by the rtti policy + // of the parameter's registry - which is not the method's for a foreign + // parameter, see foreign_parameter_info. Likewise in overrider_info. type_id* vp_begin; type_id* vp_end; static_list overriders; @@ -372,12 +406,73 @@ struct method_info : static_list::static_link { type_id method_type_id; type_id return_type_id; std::size_t* slots_strides_ptr; + foreign_parameter_info* foreign_begin; + foreign_parameter_info* foreign_end; auto arity() const { return std::distance(vp_begin, vp_end); } }; +// A class in the cone of a foreign parameter, as the parameter's registry +// describes it to the method's. Classes are identified by their position in +// the cone, never by type_id: the two registries may not share an rtti policy, +// and the same type_id can then name different classes in each. +struct foreign_class { + // Where the class's v-table holds the parameter's entry. The parameter's + // registry owns the memory and leaves the entry empty; the method's + // registry writes it. + word* entry; + bool is_abstract; + // Positions in the cone of the class and of every class deriving from it. + std::vector transitive_derived; +}; + +// A virtual parameter whose registry is not the method's. The method owns one +// per such parameter, and registers it with the parameter's registry. That +// registry's initialize() maps the type_ids - which only its rtti policy can +// interpret - allocates the slot in its v-tables, and publishes the cone of +// the parameter's class. The method's registry's initialize() builds the +// dispatch data from that, and writes the entries. +struct foreign_parameter_info : + static_list::static_link { + method_info* method; + // The position of the parameter among the method's virtual parameters. + std::size_t param; + // The initialization count of the parameter's registry. + const std::size_t* host_generation; + // Identify the method across modules, each of which may register a copy + // of it: by the state of the method's registry - one symbol per registry - + // and by its type_id, compared by that registry's rtti policy, the only + // one that can. + const void* method_state; + bool (*same_method)(type_id, type_id); + // The method's type_id. A function, because the method's registry may + // defer its type ids, and not have resolved them yet when the parameter's + // registry is initialized - which is after static construction, when any + // rtti policy can answer. + type_id (*method_type)(); + // Like `overrider_info::resolve_vp`, for the method. + void (*resolve_vp)(type_id* ids, std::size_t param); + + // Published by the parameter's registry. + + // The value of `*host_generation` when these were published, 0 if never. + std::size_t generation; + std::size_t slot; + // The cone of the parameter's class; the class itself comes first. A + // registry lays it out in the same order for every copy of the method, + // one per module. + std::vector cone; + // For each overrider of `method`, in list order, the position of its class + // for this parameter in `cone`. + std::vector overriders; + + // Written by the method's registry: the `generation` its dispatch data was + // built from. + std::size_t installed_generation; +}; + struct deferred_method_info : method_info { virtual void resolve_type_ids() = 0; }; @@ -392,6 +487,11 @@ struct overrider_info : static_list::static_link { type_id type; // of the function, for trace void (**next)(); type_id *vp_begin, *vp_end; + // Set the type_id of the class of a virtual parameter in `vp_begin`, by + // the rtti policy of the registry the parameter dispatches in. Called by + // that registry's initialize() when its type ids are deferred and it is + // not the method's. + void (*resolve_vp)(type_id* ids, std::size_t param); void (*pf)(); // Set by BOOST_OPENMETHOD_INLINE_OVERRIDE (see the Inline template // parameter of override_impl/override_aux and class inline_override in @@ -1003,7 +1103,13 @@ template struct registry_state_type { static_list classes; static_list methods; + // The parameters of other registries' methods that dispatch through this + // registry's v-tables. + static_list foreign_parameters; bool initialized; + // Incremented each time the dispatch data is replaced or released, so that + // a method in another registry can tell that its entries are gone. + std::size_t generation; std::vector dispatch_data; // The per-policy `state` objects are held in a detail::tuple, whose // element types must be unique (each is a distinct base class). If two @@ -1170,16 +1276,19 @@ detail::registry_state_type registry_state::st; //! Methods, classes and policies. //! -//! Methods exist in the context of a registry. Any class used as a method or -//! overrider parameter, or in as a method call argument, must be registered -//! with the same registry. +//! Methods exist in the context of a registry. Any class used as a virtual +//! parameter of a method or an overrider, or passed as a virtual argument, must +//! be registered with the registry the parameter dispatches in: the method's, +//! unless the parameter carries another one (see @ref method). //! //! Before calling a method, its registry must be initialized with the @ref //! initialize function. This is typically done at the beginning of `main`. //! //! Multiple registries can co-exist in the same program. They must be //! initialized individually. Classes referenced by methods in different -//! registries must be registered with each registry. +//! registries must be registered with each registry. A registry in which a +//! parameter of another registry's method dispatches must be initialized +//! before that registry. //! //! A registry also contains a set of @ref policies that control how certain //! operations are performed. For example, the `rtti` policy provides type @@ -1417,6 +1526,13 @@ auto missing_class::write(Stream& os) const { Registry::rtti::type_name(type, os); } +template +auto parameter_registry_not_initialized::write(Stream& os) const { + os << "registry of virtual parameter #" << param << " of "; + Registry::rtti::type_name(method, os); + os << " not initialized, or initialized again since"; +} + template auto missing_base::write(Stream& os) const { os << "missing base "; diff --git a/test/compile_fail_adl_registry_declared_mismatch.cpp b/test/compile_fail_adl_registry_declared_mismatch.cpp deleted file mode 100644 index 511da69f..00000000 --- a/test/compile_fail_adl_registry_declared_mismatch.cpp +++ /dev/null @@ -1,38 +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 - -#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() { - // 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; -} diff --git a/test/compile_fail_adl_registry_ref_mismatch.cpp b/test/compile_fail_adl_registry_ref_mismatch.cpp deleted file mode 100644 index 98432d20..00000000 --- a/test/compile_fail_adl_registry_ref_mismatch.cpp +++ /dev/null @@ -1,36 +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 - -#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; -}; - -// The contradiction of compile_fail_adl_registry_declared_mismatch.cpp, with -// the `virtual_ptr` passed by reference. `virtual_ptr` carries -// `zoo_registry` in every shape. -BOOST_OPENMETHOD( - poke, (const virtual_ptr&), std::string, other_registry); - -int main() { - // See compile_fail_adl_registry_declared_mismatch.cpp for why the call. - Animal animal; - virtual_ptr vp(animal); - poke(vp); - - return 0; -} diff --git a/test/compile_fail_adl_registry_virtual_mismatch.cpp b/test/compile_fail_adl_registry_virtual_mismatch.cpp deleted file mode 100644 index e33796b9..00000000 --- a/test/compile_fail_adl_registry_virtual_mismatch.cpp +++ /dev/null @@ -1,34 +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 - -#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; -}; - -// Same contradiction as compile_fail_adl_registry_declared_mismatch.cpp, in -// the `virtual_<>` shape: the class says `zoo_registry`, the method says -// otherwise. -BOOST_OPENMETHOD(poke, (virtual_), std::string, other_registry); - -int main() { - // See compile_fail_adl_registry_declared_mismatch.cpp for why the call. - Animal animal; - poke(animal); - - return 0; -} diff --git a/test/compile_fail_virtual_ptr_different_registries.cpp b/test/compile_fail_virtual_ptr_different_registries.cpp deleted file mode 100644 index 2d48110f..00000000 --- a/test/compile_fail_virtual_ptr_different_registries.cpp +++ /dev/null @@ -1,29 +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 Cat { - virtual ~Cat() { - } -}; - -struct other_registry : - default_registry::without::with< - policies::runtime_checks> {}; - -BOOST_OPENMETHOD(poke, (virtual_ptr), void, other_registry); - -int main() { - Cat felix; - poke(felix); - - return 0; -} diff --git a/test/compile_fail_virtual_registry_mismatch.cpp b/test/compile_fail_virtual_registry_mismatch.cpp deleted file mode 100644 index 6205bc28..00000000 --- a/test/compile_fail_virtual_registry_mismatch.cpp +++ /dev/null @@ -1,35 +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 - -#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_core.cpp b/test/test_core.cpp index 0a639bb4..80af1b90 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -135,8 +135,10 @@ static_assert(!detail::is_registry); BOOST_AUTO_TEST_CASE(test_init_type_ids) { type_id ids[2]; - auto last = init_type_ids>::fn(ids); - BOOST_TEST_REQUIRE(last - ids == 2); + init_type_ids< + mp11::mp_list, + mp11::mp_list, + mp11::mp_iota_c<2>>::fn(ids); BOOST_TEST_REQUIRE(ids[0] == type_id(&typeid(a))); BOOST_TEST_REQUIRE(ids[1] == type_id(&typeid(b))); } diff --git a/test/test_mixed_registries.cpp b/test/test_mixed_registries.cpp new file mode 100644 index 00000000..9ab1a2df --- /dev/null +++ b/test/test_mixed_registries.cpp @@ -0,0 +1,426 @@ +// 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) + +// Methods whose virtual parameters belong to different registries, each with +// its own rtti policy. `zoo_registry` and `garage_registry` identify classes +// by a number stored in the object, and give the same numbers to unrelated +// classes: a registry that looked up another's type_ids in its own tables +// would find the wrong class. `space_registry` uses standard RTTI. + +#include +#include +#include + +#include +#include +#include +#include + +#define BOOST_TEST_MODULE mixed_registries +#include + +using namespace boost::openmethod; + +namespace { + +auto next_non_polymorphic_id() -> std::size_t { + static std::size_t counter = 0; + + return (std::size_t(1) << (sizeof(std::size_t) * 8 - 1)) | ++counter; +} + +template +auto non_polymorphic_static_type() -> std::size_t { + static std::size_t value = next_non_polymorphic_id(); + + return value; +} + +// Classes derived from `Root` carry their type number in `type`. +template +struct number_rtti : policies::rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = std::is_base_of_v; + + template + static auto static_type() -> type_id { + if constexpr (is_polymorphic) { + return type_id(T::static_type); + } else { + return type_id(non_polymorphic_static_type()); + } + } + + template + static auto dynamic_type(const T& obj) -> type_id { + if constexpr (is_polymorphic) { + return type_id(obj.type); + } else { + return type_id(non_polymorphic_static_type()); + } + } + + template + static void type_name(type_id type, Stream& stream) { + auto index = reinterpret_cast(type); + stream << (index >= 1 && index <= 3 ? Root::names[index - 1] : "?"); + } + + static auto type_index(type_id type) -> type_id { + return type; + } + }; +}; + +struct Animal { + static constexpr std::size_t static_type = 1; + static constexpr const char* names[] = {"Animal", "Dog", "Cat"}; + std::size_t type; + + explicit Animal(std::size_t type = static_type) : type(type) { + } +}; + +struct Dog : Animal { + static constexpr std::size_t static_type = 2; + + Dog() : Animal(static_type) { + } +}; + +struct Cat : Animal { + static constexpr std::size_t static_type = 3; + + Cat() : Animal(static_type) { + } +}; + +// Same type numbers as the animals. +struct Vehicle { + static constexpr std::size_t static_type = 1; + static constexpr const char* names[] = {"Vehicle", "Car", "Truck"}; + std::size_t type; + + explicit Vehicle(std::size_t type = static_type) : type(type) { + } +}; + +struct Car : Vehicle { + static constexpr std::size_t static_type = 2; + + Car() : Vehicle(static_type) { + } +}; + +struct Truck : Vehicle { + static constexpr std::size_t static_type = 3; + + Truck() : Vehicle(static_type) { + } +}; + +struct Planet { + virtual ~Planet() = default; +}; + +struct Earth : Planet {}; +struct Mars : Planet {}; + +struct zoo_registry : + default_registry::with>::without { +}; + +// Checked, because it holds methods with parameters in `zoo_registry`. +struct garage_registry : + default_registry::with>::without:: + with {}; + +struct space_registry : + default_registry::with {}; + +// Holds methods only; every parameter is foreign. +struct arbiter_registry : + default_registry::with< + policies::runtime_checks, policies::throw_error_handler> {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, zoo_registry); +BOOST_OPENMETHOD_CLASSES(Vehicle, Car, Truck, garage_registry); +BOOST_OPENMETHOD_CLASSES(Planet, Earth, Mars, space_registry); + +// ----------------------------------------------------------------------------- +// A method of the registry of its parameter, sharing the v-tables with the +// foreign parameters below. + +BOOST_OPENMETHOD(kind, (virtual_), std::string, zoo_registry); + +BOOST_OPENMETHOD_OVERRIDE(kind, (const Animal&), std::string) { + return "animal"; +} + +BOOST_OPENMETHOD_OVERRIDE(kind, (const Dog&), std::string) { + return "dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(kind, (const Cat&), std::string) { + return "cat"; +} + +// ----------------------------------------------------------------------------- +// Foreign first parameter: its entry, a pointer into the method's dispatch +// table, is written in the v-tables of `zoo_registry`. + +BOOST_OPENMETHOD( + run_over, (virtual_, virtual_), + std::string, garage_registry); + +BOOST_OPENMETHOD_OVERRIDE( + run_over, (const Animal&, const Vehicle&), std::string) { + return "animal, vehicle"; +} + +BOOST_OPENMETHOD_OVERRIDE(run_over, (const Dog&, const Car&), std::string) { + return "dog, car"; +} + +BOOST_OPENMETHOD_OVERRIDE(run_over, (const Cat&, const Truck&), std::string) { + return "cat, truck"; +} + +// Two equally good overriders for (Dog, Truck). +BOOST_OPENMETHOD_OVERRIDE( + run_over, (const Dog& dog, const Vehicle& vehicle), std::string) { + return "dog, vehicle -> " + next(dog, vehicle); +} + +BOOST_OPENMETHOD_OVERRIDE( + run_over, (const Animal&, const Truck&), std::string) { + return "animal, truck"; +} + +// ----------------------------------------------------------------------------- +// Foreign second parameter. + +BOOST_OPENMETHOD( + carry, (virtual_, virtual_), + std::string, garage_registry); + +BOOST_OPENMETHOD_OVERRIDE(carry, (const Car&, const Dog&), std::string) { + return "car, dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(carry, (const Truck&, const Animal&), std::string) { + return "truck, animal"; +} + +// ----------------------------------------------------------------------------- +// A uni-method on a foreign parameter, in a registry that has no classes. + +BOOST_OPENMETHOD( + horn, (virtual_), std::string, + arbiter_registry); + +BOOST_OPENMETHOD_OVERRIDE(horn, (const Car&), std::string) { + return "beep"; +} + +BOOST_OPENMETHOD_OVERRIDE(horn, (const Truck&), std::string) { + return "honk"; +} + +// ----------------------------------------------------------------------------- +// Three registries, three rtti policies, and the `virtual_ptr` shapes. + +BOOST_OPENMETHOD( + land, + (virtual_ptr, + const virtual_ptr&, + virtual_), + std::string, arbiter_registry); + +BOOST_OPENMETHOD_OVERRIDE( + land, + (virtual_ptr, + const virtual_ptr&, const Planet&), + std::string) { + return "animal, vehicle, planet"; +} + +BOOST_OPENMETHOD_OVERRIDE( + land, + (virtual_ptr, + const virtual_ptr&, const Earth&), + std::string) { + return "dog, car, earth"; +} + +BOOST_OPENMETHOD_OVERRIDE( + land, + (virtual_ptr, + const virtual_ptr&, const Mars&), + std::string) { + return "cat, truck, mars"; +} + +template +auto throws(Fn fn) -> bool { + try { + fn(); + } catch (const Error&) { + return true; + } + + return false; +} + +void initialize_all() { + initialize(trace::from_env()); + initialize(trace::from_env()); + initialize(trace::from_env()); + initialize(trace::from_env()); +} + +} // namespace + +BOOST_AUTO_TEST_CASE(parameter_registry_first) { + // `garage_registry` holds methods with a parameter in `zoo_registry`, + // which has not been initialized yet. + BOOST_TEST(throws([] { + initialize(); + })); + + initialize_all(); +} + +BOOST_AUTO_TEST_CASE(dispatch) { + initialize_all(); + + Animal animal; + Dog dog; + Cat cat; + Vehicle vehicle; + Car car; + Truck truck; + Earth earth; + Mars mars; + + BOOST_TEST(kind(animal) == "animal"); + BOOST_TEST(kind(dog) == "dog"); + BOOST_TEST(kind(cat) == "cat"); + + BOOST_TEST(run_over(animal, vehicle) == "animal, vehicle"); + BOOST_TEST(run_over(dog, car) == "dog, car"); + BOOST_TEST(run_over(cat, truck) == "cat, truck"); + BOOST_TEST(run_over(cat, car) == "animal, vehicle"); + BOOST_TEST(run_over(dog, vehicle) == "dog, vehicle -> animal, vehicle"); + BOOST_TEST(run_over(animal, truck) == "animal, truck"); + BOOST_TEST(throws([&] { run_over(dog, truck); })); + + BOOST_TEST(carry(car, dog) == "car, dog"); + BOOST_TEST(carry(truck, cat) == "truck, animal"); + BOOST_TEST(throws([&] { carry(car, cat); })); + BOOST_TEST(throws([&] { carry(vehicle, dog); })); + + BOOST_TEST(horn(car) == "beep"); + BOOST_TEST(horn(truck) == "honk"); + BOOST_TEST(throws([&] { horn(vehicle); })); + + BOOST_TEST( + land( + virtual_ptr(dog), + virtual_ptr(car), + earth) == "dog, car, earth"); + BOOST_TEST( + land( + virtual_ptr(cat), + virtual_ptr(truck), + mars) == "cat, truck, mars"); + BOOST_TEST( + land( + virtual_ptr(dog), + virtual_ptr(car), + mars) == "animal, vehicle, planet"); +} + +BOOST_AUTO_TEST_CASE(parameter_registry_initialized_again) { + initialize_all(); + + Dog dog; + Car car; + + BOOST_TEST(run_over(dog, car) == "dog, car"); + + // Clears the entries of `garage_registry`'s methods in the v-tables of + // `zoo_registry`. + initialize(); + BOOST_TEST(kind(dog) == "dog"); + BOOST_TEST(throws([&] { + run_over(dog, car); + })); + BOOST_TEST( + throws([&] { carry(car, dog); })); + + // Methods on parameters of other registries are not affected. + BOOST_TEST(horn(car) == "beep"); + + initialize(); + BOOST_TEST(run_over(dog, car) == "dog, car"); + BOOST_TEST(carry(car, dog) == "car, dog"); +} + +// A program whose modules share a registry may hold several copies of a +// method, one per module, each registering a record of its foreign parameters. +// Fake a second copy of `carry`: its parameter must share the slot of the +// first, rather than take one of its own. +BOOST_AUTO_TEST_CASE(copies_share_the_slot) { + using carry_method = BOOST_OPENMETHOD_TYPE( + carry, + (virtual_, virtual_), + std::string, garage_registry); + auto& real = carry_method::fn; + BOOST_TEST_REQUIRE(real.foreign_end - real.foreign_begin == 1); + auto& real_param = *real.foreign_begin; + + std::size_t slots_strides[3] = {}; + detail::method_info copy{}; + copy.vp_begin = real.vp_begin; + copy.vp_end = real.vp_end; + copy.not_implemented = real.not_implemented; + copy.ambiguous = real.ambiguous; + copy.method_type_id = real.method_type_id; + copy.return_type_id = real.return_type_id; + copy.slots_strides_ptr = slots_strides; + + detail::foreign_parameter_info param{}; + param.method = © + param.param = real_param.param; + param.host_generation = real_param.host_generation; + param.method_state = real_param.method_state; + param.same_method = real_param.same_method; + param.method_type = real_param.method_type; + param.resolve_vp = real_param.resolve_vp; + copy.foreign_begin = ¶m; + copy.foreign_end = ¶m + 1; + + garage_registry::state().methods.push_back(copy); + zoo_registry::state().foreign_parameters.push_back(param); + + initialize_all(); + + BOOST_TEST(param.slot == real_param.slot); + BOOST_TEST_REQUIRE(param.cone.size() == real_param.cone.size()); + BOOST_TEST(param.cone[0].entry == real_param.cone[0].entry); + BOOST_TEST( + std::equal(slots_strides, slots_strides + 3, real.slots_strides_ptr)); + + Car car; + Dog dog; + BOOST_TEST(carry(car, dog) == "car, dog"); + + zoo_registry::state().foreign_parameters.remove(param); + garage_registry::state().methods.remove(copy); + initialize_all(); +} diff --git a/test/test_mixed_registries_affinity.cpp b/test/test_mixed_registries_affinity.cpp new file mode 100644 index 00000000..11300fe7 --- /dev/null +++ b/test/test_mixed_registries_affinity.cpp @@ -0,0 +1,101 @@ +// 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 method may be declared in any registry, whatever registries its parameters +// carry - through a class affinity, or spelled on the parameter. A parameter +// that carries another registry dispatches in it. + +#include +#include +#include +#include + +#include +#include + +#define BOOST_TEST_MODULE mixed_registries_affinity +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry::with> {}; + +// Holds the methods. +struct other_registry : + default_registry::with {}; + +struct Animal { + virtual ~Animal() = default; + friend auto boost_openmethod_registry(Animal*) -> zoo_registry; +}; + +struct Dog : Animal {}; + +// No affinity. +struct Vehicle { + virtual ~Vehicle() = default; +}; + +struct Car : Vehicle {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_CLASSES(Vehicle, Car, zoo_registry); +BOOST_OPENMETHOD_CLASSES(Vehicle, Car, other_registry); + +static_assert(std::is_same_v, zoo_registry>); + +// `virtual_ptr` carries `zoo_registry`, by affinity. +BOOST_OPENMETHOD(by_value, (virtual_ptr), std::string, other_registry); + +BOOST_OPENMETHOD_OVERRIDE(by_value, (virtual_ptr), std::string) { + return "dog"; +} + +BOOST_OPENMETHOD( + by_ref, (const virtual_ptr&), std::string, other_registry); + +BOOST_OPENMETHOD_OVERRIDE(by_ref, (const virtual_ptr&), std::string) { + return "dog"; +} + +// `virtual_` carries it too, by affinity. +BOOST_OPENMETHOD( + by_virtual, (virtual_), std::string, other_registry); + +BOOST_OPENMETHOD_OVERRIDE(by_virtual, (const Dog&), std::string) { + return "dog"; +} + +// `Vehicle` declares nothing: spelled, it goes to `zoo_registry`; otherwise it +// adopts the method's registry. +BOOST_OPENMETHOD( + spelled, (virtual_), std::string, + other_registry); + +BOOST_OPENMETHOD_OVERRIDE(spelled, (const Car&), std::string) { + return "car"; +} + +BOOST_OPENMETHOD( + adopted, (virtual_, virtual_), std::string, + other_registry); + +BOOST_OPENMETHOD_OVERRIDE(adopted, (const Dog&, const Car&), std::string) { + return "dog, car"; +} + +BOOST_AUTO_TEST_CASE(any_method_registry) { + initialize(); + initialize(); + + Dog dog; + Car car; + + BOOST_TEST(by_value(virtual_ptr(dog)) == "dog"); + BOOST_TEST(by_ref(virtual_ptr(dog)) == "dog"); + BOOST_TEST(by_virtual(dog) == "dog"); + BOOST_TEST(spelled(car) == "car"); + BOOST_TEST(adopted(dog, car) == "dog, car"); +} diff --git a/test/test_mixed_registries_deferred.cpp b/test/test_mixed_registries_deferred.cpp new file mode 100644 index 00000000..c5100ef4 --- /dev/null +++ b/test/test_mixed_registries_deferred.cpp @@ -0,0 +1,211 @@ +// 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) + +// Methods whose virtual parameters mix registries that defer their type ids +// with registries that do not. The animals get their type numbers at run time, +// at the start of the test: a type id read during static construction is zero, +// and dispatch goes wrong. + +#include +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE mixed_registries_deferred +#include + +using namespace boost::openmethod; + +namespace { + +auto next_non_polymorphic_id() -> std::size_t { + static std::size_t counter = 0; + + return (std::size_t(1) << (sizeof(std::size_t) * 8 - 1)) | ++counter; +} + +template +auto non_polymorphic_static_type() -> std::size_t { + static std::size_t value = next_non_polymorphic_id(); + + return value; +} + +struct Animal { + static std::size_t static_type; + std::size_t type; + + Animal() : type(static_type) { + } + + protected: + explicit Animal(std::size_t type) : type(type) { + } +}; + +struct Dog : Animal { + static std::size_t static_type; + + Dog() : Animal(static_type) { + } +}; + +struct Cat : Animal { + static std::size_t static_type; + + Cat() : Animal(static_type) { + } +}; + +std::size_t Animal::static_type; +std::size_t Dog::static_type; +std::size_t Cat::static_type; + +void assign_type_ids() { + Animal::static_type = 1; + Dog::static_type = 2; + Cat::static_type = 3; +} + +struct late_rtti : policies::deferred_static_rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = std::is_base_of_v; + + template + static auto static_type() -> type_id { + if constexpr (is_polymorphic) { + return type_id(T::static_type); + } else { + return type_id(non_polymorphic_static_type()); + } + } + + template + static auto dynamic_type(const T& obj) -> type_id { + if constexpr (is_polymorphic) { + return type_id(obj.type); + } else { + return type_id(non_polymorphic_static_type()); + } + } + + static auto type_index(type_id type) -> type_id { + return type; + } + }; +}; + +struct Vehicle { + virtual ~Vehicle() = default; +}; + +struct Car : Vehicle {}; +struct Truck : Vehicle {}; + +struct Planet { + virtual ~Planet() = default; +}; + +struct Earth : Planet {}; +struct Mars : Planet {}; + +struct late_registry : + default_registry::with::without::with< + policies::throw_error_handler> {}; + +struct garage_registry : + default_registry::with< + policies::runtime_checks, policies::throw_error_handler> {}; + +struct space_registry : + default_registry::with {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, late_registry); +BOOST_OPENMETHOD_CLASSES(Vehicle, Car, Truck, garage_registry); +BOOST_OPENMETHOD_CLASSES(Planet, Earth, Mars, space_registry); + +// The method's registry defers; so does its only parameter's. +BOOST_OPENMETHOD(sound, (virtual_), std::string, late_registry); + +BOOST_OPENMETHOD_OVERRIDE(sound, (const Dog&), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(sound, (const Cat&), std::string) { + return "meow"; +} + +// The method's registry does not defer; one parameter's does, the first or the +// second. +BOOST_OPENMETHOD( + ride, (virtual_, virtual_), + std::string, garage_registry); + +BOOST_OPENMETHOD_OVERRIDE(ride, (const Dog&, const Car&), std::string) { + return "dog, car"; +} + +BOOST_OPENMETHOD_OVERRIDE(ride, (const Cat&, const Truck&), std::string) { + return "cat, truck"; +} + +BOOST_OPENMETHOD( + drive, (virtual_, virtual_), + std::string, garage_registry); + +BOOST_OPENMETHOD_OVERRIDE(drive, (const Car&, const Cat&), std::string) { + return "car, cat"; +} + +BOOST_OPENMETHOD_OVERRIDE(drive, (const Truck&, const Dog&), std::string) { + return "truck, dog"; +} + +// The method's registry defers; its foreign parameter's does not. +BOOST_OPENMETHOD( + visit, (virtual_, virtual_), + std::string, late_registry); + +BOOST_OPENMETHOD_OVERRIDE(visit, (const Dog&, const Earth&), std::string) { + return "dog, earth"; +} + +BOOST_OPENMETHOD_OVERRIDE(visit, (const Cat&, const Mars&), std::string) { + return "cat, mars"; +} + +} // namespace + +BOOST_AUTO_TEST_CASE(mixed_deferral) { + assign_type_ids(); + + initialize(); + initialize(); + initialize(); + + Dog dog; + Cat cat; + Car car; + Truck truck; + Earth earth; + Mars mars; + + BOOST_TEST(sound(dog) == "bark"); + BOOST_TEST(sound(cat) == "meow"); + + BOOST_TEST(ride(dog, car) == "dog, car"); + BOOST_TEST(ride(cat, truck) == "cat, truck"); + + BOOST_TEST(drive(car, cat) == "car, cat"); + BOOST_TEST(drive(truck, dog) == "truck, dog"); + + BOOST_TEST(visit(dog, earth) == "dog, earth"); + BOOST_TEST(visit(cat, mars) == "cat, mars"); +}