diff --git a/.codecov.yml b/.codecov.yml index 233bbeda..b45c5999 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -20,4 +20,9 @@ comment: # See https://docs.codecov.com/docs/ignoring-paths ignore: - extra/**/* - # - test/**/* + # `test/**`, not the sample's `test/**/*`: the latter compiles to + # (?s:test/.*/[^\/]*)\Z, which needs a second slash and so matches only the + # subdirectories of test/ - every top-level test/test_*.cpp stays counted. + # Check a pattern with: + # curl -X POST --data-binary @.codecov.yml https://codecov.io/validate + - test/** diff --git a/CLAUDE.md b/CLAUDE.md index 0e83b6e3..c5250713 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -145,6 +145,9 @@ The library is structured in three conceptual layers: - Registry and policy framework - Error types: `not_initialized`, `bad_call`, `no_overrider`, `ambiguous_call`, etc. - No executable dispatch code + - An internal foundation, *not* an entry point: every other header pulls it in, and + nothing outside `include/` includes it directly. See *Overriding the default registry* + below. 2. **Core API** ([core.hpp](include/boost/openmethod/core.hpp)) - `method` - Method implementation @@ -330,6 +333,65 @@ One self-contained example per subdirectory of `doc/modules/ROOT/examples/shared `test/dynamic_loading/` (whose `registry_state_id()` is compared across modules to prove the state is a single symbol) and `test/implicit_shared_libraries/`. +### Overriding the default registry + +The registry is **forward-declared** before `core.hpp` - or any header that includes it, +`` among them - and **defined after**. Do not reintroduce the old +"include a pre-core header, define the registry, `#define`, then include" idiom: +`preamble.hpp` and `default_registry.hpp` should appear nowhere outside `include/`. Nothing +enforces that automatically; check it by hand when touching this area: + +```bash +git grep -n "openmethod/preamble.hpp\|openmethod/default_registry.hpp" -- doc test +``` + +```cpp +struct my_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY my_registry + +#include +#include // extra policies, any order +#include // the TU that calls initialize() + +struct my_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::vptr_map<>> {}; +``` + +A registry the library provides needs no declaration at all - `default_registry`, +`indirect_registry` and the five stock policies are complete as soon as +`` has been included, because `core.hpp` includes +`default_registry.hpp` *before* it tests the macro: + +```cpp +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY boost::openmethod::indirect_registry +#include +``` + +This works because every use of the macro in the headers is a name-only context - a default +template argument, an alias, a deduction guide, or a member typedef inside a template. Three +rules: + +- The registry must be **complete** before the first construct that instantiates it: a + `BOOST_OPENMETHOD*` macro, `use_classes`, `method`, `virtual_ptr`, `inplace_vptr_base`, + `initialize()`, or `BOOST_OPENMETHOD_{IMPORT,EXPORT,INSTANTIATE}_REGISTRY`. Violating this is + a hard error (`incomplete type ... used in nested name specifier`, from `use_class_aux` in + `core.hpp`) - never silent. +- It must name a **class**, declared with the same class-key (`struct`) as the definition. A + `class`/`struct` mismatch is MSVC C4099, an error under the suite's `/W4 /WX`. +- **Qualify** the name if it could also be found in `namespace boost::openmethod`. The macro + is expanded inside that namespace, so `#define BOOST_OPENMETHOD_DEFAULT_REGISTRY registry` + binds to `boost::openmethod::registry` rather than the global one. That one is loud - + `missing template arguments` - but a name that *does* resolve would bind to the wrong type + silently. `::registry` works. + +`test/CMakeLists.txt` withholds the shared PCH from any `test_*.cpp` that overrides the +registry - a force-included PCH would still precede the `#define`. It detects them by scanning +for the token `BOOST_OPENMETHOD_DEFAULT_REGISTRY` **or** for an include of a header that +carries the override on the file's behalf (`test_capture_errors.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. + ### Custom RTTI When `` is unavailable or insufficient, use static_rtti or implement custom RTTI. See `doc/modules/ROOT/examples/custom_rtti/` and policies in `include/boost/openmethod/policies/`. diff --git a/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp b/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp index 6fbb0fa4..163efd00 100644 --- a/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp +++ b/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp @@ -39,10 +39,15 @@ struct Times : Node { }; // end::classes[] -// tag::policy[] -#include -#include +// tag::setup[] +struct custom_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry + +#include +#include +// end::setup[] +// tag::policy[] struct custom_rtti : boost::openmethod::policies::rtti { template struct fn : defaults { @@ -75,12 +80,8 @@ struct custom_rtti : boost::openmethod::policies::rtti { // tag::registry[] struct custom_registry : boost::openmethod::registry< custom_rtti, boost::openmethod::policies::vptr_vector> {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry // end::registry[] -#include -#include #include using boost::openmethod::virtual_ptr; @@ -117,7 +118,6 @@ int main() { postfix(e, std::cout); std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20 } -// end::content[] void call_via_ref(const Node& node, std::ostream& os) { postfix(node, os); diff --git a/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp b/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp index 8e36ef52..0c154421 100644 --- a/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp +++ b/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp @@ -34,10 +34,15 @@ struct Times : Node { }; // end::classes[] -// tag::policy[] -#include -#include +// tag::setup[] +struct custom_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry + +#include +#include +// end::setup[] +// tag::policy[] // note: vvvvvvvvvvvvvvvv struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { template @@ -71,12 +76,8 @@ struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { // tag::registry[] struct custom_registry : boost::openmethod::registry< custom_rtti, boost::openmethod::policies::vptr_vector> {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry // end::registry[] -#include -#include #include using boost::openmethod::virtual_ptr; @@ -111,7 +112,6 @@ int main() { Plus d{a, b}; Times e{d, c}; std::cout << value(e) << "\n"; // 2 3 + 4 * = 20 } -// end::content[] auto call_via_ref(const Node& node, std::ostream& os) { return value(node); diff --git a/doc/modules/ROOT/examples/deferred_custom_rtti.cpp b/doc/modules/ROOT/examples/deferred_custom_rtti.cpp deleted file mode 100644 index 87e1f097..00000000 --- a/doc/modules/ROOT/examples/deferred_custom_rtti.cpp +++ /dev/null @@ -1,165 +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) - -#ifdef _MSC_VER -#pragma warning(disable : 4312) -#endif - -// tag::classes[] -struct custom_type_info { - static unsigned last; - unsigned id = ++last; -}; - -unsigned custom_type_info::last; - -struct Animal { - Animal() { - type = type_info.id; - } - - virtual ~Animal() = default; - - virtual auto cast_impl(unsigned target) -> void* { - if (type_info.id == target) { - return this; - } else { - return nullptr; - } - } - - template - auto cast() -> Class* { - return reinterpret_cast(cast_impl(Class::type_info.id)); - } - - static custom_type_info type_info; - unsigned type; -}; - -custom_type_info Animal::type_info; - -struct Cat : virtual Animal { - Cat() { - type = type_info.id; - } - - virtual auto cast_impl(unsigned target) -> void* { - if (type_info.id == target) { - return this; - } else { - return Animal::cast_impl(target); - } - } - - static custom_type_info type_info; -}; - -custom_type_info Cat::type_info; -// end::classes[] - -struct Dog : virtual Animal { - Dog() { - type = type_info.id; - } - - virtual auto cast_impl(unsigned target) -> void* { - if (type_info.id == target) { - return this; - } else { - return Animal::cast_impl(target); - } - } - - static custom_type_info type_info; -}; - -#include -#include - -namespace bom = boost::openmethod; - -// tag::registry[] -struct custom_rtti : bom::policies::deferred_static_rtti { - template - struct fn : defaults { - template - static constexpr bool is_polymorphic = std::is_base_of_v; - - template - static auto static_type() -> bom::type_id { - if constexpr (std::is_base_of_v) { - return reinterpret_cast(T::type_info.id); - } else { - return 0; - } - } - - template - static auto dynamic_type(const T& obj) -> bom::type_id { - if constexpr (std::is_base_of_v) { - return reinterpret_cast(obj.type); - } else { - return nullptr; - } - } - - // to support virtual inheritance: - template - static auto dynamic_cast_ref(Base&& obj) -> Derived { - using base_type = std::remove_reference_t; - if constexpr (std::is_base_of_v) { - return *obj.template cast>(); - } else { - abort(); // not supported - } - } - }; -}; - -struct custom_registry : bom::registry { -}; -// end::registry[] - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry - -#include - -#include -#include - -using boost::openmethod::virtual_ptr; - -BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (std::ostream & os, virtual_ptr /*cat*/), void) { - os << "hiss"; -} - -BOOST_OPENMETHOD_OVERRIDE( - poke, (std::ostream & os, virtual_ptr /*dog*/), void) { - os << "bark"; -} - -BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); - -custom_type_info Dog::type_info; - -auto main() -> int { - boost::openmethod::initialize(); - - std::unique_ptr a(new Cat); - std::unique_ptr b(new Dog); - - poke(std::cout, *a); // prints "hiss" - std::cout << "\n"; - - poke(std::cout, *b); // prints "bark" - std::cout << "\n"; - - return 0; -} -// end::example[] diff --git a/doc/modules/ROOT/examples/static_rtti.cpp b/doc/modules/ROOT/examples/static_rtti.cpp deleted file mode 100644 index ab7c5884..00000000 --- a/doc/modules/ROOT/examples/static_rtti.cpp +++ /dev/null @@ -1,56 +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) - -//[ all - -#include -#include - -struct static_registry - : boost::openmethod::registry {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry - -#include -#include -#include - -#include - -struct Animal {}; - -struct Dog : Animal {}; - -struct Cat : Animal {}; - -using namespace boost::openmethod::aliases; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); - -BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (virtual_ptr dog, std::ostream& os), void) { - os << "bark\n"; -} - -BOOST_OPENMETHOD_OVERRIDE( - poke, (virtual_ptr cat, std::ostream& os), void) { - os << "hiss\n"; -} - -int main() { - boost::openmethod::initialize(); - - unique_virtual_ptr a = make_unique_virtual(), - b = make_unique_virtual(); - - poke(a, std::cout); // hiss - poke(b, std::cout); // bark - - return 0; -} - -//] diff --git a/doc/modules/ROOT/examples/throw_error_handler.cpp b/doc/modules/ROOT/examples/throw_error_handler.cpp index 93a08adc..f1c520bd 100644 --- a/doc/modules/ROOT/examples/throw_error_handler.cpp +++ b/doc/modules/ROOT/examples/throw_error_handler.cpp @@ -4,9 +4,13 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) // tag::example[] -#include +struct custom_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry + +#include +#include -#include +#include struct Animal { virtual ~Animal() = default; @@ -32,11 +36,6 @@ struct throw_if_not_implemented : bom::policies::error_handler { struct custom_registry : bom::default_registry::with { }; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry - -#include -#include - using boost::openmethod::virtual_ptr; BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); diff --git a/doc/modules/ROOT/pages/custom_rtti.adoc b/doc/modules/ROOT/pages/custom_rtti.adoc index 6a21ccaf..14f91586 100644 --- a/doc/modules/ROOT/pages/custom_rtti.adoc +++ b/doc/modules/ROOT/pages/custom_rtti.adoc @@ -87,9 +87,22 @@ compile-time constants: include::{example}/1/custom_rtti.cpp[tag=classes] ---- -Let's define a `rtti` policy for this scheme. We are going to replace the -default registry globally, so we do _not_ include `` -or `` - only what we need to implement the policy: +We are going to replace the default registry for the whole program. That takes +two lines before the library is included: a declaration of the registry class, +and a definition of +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +naming it. The registry itself comes further down; only its _name_ has to be +known when `` is parsed - which any header that +includes it, `` among them, does for you. + +[source,c++] +---- +include::{example}/1/custom_rtti.cpp[tag=setup] +---- + +Now we can define a `rtti` policy for our scheme. Everything it needs is +already in scope: the cpp:rtti[] category and its cpp:rtti::defaults[] came in +with ``. [source,c++] ---- @@ -123,13 +136,10 @@ small, dense range. It means that we can use them as indexes in a vector. include::{example}/1/custom_rtti.cpp[tag=registry] ---- -Defining macro -xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] -sets the default registry used by all library components that need one. - -Next, we include the main header. -Because `BOOST_OPENMETHOD_DEFAULT_REGISTRY` is defined, its value is used -for the default registry. +This is the definition promised by the declaration shown above. It must appear +before the first `BOOST_OPENMETHOD`, `BOOST_OPENMETHOD_OVERRIDE` or +`BOOST_OPENMETHOD_CLASSES`: those instantiate the registry, and an incomplete +type will not do. The rest of the example is unchanged. @@ -164,3 +174,16 @@ postpones reading the type ids until `initialize` is called: ---- include::{example}/2/custom_rtti.cpp[tag=policy] ---- + +The setup is the same as before - the registry is declared, named as the default, +and defined once the library is in scope: + +[source,c++] +---- +include::{example}/2/custom_rtti.cpp[tag=setup] +---- + +[source,c++] +---- +include::{example}/2/custom_rtti.cpp[tag=registry] +---- diff --git a/doc/modules/ROOT/pages/error_handling.adoc b/doc/modules/ROOT/pages/error_handling.adoc index e954e16a..dd3313b9 100644 --- a/doc/modules/ROOT/pages/error_handling.adoc +++ b/doc/modules/ROOT/pages/error_handling.adoc @@ -24,8 +24,12 @@ not implemented spin ---- -We can also replace the `error_handler` policy with our own. -For example: +We can also replace the `error_handler` policy with our own. Since the handler +is part of the registry, this means overriding the default registry: the +listing opens by declaring `custom_registry` and naming it in +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY], +then defines it further down, once the policy it is built from exists. See +xref:registries_and_policies.adoc[Registries and Policies] for that recipe. [source,c++] diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 03c25e67..3003bb6b 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -104,46 +104,52 @@ parameters. *The headers below are for advanced use*. -## Pre-Core Headers - -The following headers can be included before `core.hpp` to define custom -registries and policies, and override the default registry by defining -xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. - -### link:{headers-url}/boost/openmethod/preamble.hpp[] - -Defines `registry` and stock policy categories. Also defines all types and -functions necessary for the definition of `registry`. +## Policy Headers + +Most stock policies live in a header of their own. Those that make up +cpp:default_registry[] come in with ``; the others are +included explicitly. A policy header depends on nothing but the library's +foundations, so it may be included in any order relative to +`` - in particular _after_ it, which is where a program +that overrides +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`] +puts it. + +Three policies have no header of their own: cpp:indirect_vptr[], +cpp:runtime_checks[] and cpp:deferred_static_rtti[] are defined alongside +`registry` itself, and are available as soon as `` has +been included. ### link:{headers-url}/boost/openmethod/policies/std_rtti.hpp[] -Provides an implementation of the `rtti` policy using standard RTTI. +Provides an implementation of the `rtti` policy using standard RTTI. Part of +`default_registry`. ### link:{headers-url}/boost/openmethod/policies/fast_perfect_hash.hpp[] Provides an implementation of the `hash` policy using a fast perfect hash -function. +function. Part of `default_registry`. ### link:{headers-url}/boost/openmethod/policies/vptr_vector.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers -in a `std::vector` indexed by type ids, possibly hashed. +in a `std::vector` indexed by type ids, possibly hashed. Part of +`default_registry`. ### link:{headers-url}/boost/openmethod/policies/default_error_handler.hpp[] Provides an implementation of the `error_handler` policy that calls a `std::function` when an error is encountered, and before -the library aborts the program. +the library aborts the program. Part of `default_registry`. ### link:{headers-url}/boost/openmethod/policies/stderr_output.hpp[] Provides an implementation of the `output` policy that writes diagnostics to -the C standard error stream (not using iostreams). +the C standard error stream (not using iostreams). Part of `default_registry`. -### link:{headers-url}/boost/openmethod/default_registry.hpp[] - -Defines the default registry, which contains all the stock policies listed -above. Includes all the headers listed in this section so far. +NOTE: `default_registry` also contains `runtime_checks` when +xref:reference:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[`BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS`] +is defined. That policy needs no header - see above. ### link:{headers-url}/boost/openmethod/policies/static_rtti.hpp[] @@ -159,3 +165,21 @@ exceptions. Provides an implementation of the `vptr` policy that stores the v-table pointers in a map (by default a `std::map`) indexed by type ids. + +## Headers Included by Other Headers + +These are the library's foundations. Every other header includes them, and a +program has no reason to include either directly. + +### link:{headers-url}/boost/openmethod/preamble.hpp[] + +Defines `registry`, the stock policy categories, and everything else needed to +define a registry. + +### link:{headers-url}/boost/openmethod/default_registry.hpp[] + +Defines cpp:default_registry[] and cpp:indirect_registry[], and includes the +five policy headers the former is built from. `core.hpp` includes it +unconditionally, so both registries are available as soon as +`` has been included, whether as the default registry or +as a base for one built with cpp:with[] and cpp:without[]. diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index 928f5088..e7e0002e 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -7,14 +7,48 @@ 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 -xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], take an additional -argument, a cpp:registry[] class, which defaults to cpp:default_registry[]. The -default registry can be overridden by defining the macroprocessor symbol +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], take an +additional argument, a cpp:registry[] class, which defaults to +cpp:default_registry[]. The default registry can be overridden by defining the +preprocessor symbol xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] -_before_ including ``. The value of the symbol is -used as a default template parameter for `use_classes`, `method`, `virtual_ptr`, -and others. Once the `core` header has been included, changing -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` has no effect. +_before_ including `` (or any header that includes +it, like ``). The value of the symbol is used as a default +template parameter for `use_classes`, `method`, `virtual_ptr`, and others. Once +it has been included, changing `BOOST_OPENMETHOD_DEFAULT_REGISTRY` has no +effect. + +For a registry the library provides, that is the whole recipe: + +[source,c++] +---- +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY boost::openmethod::indirect_registry +#include +---- + +A registry of your own is only _declared_ before the include, and defined after +it, where the policies it is built from are available - the library's, and any +of your own, written against them: + +[source,c++] +---- +struct my_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY my_registry + +#include +#include + +struct my_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::vptr_map<>> {}; +---- + +The definition must precede the first construct that instantiates the registry: +a `BOOST_OPENMETHOD` macro, a `use_classes`, a `method` or a `virtual_ptr`. The +symbol must name a class, not a typedef or an alias template, and the +declaration must use the same class-key as the definition. Qualify the name if +it could also be found in namespace `boost::openmethod` - `registry` in +particular. 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 @@ -52,7 +86,7 @@ Policies are placed in the cpp:boost::openmethod::policies[] namespace. |=== -if +If xref:reference:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] is defined, `default_registry` also contains the `runtime_checks` policy. This enables extra validations during method dispatch, which can detect missing class @@ -65,7 +99,8 @@ about xref:shared_libraries.adoc[shared libraries]. Registries can be created from scratch, using the `registry` template. Here is the definition of `default_registry`, copied from -``: +``, which `` +always includes: [source,c++] ---- @@ -115,7 +150,7 @@ left to right; cpp:finalize[] calls each policy's `finalize` in the reverse order. A policy that depends on another policy having been initialized must therefore be listed _after_ its dependency. In particular, `vptr_vector` reads the state of the `type_hash` policy, so the `type_hash` policy must come before -`vptr_vector` — as in `default_registry` above, where `fast_perfect_hash` +`vptr_vector` - as in `default_registry` above, where `fast_perfect_hash` precedes `vptr_vector`. This particular requirement is enforced with a `static_assert`. @@ -129,7 +164,7 @@ struct indirect_registry : default_registry::with {}; ---- Policies are implemented as unary -https://www.boost.org/doc/libs/1_89_0/libs/mp11/doc/html/mp11.html[Boost.MP11 +https://www.boost.org/doc/libs/latest/libs/mp11/doc/html/mp11.html[Boost.MP11 quoted metafunctions]. A policy is an ordinary class that contains a nested class template `fn`, which is instantiated by the registry, passing itself as the single template argument. The reason for this mechanism is to allow policies diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index 0861c919..716a6fd2 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -44,7 +44,9 @@ BOOST_OPENMETHOD_EXPORT_REGISTRY(boost::openmethod::default_registry); BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(boost::openmethod::default_registry); ---- -Use them at namespace scope, after `` has been included. +Use them at namespace scope, after `` has been included +and after the registry class is defined - they expand to +`registry_state`, which needs a complete type. Everything they emit is fully qualified, so nothing need be added to `namespace boost::openmethod`. @@ -236,12 +238,13 @@ most programs that dynamically load shared libraries do so at the very beginning of their execution. Otherwise, indirect v-table pointers must be used. This is achieved by using a -registry that contains the cpp:indirect_vptr[] policy. -`` provides an cpp:indirect_registry[] -that has the same policies as `default_registry`, plus `indirect_vptr`. Make it -the registry the `BOOST_OPENMETHOD` macros use by defining +registry that contains the cpp:indirect_vptr[] policy. The library provides an +cpp:indirect_registry[] that has the same policies as `default_registry`, plus +`indirect_vptr`. Make it the registry the `BOOST_OPENMETHOD` macros use by +defining xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] -_before_ including ``. +_before_ including ``, or any header that includes +it, like ``. The `indirect_vptr` example does that in the header both modules share, rather than passing a compiler switch from the build system. One place then settles @@ -277,15 +280,30 @@ A custom registry is shared exactly the same way - name it instead of [source,c++] ---- // my_registry.hpp +#ifndef MY_REGISTRY_DEFINED +#define MY_REGISTRY_DEFINED + +struct my_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY my_registry + +#include + struct my_registry : boost::openmethod::registry {}; #ifdef OWNS_REGISTRY_STATE BOOST_OPENMETHOD_EXPORT_REGISTRY(my_registry); #else BOOST_OPENMETHOD_IMPORT_REGISTRY(my_registry); +#endif + #endif ---- +The `#define` makes `my_registry` the registry the `BOOST_OPENMETHOD` macros +use, exactly as `indirect_vptr/animals.hpp` does above; drop it if the methods +name their registry explicitly. The sharing macros come last, after the +definition. + [source,c++] ---- // registry.cpp - exactly one translation unit of the owning module diff --git a/doc/modules/ROOT/snippets/static_rtti.cpp b/doc/modules/ROOT/snippets/static_rtti.cpp index 1566d17e..31f6809c 100644 --- a/doc/modules/ROOT/snippets/static_rtti.cpp +++ b/doc/modules/ROOT/snippets/static_rtti.cpp @@ -3,20 +3,20 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -// `static_rtti` has to be selected before is included, -// so this example needs a translation unit of its own. +// The #define that selects `static_registry` has to precede +// , so this example needs a translation unit of its own. // tag::registry[] -#include +struct static_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry + +#include #include struct static_registry : boost::openmethod::registry {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry // end::registry[] -#include #include #include diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 3cab92ce..10daed3a 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -35,19 +35,45 @@ //! //! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` can be defined by a program to change //! the default registry globally, *before* including -//! ``. After that, changing its value has no effect, +//! `` (or any header that includes it, like +//! ``). After that, changing its value has no effect, //! even on other macros. //! -//! To override the default registry, proceed as follows: +//! To use a registry that the library provides, name it in the macro: //! -//! @li Define a @ref boost::openmethod::registry class, either from scratch, or -//! by tuning an existing registry. Include ``, -//! ``, and headers under -//! `boost/openmethod/policies` as needed. +//! @code +//! #define BOOST_OPENMETHOD_DEFAULT_REGISTRY boost::openmethod::indirect_registry +//! #include +//! @endcode //! -//! @li Set `BOOST_OPENMETHOD_DEFAULT_REGISTRY` to the new registry class. +//! To use a registry of your own, *declare* the class before the include, and +//! *define* it after: //! -//! @li Include ``. +//! @code +//! struct my_registry; +//! #define BOOST_OPENMETHOD_DEFAULT_REGISTRY my_registry +//! +//! #include +//! // plus any policies/ and interop/ headers needed, in any order +//! +//! struct my_registry +//! : boost::openmethod::default_registry::with {}; +//! @endcode +//! +//! Only the declaration has to precede the include. Deferring the definition is +//! what makes it possible to build the registry from the policies that +//! `` brings in - and from policies of your own, written +//! against them. +//! +//! The registry must be complete before the first construct that instantiates +//! it: a `BOOST_OPENMETHOD*` macro, @ref boost::openmethod::use_classes, +//! @ref boost::openmethod::method, @ref boost::openmethod::virtual_ptr, or +//! @ref BOOST_OPENMETHOD_IMPORT_REGISTRY and its companions. +//! +//! @note The value must name a class, not a typedef or an alias template, and +//! the declaration must use the same class-key as the definition. Qualify the +//! name (`::my_registry`, `myapp::my_registry`) if it could also be found in +//! namespace `boost::openmethod` - `registry` in particular. //! //! @note Use this feature with caution, as it will cause ODR violations if //! different translation units define different default registries. @@ -1946,7 +1972,8 @@ struct validate_method_parameter< //! The default value for `Registry` is @ref default_registry, but it can be //! overridden by defining the preprocessor symbol //! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, *before* including -//! ``. Setting the symbol afterwards has no effect. +//! `` (or any header that includes it, like +//! ``). Setting the symbol afterwards has no effect. //! //! Specializations of `method` have a single instance: the static member `fn`, //! which has an `operator()` that forwards to the appropriate overrider. It is diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index ad7e713f..ca8926ec 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -89,7 +89,8 @@ struct indirect_registry : default_registry::with {}; //! Enable runtime checks in @ref boost::openmethod::default_registry. //! //! May be defined by a program before including -//! `` to enable runtime checks. See +//! `` (or any header that includes it, +//! like ``) to enable runtime checks. See //! @ref boost::openmethod::default_registry for details. //! //! @par Example diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 5b7676ac..4e895841 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -177,8 +177,9 @@ inline constexpr bool method_not_found = false; //! //! @note The default registry is the value of //! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY at the point -//! `` is included. Changing the value of this symbol -//! has no effect after that point. +//! `` is included, directly or through a header +//! like ``. Changing the value of this symbol has no +//! effect after that point. //! //! @par Example //! @@ -536,8 +537,9 @@ inline constexpr bool method_not_found = false; //! documentation for more details. //! //! @note The default registry is the value of -//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when `` is -//! included. Subsequently changing it has no retroactive effect. +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when `` +//! is included, directly or through a header like ``. +//! Subsequently changing it has no retroactive effect. //! //! @par Examples //! diff --git a/include/boost/openmethod/policies/static_rtti.hpp b/include/boost/openmethod/policies/static_rtti.hpp index 8cccf230..d907818c 100644 --- a/include/boost/openmethod/policies/static_rtti.hpp +++ b/include/boost/openmethod/policies/static_rtti.hpp @@ -21,8 +21,9 @@ namespace boost::openmethod::policies { //! //! @par Example //! -//! Selecting the policy, which has to happen before `` -//! is included: +//! Selecting the policy. The registry is declared before +//! `` (or any header that includes it, like +//! ``), and defined after it: //! //! include:static_rtti.cpp#registry //! diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f22005e3..c404d5c1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -66,11 +66,18 @@ endfunction() # Precompiled headers: most test_*.cpp files just #include # (and friends) before anything else, so a shared PCH avoids re-parsing that # (large, mp11-heavy) header once per test executable. A handful of files -# instead define their own registry and #define BOOST_OPENMETHOD_DEFAULT_REGISTRY -# *before* the first inclusion of core.hpp (directly or via boost/openmethod.hpp); -# force-including a PCH that already pulled in core.hpp would defeat that -# override, so those files are detected (by scanning for the macro) and left -# without a PCH. +# instead #define BOOST_OPENMETHOD_DEFAULT_REGISTRY *before* the first inclusion +# of core.hpp (directly or via boost/openmethod.hpp), and define the registry +# itself after it; force-including a PCH that already pulled in core.hpp would +# precede the #define and defeat the override, so those files are left without a +# PCH. +# +# Detected by scanning for the macro, or for an include of a header that carries +# the override on the file's behalf: test_capture_errors.hpp and +# test_checked_registry.hpp each own a whole recipe, so the tests including them +# name neither the macro nor their registry. Add another such header and this +# list has to learn about it - miss one and it still compiles, the macros simply +# bind to default_registry, and the test's expectations fail at run time. set(BOOST_OPENMETHOD_TEST_PCH_HEADERS @@ -91,7 +98,14 @@ foreach(test_cpp ${test_cpp_files}) add_dependencies(tests ${test_target}) file(READ ${test_cpp} test_cpp_contents) - string(FIND "${test_cpp_contents}" "BOOST_OPENMETHOD_DEFAULT_REGISTRY" test_cpp_overrides_registry) + set(test_cpp_overrides_registry -1) + foreach(marker "BOOST_OPENMETHOD_DEFAULT_REGISTRY" "test_capture_errors.hpp" + "test_checked_registry.hpp") + string(FIND "${test_cpp_contents}" "${marker}" marker_pos) + if (NOT marker_pos EQUAL -1) + set(test_cpp_overrides_registry ${marker_pos}) + endif() + endforeach() if (test_cpp_overrides_registry EQUAL -1) if (NOT boost_openmethod_pch_owner) diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index 1c380026..932f5b0e 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -6,26 +6,16 @@ #ifndef BOOST_OPENMETHOD_TEST_DYNAMIC_LOADING_REGISTRY_HPP #define BOOST_OPENMETHOD_TEST_DYNAMIC_LOADING_REGISTRY_HPP -#include - -#include - // The registry under test. The build selects the variant by defining // BOOST_OPENMETHOD_DEFAULT_REGISTRY on the command line (e.g. to -// ::boost::openmethod::indirect_registry); otherwise fall back to -// default_registry, matching core.hpp's own default. This must be set before -// the .cpp files include core.hpp. -#ifndef BOOST_OPENMETHOD_DEFAULT_REGISTRY -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY ::boost::openmethod::default_registry -#endif +// ::boost::openmethod::indirect_registry). With no switch, core.hpp supplies +// its own default, default_registry. Either way the macro is defined and the +// registry complete once this header has been included - which is why the +// alias below comes after it, not before. +#include using test_registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY; -// Share the registry state across the modules. Included *after* the -// BOOST_OPENMETHOD_DEFAULT_REGISTRY definition above, so core.hpp binds its -// macro default registry to test_registry. -#include - // The module that owns the state compiles with EXPORT_REGISTRY defined; every // other module imports it. The visibility attribute goes on the declaration; // the definition that follows carries none (repeating it is an error on GCC). diff --git a/test/implicit_shared_libraries/custom_registry/registry.hpp b/test/implicit_shared_libraries/custom_registry/registry.hpp index f7357488..dc4b56aa 100644 --- a/test/implicit_shared_libraries/custom_registry/registry.hpp +++ b/test/implicit_shared_libraries/custom_registry/registry.hpp @@ -6,8 +6,14 @@ #ifndef BOOST_OPENMETHOD_TEST_IMPLICIT_SHARED_LIBRARIES_CUSTOM_REGISTRY_HPP #define BOOST_OPENMETHOD_TEST_IMPLICIT_SHARED_LIBRARIES_CUSTOM_REGISTRY_HPP +struct custom_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry + +// Only the *declaration* above precedes the include. The definition below is +// what the BOOST_OPENMETHOD macros, virtual_ptr and unique_virtual_ptr bind +// to; it just has to be complete before the first of them. #include -#include +#include #include // default_registry, with the "vector" and "hash" policies removed and vptr_map @@ -33,13 +39,6 @@ static_assert(!boost::mp11::mp_contains< custom_registry::policy_list, boost::openmethod::policies::fast_perfect_hash>::value); -// Must be set before core.hpp is included (just below), so that the -// BOOST_OPENMETHOD macros, virtual_ptr and unique_virtual_ptr all default to -// custom_registry. -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry - -#include - // Where each macro goes: // // extern template ... BOOST_SYMBOL_IMPORT - header, every client TU diff --git a/test/test_capture_errors.hpp b/test/test_capture_errors.hpp index 86b98d71..ddfaabee 100644 --- a/test/test_capture_errors.hpp +++ b/test/test_capture_errors.hpp @@ -6,11 +6,15 @@ #ifndef BOOST_OPENMETHOD_TEST_CAPTURE_ERRORS_HPP #define BOOST_OPENMETHOD_TEST_CAPTURE_ERRORS_HPP -// Deliberately depends only on preamble.hpp, not core.hpp: it must be -// includable before a test defines its own registry and sets -// BOOST_OPENMETHOD_DEFAULT_REGISTRY, which has to happen before the first -// inclusion of core.hpp (directly or transitively). -#include +// This header owns the whole registry recipe for the tests that capture +// diagnostics: the declaration, the BOOST_OPENMETHOD_DEFAULT_REGISTRY +// definition, the library include, and `test_registry` itself. Including it +// first - before anything that pulls in core.hpp - is all a test has to do, +// and there is no ordering left for a caller to get wrong. +struct test_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include #include #include @@ -25,6 +29,9 @@ struct capture_output : boost::openmethod::policies::output { }; }; +struct test_registry + : boost::openmethod::default_registry::with {}; + template struct capture_errors { using error_handler = typename Registry::error_handler; diff --git a/test/test_checked_registry.hpp b/test/test_checked_registry.hpp new file mode 100644 index 00000000..b9a31c10 --- /dev/null +++ b/test/test_checked_registry.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_TEST_CHECKED_REGISTRY_HPP +#define BOOST_OPENMETHOD_TEST_CHECKED_REGISTRY_HPP + +// This header owns the whole registry recipe for the tests that expect a +// registration error to be thrown: the declaration, the +// BOOST_OPENMETHOD_DEFAULT_REGISTRY definition, the library include, and +// `test_registry` itself. Including it first - before anything that pulls in +// core.hpp - is all a test has to do, and there is no ordering left for a +// caller to get wrong. +// +// `runtime_checks` catches what initialize() cannot; `throw_error_handler` +// turns the diagnosis into an exception the test can catch. +struct test_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +struct test_registry : boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks, + boost::openmethod::policies::throw_error_handler> {}; + +#endif diff --git a/test/test_class_registration_missing_base_class.cpp b/test/test_class_registration_missing_base_class.cpp index 790cb677..e1f395b9 100644 --- a/test/test_class_registration_missing_base_class.cpp +++ b/test/test_class_registration_missing_base_class.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include -#include +#include "test_checked_registry.hpp" -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #define BOOST_TEST_MODULE class_registration_missing_base_class diff --git a/test/test_class_registration_unknown_class_call.cpp b/test/test_class_registration_unknown_class_call.cpp index ea2bb000..25467986 100644 --- a/test/test_class_registration_unknown_class_call.cpp +++ b/test/test_class_registration_unknown_class_call.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include -#include +#include "test_checked_registry.hpp" -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #define BOOST_TEST_MODULE class_registration_unknown_class_call diff --git a/test/test_class_registration_unknown_class_overrider.cpp b/test/test_class_registration_unknown_class_overrider.cpp index 47a11862..0781b49f 100644 --- a/test/test_class_registration_unknown_class_overrider.cpp +++ b/test/test_class_registration_unknown_class_overrider.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include -#include +#include "test_checked_registry.hpp" -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #define BOOST_TEST_MODULE class_registration_unknown_class_overrider diff --git a/test/test_core.cpp b/test/test_core.cpp index 23a9f653..13ee4473 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -9,16 +9,14 @@ #define BOOST_TEST_MODULE core #include -#include +#include +#include +#include using namespace boost::openmethod; using namespace boost::openmethod::detail; namespace mp11 = boost::mp11; -#include -#include -#include - #include "test_util.hpp" namespace test_virtual { diff --git a/test/test_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp index 83d26822..e56a2de2 100644 --- a/test/test_custom_rtti_deferred.cpp +++ b/test/test_custom_rtti_deferred.cpp @@ -3,7 +3,13 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include +struct test_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include namespace { constexpr std::size_t non_polymorphic_high_bit = std::size_t(1) @@ -27,11 +33,25 @@ struct Animal { Animal(const char* name, std::size_t type) : name(name), type(type) { } + virtual auto cast_aux(std::size_t to_type) -> void* { + return to_type == static_type ? this : nullptr; + } + static std::size_t last_type_id; static std::size_t static_type; std::size_t type; }; +// Casting to a virtually derived class cannot go through static_cast, so the +// policy below routes it through `dynamic_cast_ref`, which lands here. +template +auto custom_dynamic_cast(Base& obj) -> Derived { + using derived_type = std::remove_cv_t>; + return *reinterpret_cast( + const_cast&>(obj).cast_aux( + derived_type::static_type)); +} + std::size_t Animal::last_type_id; std::size_t Animal::static_type = ++Animal::last_type_id; @@ -53,6 +73,32 @@ struct Cat : Animal { std::size_t Cat::static_type = ++Animal::last_type_id; +struct Bat : virtual Animal { + Bat(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + auto cast_aux(std::size_t to_type) -> void* override { + return to_type == static_type ? this : Animal::cast_aux(to_type); + } + + static std::size_t static_type; +}; + +std::size_t Bat::static_type = ++Animal::last_type_id; + +struct Owl : virtual Animal { + Owl(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + auto cast_aux(std::size_t to_type) -> void* override { + return to_type == static_type ? this : Animal::cast_aux(to_type); + } + + static std::size_t static_type; +}; + +std::size_t Owl::static_type = ++Animal::last_type_id; + struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { template struct fn : defaults { @@ -81,14 +127,20 @@ struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { template static void type_name(boost::openmethod::type_id type, Stream& stream) { - static const char* name[] = {"?", "Animal", "Dog", "Cat"}; + static const char* name[] = {"?", "Animal", "Dog", + "Cat", "Bat", "Owl"}; auto idx = reinterpret_cast(type); - stream << (idx >= 1 && idx <= 3 ? name[idx] : "?"); + stream << (idx >= 1 && idx <= 5 ? name[idx] : "?"); } static auto type_index(boost::openmethod::type_id type) { return type; } + + template + static auto dynamic_cast_ref(Base&& obj) -> Derived { + return custom_dynamic_cast(obj); + } }; }; @@ -96,18 +148,13 @@ struct test_registry : boost::openmethod::default_registry::with::without< boost::openmethod::policies::type_hash> {}; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #define BOOST_TEST_MODULE custom_rtti_deferred #include #include using namespace boost::openmethod; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, Bat, Owl); BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); @@ -126,6 +173,22 @@ BOOST_OPENMETHOD_OVERRIDE(meet, (Dog&, Dog&, std::ostream& os), void) { os << "Both wag tails."; } +// Bat and Owl derive virtually, so reaching these overriders goes through +// `dynamic_cast_ref` rather than a static_cast - with type ids that are only +// assigned during static construction. + +BOOST_OPENMETHOD_OVERRIDE(poke, (Bat & bat, std::ostream& os), void) { + os << bat.name << " screeches."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Owl & owl, std::ostream& os), void) { + os << owl.name << " hoots."; +} + +BOOST_OPENMETHOD_OVERRIDE(meet, (Bat&, Owl&, std::ostream& os), void) { + os << "The bat evades the owl."; +} + BOOST_AUTO_TEST_CASE(custom_rtti_deferred) { initialize(); @@ -148,4 +211,25 @@ BOOST_AUTO_TEST_CASE(custom_rtti_deferred) { meet(a, a, os); BOOST_TEST(os.str() == "Both wag tails."); } + + // virtual bases: dispatch and cast both go through the deferred ids + Animal &&c = Bat("Echo"), &&d = Owl("Hedwig"); + + { + std::stringstream os; + poke(c, os); + BOOST_TEST(os.str() == "Echo screeches."); + } + + { + std::stringstream os; + poke(d, os); + BOOST_TEST(os.str() == "Hedwig hoots."); + } + + { + std::stringstream os; + meet(c, d, os); + BOOST_TEST(os.str() == "The bat evades the owl."); + } } diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp index 8873e401..98f70647 100644 --- a/test/test_custom_rtti_simple.cpp +++ b/test/test_custom_rtti_simple.cpp @@ -3,7 +3,13 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include +struct test_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include namespace { constexpr std::size_t non_polymorphic_high_bit = std::size_t(1) @@ -88,11 +94,6 @@ struct test_registry : boost::openmethod::default_registry::with::without< boost::openmethod::policies::type_hash> {}; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #define BOOST_TEST_MODULE custom_rtti_simple #include #include diff --git a/test/test_custom_rtti_simple_projection.cpp b/test/test_custom_rtti_simple_projection.cpp index 8ed21fc7..d2f06b3b 100644 --- a/test/test_custom_rtti_simple_projection.cpp +++ b/test/test_custom_rtti_simple_projection.cpp @@ -3,7 +3,11 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include +struct test_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include namespace { template @@ -76,11 +80,6 @@ struct custom_rtti : boost::openmethod::policies::rtti { struct test_registry : boost::openmethod::default_registry::with { }; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #define BOOST_TEST_MODULE custom_rtti_simple_projection #include #include diff --git a/test/test_custom_rtti_virtual_base.cpp b/test/test_custom_rtti_virtual_base.cpp index 2434a08c..0ccb51b9 100644 --- a/test/test_custom_rtti_virtual_base.cpp +++ b/test/test_custom_rtti_virtual_base.cpp @@ -3,7 +3,13 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include +struct test_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include namespace { constexpr std::size_t non_polymorphic_high_bit = std::size_t(1) @@ -113,11 +119,6 @@ struct test_registry : boost::openmethod::default_registry::with::without< boost::openmethod::policies::type_hash> {}; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #define BOOST_TEST_MODULE custom_rtti_virtual_base #include #include diff --git a/test/test_inplace_vptr.cpp b/test/test_inplace_vptr.cpp index 012d987d..74360f91 100644 --- a/test/test_inplace_vptr.cpp +++ b/test/test_inplace_vptr.cpp @@ -3,15 +3,7 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include -#include - -#include - -namespace bom = boost::openmethod; -struct test_registry : bom::default_registry::without< - bom::policies::vptr, bom::policies::type_hash> {}; - +struct test_registry; #define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry #include @@ -19,6 +11,13 @@ struct test_registry : bom::default_registry::without< #include #include +#include +#include + +namespace bom = boost::openmethod; +struct test_registry : bom::default_registry::without< + bom::policies::vptr, bom::policies::type_hash> {}; + #define BOOST_TEST_MODULE intrusive #include diff --git a/test/test_policies.cpp b/test/test_policies.cpp index 0026eb06..af35b617 100644 --- a/test/test_policies.cpp +++ b/test/test_policies.cpp @@ -9,7 +9,7 @@ #define BOOST_TEST_MODULE policies #include -#include +#include #include "test_util.hpp" diff --git a/test/test_runtime_errors_bad_call.cpp b/test/test_runtime_errors_bad_call.cpp index e0a08f0b..1cb696d1 100644 --- a/test/test_runtime_errors_bad_call.cpp +++ b/test/test_runtime_errors_bad_call.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #include "test_util.hpp" diff --git a/test/test_runtime_errors_bad_call_type_ids.cpp b/test/test_runtime_errors_bad_call_type_ids.cpp index e4719f02..00df1707 100644 --- a/test/test_runtime_errors_bad_call_type_ids.cpp +++ b/test/test_runtime_errors_bad_call_type_ids.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #include "test_util.hpp" diff --git a/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp b/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp index e753072f..3bbe4292 100644 --- a/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp +++ b/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #include diff --git a/test/test_runtime_errors_call_unknown_class.cpp b/test/test_runtime_errors_call_unknown_class.cpp index 521d149d..6530eb2d 100644 --- a/test/test_runtime_errors_call_unknown_class.cpp +++ b/test/test_runtime_errors_call_unknown_class.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #include "test_util.hpp" diff --git a/test/test_runtime_errors_duplicate_overrider.cpp b/test/test_runtime_errors_duplicate_overrider.cpp index c457e47b..f387bf7f 100644 --- a/test/test_runtime_errors_duplicate_overrider.cpp +++ b/test/test_runtime_errors_duplicate_overrider.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #define BOOST_TEST_MODULE runtime_errors_duplicate_overrider diff --git a/test/test_runtime_errors_initialize_unknown_class.cpp b/test/test_runtime_errors_initialize_unknown_class.cpp index 35727f51..1dc50856 100644 --- a/test/test_runtime_errors_initialize_unknown_class.cpp +++ b/test/test_runtime_errors_initialize_unknown_class.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #include "test_util.hpp" diff --git a/test/test_runtime_errors_no_initialization.cpp b/test/test_runtime_errors_no_initialization.cpp index c3984fe7..2c257dc9 100644 --- a/test/test_runtime_errors_no_initialization.cpp +++ b/test/test_runtime_errors_no_initialization.cpp @@ -3,16 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; - -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include #include #include diff --git a/test/test_runtime_errors_throw_error.cpp b/test/test_runtime_errors_throw_error.cpp index c1eb8873..81d9ea33 100644 --- a/test/test_runtime_errors_throw_error.cpp +++ b/test/test_runtime_errors_throw_error.cpp @@ -3,16 +3,15 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include -#include - -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::throw_error_handler> {}; - +struct test_registry; #define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry #include #include +#include + +struct test_registry : boost::openmethod::default_registry::with< + boost::openmethod::policies::throw_error_handler> {}; #include "test_util.hpp" diff --git a/test/test_static_rtti.cpp b/test/test_static_rtti.cpp index 8277b6d0..536a03dd 100644 --- a/test/test_static_rtti.cpp +++ b/test/test_static_rtti.cpp @@ -3,20 +3,19 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#define BOOST_TEST_MODULE openmethod -#include +struct static_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry -#include +#include #include +#include +#include struct static_registry : boost::openmethod::registry {}; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry - -#include -#include -#include +#define BOOST_TEST_MODULE openmethod +#include struct Animal {};