From 64c17b97faba6f94574671cc549bb613eb7fc0d3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 23 Aug 2026 12:24:22 -0400 Subject: [PATCH 1/4] doc: forward-declare the registry instead of including preamble.hpp Overriding BOOST_OPENMETHOD_DEFAULT_REGISTRY forced users to learn the header layering: include a "pre-core" header (preamble.hpp or default_registry.hpp), define the registry, #define the macro, then include . ref_headers.adoc codified that as a "Pre-Core Headers" section, and custom_rtti.adoc told the reader not to include yet. None of it is necessary. 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 - so the registry can be forward-declared before the include and defined after it, where has already supplied registry, default_registry, indirect_registry and the five stock policies: 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<>> {}; A registry the library provides needs no declaration at all: core.hpp includes default_registry.hpp *before* it tests the macro, so #define plus the include is the whole recipe. preamble.hpp and default_registry.hpp now appear nowhere outside include/. Three rules, all documented: the registry must be complete before the first construct that instantiates it (a hard error otherwise, never silent); it must name a class declared with the same class-key as the definition (a mismatch is MSVC C4099, an error under /W4 /WX); and the name must be qualified if it could also be found in namespace boost::openmethod - `registry` in particular, which otherwise resolves to boost::openmethod::registry and fails with "missing template arguments". Migrated the 17 tests that override the default, plus test_policies.cpp. test_capture_errors.hpp now includes ; test_core.cpp drops a redundant preamble.hpp; dynamic_loading/registry.hpp loses its whole #ifndef fallback, which only existed because the alias preceded the include. The two custom_rtti step-by-step examples get a tag::setup[] region so tag::policy[] no longer opens with an include. ref_headers.adoc renames "Pre-Core Headers" to "Policy Headers" and demotes preamble.hpp and default_registry.hpp to "Headers Included by Other Headers". registries_and_policies.adoc becomes the canonical home of the recipe; its reference to , a file that does not exist, is fixed. shared_libraries.adoc's my_registry.hpp listing gains the #define and include it was missing - as written it never actually overrode the default. Removed doc/modules/ROOT/examples/deferred_custom_rtti.cpp: an orphan no page included, whose virtual-inheritance and dynamic_cast_ref coverage test_custom_rtti_virtual_base.cpp already provides. Also dropped three stray "end::" tags with no opener, and the dead Quickbook markers in examples/static_rtti.cpp. Closes #90 Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 53 ++++++ .../examples/custom_rtti/1/custom_rtti.cpp | 16 +- .../examples/custom_rtti/2/custom_rtti.cpp | 16 +- .../ROOT/examples/deferred_custom_rtti.cpp | 165 ------------------ doc/modules/ROOT/examples/static_rtti.cpp | 15 +- .../ROOT/examples/throw_error_handler.cpp | 13 +- doc/modules/ROOT/pages/custom_rtti.adoc | 29 +-- doc/modules/ROOT/pages/ref_headers.adoc | 53 ++++-- .../ROOT/pages/registries_and_policies.adoc | 41 ++++- doc/modules/ROOT/pages/shared_libraries.adoc | 22 ++- doc/modules/ROOT/snippets/static_rtti.cpp | 12 +- include/boost/openmethod/core.hpp | 44 ++++- include/boost/openmethod/default_registry.hpp | 2 +- include/boost/openmethod/macros.hpp | 4 +- .../boost/openmethod/policies/static_rtti.hpp | 4 +- test/CMakeLists.txt | 10 +- test/dynamic_loading/registry.hpp | 20 +-- .../custom_registry/registry.hpp | 15 +- test/test_capture_errors.hpp | 8 +- ..._class_registration_missing_base_class.cpp | 11 +- ..._class_registration_unknown_class_call.cpp | 11 +- ...s_registration_unknown_class_overrider.cpp | 11 +- test/test_core.cpp | 8 +- test/test_custom_rtti_deferred.cpp | 13 +- test/test_custom_rtti_simple.cpp | 13 +- test/test_custom_rtti_simple_projection.cpp | 11 +- test/test_custom_rtti_virtual_base.cpp | 13 +- test/test_inplace_vptr.cpp | 17 +- test/test_policies.cpp | 2 +- test/test_runtime_errors_bad_call.cpp | 11 +- .../test_runtime_errors_bad_call_type_ids.cpp | 11 +- ...ime_errors_bad_call_type_ids_smart_ptr.cpp | 13 +- ...test_runtime_errors_call_unknown_class.cpp | 11 +- ...est_runtime_errors_duplicate_overrider.cpp | 11 +- ...untime_errors_initialize_unknown_class.cpp | 11 +- .../test_runtime_errors_no_initialization.cpp | 13 +- test/test_runtime_errors_throw_error.cpp | 11 +- test/test_static_rtti.cpp | 15 +- 38 files changed, 355 insertions(+), 404 deletions(-) delete mode 100644 doc/modules/ROOT/examples/deferred_custom_rtti.cpp diff --git a/CLAUDE.md b/CLAUDE.md index 0e83b6e3..a69cfc1f 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,56 @@ 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 `` and **defined after** it. +Do not reintroduce the old "include a pre-core header, define the registry, `#define`, then +include" idiom - `preamble.hpp` and `default_registry.hpp` appear nowhere outside `include/`, +and the greps in *Development Workflow* enforce that. + +```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`, `core.hpp:376`) - 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`. + `#define BOOST_OPENMETHOD_DEFAULT_REGISTRY registry` silently resolves to + `boost::openmethod::registry` and fails with `missing template arguments`; `::registry` works. + +`test/CMakeLists.txt` scans each `test_*.cpp` for the token `BOOST_OPENMETHOD_DEFAULT_REGISTRY` +and withholds the shared PCH from any file that has it - a force-included PCH would still +precede the `#define`. That detection is unaffected by the ordering, but the count matters: +17 files under `test/` carry the macro. + ### 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 8662f3e3..56692806 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 98004dcf..2bbd2d2a 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 72e8fd62..00000000 --- a/doc/modules/ROOT/examples/deferred_custom_rtti.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2018-2027 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 index a452d319..e3724615 100644 --- a/doc/modules/ROOT/examples/static_rtti.cpp +++ b/doc/modules/ROOT/examples/static_rtti.cpp @@ -3,22 +3,19 @@ // 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 {}; - +struct static_registry; #define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry #include +#include #include #include #include +struct static_registry + : boost::openmethod::registry {}; + struct Animal {}; struct Dog : Animal {}; @@ -52,5 +49,3 @@ int main() { return 0; } - -//] diff --git a/doc/modules/ROOT/examples/throw_error_handler.cpp b/doc/modules/ROOT/examples/throw_error_handler.cpp index 4f77b794..516d5ef8 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..363309b5 100644 --- a/doc/modules/ROOT/pages/custom_rtti.adoc +++ b/doc/modules/ROOT/pages/custom_rtti.adoc @@ -87,9 +87,21 @@ 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. + +[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 +135,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 at the top of the file. 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. diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 03c25e67..f71475ca 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -104,46 +104,43 @@ parameters. *The headers below are for advanced use*. -## Pre-Core Headers +## Policy 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`. +Each stock policy lives in a header of its own. The five 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. ### 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). - -### 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. +the C standard error stream (not using iostreams). Part of `default_registry`. ### link:{headers-url}/boost/openmethod/policies/static_rtti.hpp[] @@ -159,3 +156,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..9a54adc7 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -11,11 +11,43 @@ xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], take an 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_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 +_before_ including ``. The value of the symbol is used as +a default template parameter for `use_classes`, `method`, `virtual_ptr`, and +others. Once the header 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 control how type information is obtained, how vptrs are acquired, how errors are @@ -65,7 +97,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++] ---- diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index 0861c919..2d230dc6 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,10 +238,10 @@ 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 ``. @@ -277,6 +279,11 @@ A custom registry is shared exactly the same way - name it instead of [source,c++] ---- // my_registry.hpp +struct my_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY my_registry + +#include + struct my_registry : boost::openmethod::registry {}; #ifdef OWNS_REGISTRY_STATE @@ -286,6 +293,11 @@ BOOST_OPENMETHOD_IMPORT_REGISTRY(my_registry); #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 5fc0f98a..d06b10d5 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 cedd132a..da91385e 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, +//! ``. 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,7 @@ 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. +//! ``. 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 1b63669c..10f7cb90 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -89,7 +89,7 @@ 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 +//! `` 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 4ebbb9de..f85e3b67 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -177,7 +177,7 @@ 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 +//! `` is included. Changing the value of this symbol //! has no effect after that point. //! //! @par Example @@ -536,7 +536,7 @@ 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 +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when `` is //! included. 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 da02ce42..b5442a6c 100644 --- a/include/boost/openmethod/policies/static_rtti.hpp +++ b/include/boost/openmethod/policies/static_rtti.hpp @@ -21,8 +21,8 @@ namespace boost::openmethod::policies { //! //! @par Example //! -//! Selecting the policy, which has to happen before `` -//! is included: +//! Selecting the policy. The registry is declared before +//! ``, and defined after it: //! //! include:static_rtti.cpp#registry //! diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f22005e3..c0695e99 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -66,11 +66,11 @@ 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 detected (by +# scanning for the macro) and left without a PCH. set(BOOST_OPENMETHOD_TEST_PCH_HEADERS diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index 6421d7f3..0e97f500 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 54783759..85b101de 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 8eb02485..0fb9cb3c 100644 --- a/test/test_capture_errors.hpp +++ b/test/test_capture_errors.hpp @@ -6,11 +6,9 @@ #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 +// Include after the #define that selects the test's registry - which is the +// first line of every test that uses this header, so this is automatic. +#include #include #include diff --git a/test/test_class_registration_missing_base_class.cpp b/test/test_class_registration_missing_base_class.cpp index 1b1b6332..06415335 100644 --- a/test/test_class_registration_missing_base_class.cpp +++ b/test/test_class_registration_missing_base_class.cpp @@ -3,18 +3,17 @@ // 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 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 #include diff --git a/test/test_class_registration_unknown_class_call.cpp b/test/test_class_registration_unknown_class_call.cpp index 1688d631..64945211 100644 --- a/test/test_class_registration_unknown_class_call.cpp +++ b/test/test_class_registration_unknown_class_call.cpp @@ -3,18 +3,17 @@ // 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 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 #include diff --git a/test/test_class_registration_unknown_class_overrider.cpp b/test/test_class_registration_unknown_class_overrider.cpp index 2e4f8b6b..4843a3f6 100644 --- a/test/test_class_registration_unknown_class_overrider.cpp +++ b/test/test_class_registration_unknown_class_overrider.cpp @@ -3,18 +3,17 @@ // 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 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 #include diff --git a/test/test_core.cpp b/test/test_core.cpp index bc4543c1..96963dee 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 ef954da6..07520cff 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) @@ -96,11 +102,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_deferred #include #include diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp index 4a8f5a0f..f87a8141 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 7054bf72..0da31f7f 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 f85edd12..be9f29a2 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 6d4b2a12..5270947f 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 019a84e0..b24a2e5e 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 c01e2661..ee176944 100644 --- a/test/test_runtime_errors_bad_call.cpp +++ b/test/test_runtime_errors_bad_call.cpp @@ -3,18 +3,17 @@ // 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 "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" #define BOOST_TEST_MODULE runtime_errors_bad_call diff --git a/test/test_runtime_errors_bad_call_type_ids.cpp b/test/test_runtime_errors_bad_call_type_ids.cpp index 36df5218..501893f9 100644 --- a/test/test_runtime_errors_bad_call_type_ids.cpp +++ b/test/test_runtime_errors_bad_call_type_ids.cpp @@ -3,18 +3,17 @@ // 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 "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" #define BOOST_TEST_MODULE runtime_errors_bad_call_type_ids 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 f56072e6..0c40313f 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,19 +3,18 @@ // 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 {}; - +struct test_registry; #define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry #include #include #include +#include "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + #include "test_util.hpp" #define BOOST_TEST_MODULE runtime_errors_bad_call_type_ids_smart_ptr diff --git a/test/test_runtime_errors_call_unknown_class.cpp b/test/test_runtime_errors_call_unknown_class.cpp index c23af798..6a75653a 100644 --- a/test/test_runtime_errors_call_unknown_class.cpp +++ b/test/test_runtime_errors_call_unknown_class.cpp @@ -3,18 +3,17 @@ // 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 "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" #define BOOST_TEST_MODULE runtime_errors_call_unknown_class diff --git a/test/test_runtime_errors_duplicate_overrider.cpp b/test/test_runtime_errors_duplicate_overrider.cpp index 9e0f0263..06efcc41 100644 --- a/test/test_runtime_errors_duplicate_overrider.cpp +++ b/test/test_runtime_errors_duplicate_overrider.cpp @@ -3,18 +3,17 @@ // 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 "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 #include diff --git a/test/test_runtime_errors_initialize_unknown_class.cpp b/test/test_runtime_errors_initialize_unknown_class.cpp index 2295744d..6923eae5 100644 --- a/test/test_runtime_errors_initialize_unknown_class.cpp +++ b/test/test_runtime_errors_initialize_unknown_class.cpp @@ -3,18 +3,17 @@ // 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 "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" #define BOOST_TEST_MODULE runtime_errors_initialize_unknown_class diff --git a/test/test_runtime_errors_no_initialization.cpp b/test/test_runtime_errors_no_initialization.cpp index b3079f4a..64630bb7 100644 --- a/test/test_runtime_errors_no_initialization.cpp +++ b/test/test_runtime_errors_no_initialization.cpp @@ -3,19 +3,18 @@ // 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 {}; - +struct test_registry; #define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry #include #include #include +#include "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + #include "test_util.hpp" #define BOOST_TEST_MODULE runtime_errors_no_initialization diff --git a/test/test_runtime_errors_throw_error.cpp b/test/test_runtime_errors_throw_error.cpp index 156d6301..faf70729 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 56174f22..b8c878e4 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 {}; From 2f5636bc6c192c0dfbf2b8dc6d27c4d274e2f3ff Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 23 Aug 2026 13:27:51 -0400 Subject: [PATCH 2/4] fix: unbreak the GCC build, and address review of the registry recipe The doc comment added for BOOST_OPENMETHOD_DEFAULT_REGISTRY ended a `//!` line with a backslash, making it a line continuation. GCC's -Wcomment (in -Wall) rejects that under -Werror, so every GCC job on both CIs failed while every clang, MSVC and Xcode job passed. Put the #define on one line. Verified by reproducing the exact diagnostic with the pre-fix header and confirming g++ -Wall -Wextra -Werror accepts the fixed one. Restore the coverage lost with deferred_custom_rtti.cpp. That example was the only place combining deferred_static_rtti with virtual bases and dynamic_cast_ref: test_custom_rtti_virtual_base.cpp has the virtual bases and the cast but derives from policies::rtti, and test_custom_rtti_deferred.cpp had neither. Add Bat and Owl, virtually derived, plus the dynamic_cast_ref the cast now needs - `requires_dynamic_cast` is true exactly when static_cast is ill-formed, so these overriders reach the policy hook. Confirmed by sabotaging dynamic_cast_ref and watching the test fail. Name core.hpp, not , as the point where the macro is read. initialize.hpp, inplace_vptr.hpp and every interop/*.hpp also include core.hpp, so a TU that includes one of those before the #define binds silently to default_registry. Four places still said otherwise. test_capture_errors.hpp now owns the whole recipe - declaration, #define, include and test_registry - instead of the seven tests repeating it. There is no ordering left for a caller to get wrong, which is what the previous version's comment had claimed without being able to guarantee it. Those tests no longer name the macro, so test/CMakeLists.txt would have handed them the shared PCH and silently defeated the override; teach its scan to follow the header too. Documentation fixes from the same review: - ref_headers.adoc claimed every stock policy has its own header; indirect_vptr, runtime_checks and deferred_static_rtti live in preamble.hpp. Say so, and note runtime_checks joins default_registry under BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS. - custom_rtti.adoc pointed at a declaration "at the top of the file" that sits below the classes listing, and its Deferred RTTI section never showed the declaration step at all - example 2's setup and registry tags were unrendered. - error_handling.adoc's listing opened with five lines of registry boilerplate its prose never introduced. - shared_libraries.adoc's my_registry.hpp is a complete header now, so give it the include guard the animals.hpp it is compared to has. - CLAUDE.md cited a line number that had already moved, pointed at greps in a section that has none, miscounted the files carrying the macro, and described an unqualified `registry` as resolving silently when it is a hard error. Remove doc/modules/ROOT/examples/static_rtti.cpp: an orphan no page references, left marker-free by the previous commit, duplicating snippets/static_rtti.cpp - which exercises the same policy, classes and dispatch, is the copy the docs render, and asserts more. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 37 +++++--- doc/modules/ROOT/examples/static_rtti.cpp | 51 ----------- doc/modules/ROOT/pages/custom_rtti.adoc | 20 ++++- doc/modules/ROOT/pages/error_handling.adoc | 8 +- doc/modules/ROOT/pages/ref_headers.adoc | 11 ++- .../ROOT/pages/registries_and_policies.adoc | 22 ++--- doc/modules/ROOT/pages/shared_libraries.adoc | 8 +- include/boost/openmethod/core.hpp | 9 +- include/boost/openmethod/default_registry.hpp | 3 +- include/boost/openmethod/macros.hpp | 10 ++- .../boost/openmethod/policies/static_rtti.hpp | 3 +- test/CMakeLists.txt | 18 +++- test/test_capture_errors.hpp | 13 ++- test/test_custom_rtti_deferred.cpp | 89 ++++++++++++++++++- test/test_runtime_errors_bad_call.cpp | 9 +- .../test_runtime_errors_bad_call_type_ids.cpp | 9 +- ...ime_errors_bad_call_type_ids_smart_ptr.cpp | 9 +- ...test_runtime_errors_call_unknown_class.cpp | 9 +- ...est_runtime_errors_duplicate_overrider.cpp | 9 +- ...untime_errors_initialize_unknown_class.cpp | 9 +- .../test_runtime_errors_no_initialization.cpp | 9 +- 21 files changed, 209 insertions(+), 156 deletions(-) delete mode 100644 doc/modules/ROOT/examples/static_rtti.cpp diff --git a/CLAUDE.md b/CLAUDE.md index a69cfc1f..c5250713 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -335,10 +335,15 @@ is a single symbol) and `test/implicit_shared_libraries/`. ### Overriding the default registry -The registry is **forward-declared** before `` and **defined after** it. -Do not reintroduce the old "include a pre-core header, define the registry, `#define`, then -include" idiom - `preamble.hpp` and `default_registry.hpp` appear nowhere outside `include/`, -and the greps in *Development Workflow* enforce that. +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; @@ -370,18 +375,22 @@ 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`, `core.hpp:376`) - never - silent. + 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`. - `#define BOOST_OPENMETHOD_DEFAULT_REGISTRY registry` silently resolves to - `boost::openmethod::registry` and fails with `missing template arguments`; `::registry` works. - -`test/CMakeLists.txt` scans each `test_*.cpp` for the token `BOOST_OPENMETHOD_DEFAULT_REGISTRY` -and withholds the shared PCH from any file that has it - a force-included PCH would still -precede the `#define`. That detection is unaffected by the ordering, but the count matters: -17 files under `test/` carry the macro. +- **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/static_rtti.cpp b/doc/modules/ROOT/examples/static_rtti.cpp deleted file mode 100644 index e3724615..00000000 --- a/doc/modules/ROOT/examples/static_rtti.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2018-2027 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) - -struct static_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry - -#include -#include -#include -#include - -#include - -struct static_registry - : boost::openmethod::registry {}; - -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/pages/custom_rtti.adoc b/doc/modules/ROOT/pages/custom_rtti.adoc index 363309b5..14f91586 100644 --- a/doc/modules/ROOT/pages/custom_rtti.adoc +++ b/doc/modules/ROOT/pages/custom_rtti.adoc @@ -92,7 +92,8 @@ 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. +known when `` is parsed - which any header that +includes it, `` among them, does for you. [source,c++] ---- @@ -135,8 +136,8 @@ small, dense range. It means that we can use them as indexes in a vector. include::{example}/1/custom_rtti.cpp[tag=registry] ---- -This is the definition promised by the declaration at the top of the file. It -must appear before the first `BOOST_OPENMETHOD`, `BOOST_OPENMETHOD_OVERRIDE` or +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. @@ -173,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 f71475ca..3003bb6b 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -106,7 +106,7 @@ parameters. ## Policy Headers -Each stock policy lives in a header of its own. The five that make up +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 @@ -115,6 +115,11 @@ 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. Part of @@ -142,6 +147,10 @@ the library aborts the program. Part of `default_registry`. Provides an implementation of the `output` policy that writes diagnostics to the C standard error stream (not using iostreams). Part of `default_registry`. +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[] Provides a minimal implementation of the `rtti` policy that does not depend on diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index 9a54adc7..e7e0002e 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -7,14 +7,16 @@ 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 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: @@ -84,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 @@ -148,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`. @@ -162,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 2d230dc6..716a6fd2 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -243,7 +243,8 @@ 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 @@ -279,6 +280,9 @@ 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 @@ -290,6 +294,8 @@ struct my_registry : boost::openmethod::registry {}; BOOST_OPENMETHOD_EXPORT_REGISTRY(my_registry); #else BOOST_OPENMETHOD_IMPORT_REGISTRY(my_registry); +#endif + #endif ---- diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index da91385e..c5884357 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -35,14 +35,14 @@ //! //! `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 use a registry that the library provides, name it in the macro: //! //! @code -//! #define BOOST_OPENMETHOD_DEFAULT_REGISTRY \ -//! boost::openmethod::indirect_registry +//! #define BOOST_OPENMETHOD_DEFAULT_REGISTRY boost::openmethod::indirect_registry //! #include //! @endcode //! @@ -1972,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 10f7cb90..64af1b0d 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 f85e3b67..d88d4ac7 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 b5442a6c..5c42d657 100644 --- a/include/boost/openmethod/policies/static_rtti.hpp +++ b/include/boost/openmethod/policies/static_rtti.hpp @@ -22,7 +22,8 @@ namespace boost::openmethod::policies { //! @par Example //! //! Selecting the policy. The registry is declared before -//! ``, and defined after it: +//! `` (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 c0695e99..8da121be 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,8 +69,14 @@ endfunction() # 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 detected (by -# scanning for the macro) and left without a PCH. +# 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 owns the whole +# recipe for the tests that capture diagnostics, so those name neither the macro +# nor their registry. 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 +97,13 @@ 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") + 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/test_capture_errors.hpp b/test/test_capture_errors.hpp index 0fb9cb3c..1f007dbf 100644 --- a/test/test_capture_errors.hpp +++ b/test/test_capture_errors.hpp @@ -6,8 +6,14 @@ #ifndef BOOST_OPENMETHOD_TEST_CAPTURE_ERRORS_HPP #define BOOST_OPENMETHOD_TEST_CAPTURE_ERRORS_HPP -// Include after the #define that selects the test's registry - which is the -// first line of every test that uses this header, so this is automatic. +// 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 @@ -23,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_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp index 07520cff..a8ac8b76 100644 --- a/test/test_custom_rtti_deferred.cpp +++ b/test/test_custom_rtti_deferred.cpp @@ -33,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; @@ -59,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 { @@ -87,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); + } }; }; @@ -108,7 +154,7 @@ struct test_registry 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); @@ -127,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(); @@ -149,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_runtime_errors_bad_call.cpp b/test/test_runtime_errors_bad_call.cpp index ee176944..3dfbbe26 100644 --- a/test/test_runtime_errors_bad_call.cpp +++ b/test/test_runtime_errors_bad_call.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; +#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 501893f9..3aaaefdc 100644 --- a/test/test_runtime_errors_bad_call_type_ids.cpp +++ b/test/test_runtime_errors_bad_call_type_ids.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; +#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 0c40313f..96e8b93a 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,18 +3,11 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry +#include "test_capture_errors.hpp" -#include #include #include -#include "test_capture_errors.hpp" - -struct test_registry - : boost::openmethod::default_registry::with {}; - #include "test_util.hpp" #define BOOST_TEST_MODULE runtime_errors_bad_call_type_ids_smart_ptr diff --git a/test/test_runtime_errors_call_unknown_class.cpp b/test/test_runtime_errors_call_unknown_class.cpp index 6a75653a..eb5a9f49 100644 --- a/test/test_runtime_errors_call_unknown_class.cpp +++ b/test/test_runtime_errors_call_unknown_class.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; +#include #include "test_util.hpp" diff --git a/test/test_runtime_errors_duplicate_overrider.cpp b/test/test_runtime_errors_duplicate_overrider.cpp index 06efcc41..0c86b555 100644 --- a/test/test_runtime_errors_duplicate_overrider.cpp +++ b/test/test_runtime_errors_duplicate_overrider.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; +#include #define BOOST_TEST_MODULE runtime_errors_duplicate_overrider #include diff --git a/test/test_runtime_errors_initialize_unknown_class.cpp b/test/test_runtime_errors_initialize_unknown_class.cpp index 6923eae5..77e3e1c2 100644 --- a/test/test_runtime_errors_initialize_unknown_class.cpp +++ b/test/test_runtime_errors_initialize_unknown_class.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry - -#include -#include - #include "test_capture_errors.hpp" -struct test_registry - : boost::openmethod::default_registry::with {}; +#include #include "test_util.hpp" diff --git a/test/test_runtime_errors_no_initialization.cpp b/test/test_runtime_errors_no_initialization.cpp index 64630bb7..3ff0495f 100644 --- a/test/test_runtime_errors_no_initialization.cpp +++ b/test/test_runtime_errors_no_initialization.cpp @@ -3,18 +3,11 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry +#include "test_capture_errors.hpp" -#include #include #include -#include "test_capture_errors.hpp" - -struct test_registry - : boost::openmethod::default_registry::with {}; - #include "test_util.hpp" #define BOOST_TEST_MODULE runtime_errors_no_initialization From f82c8d516ebb625b29113508aa04f64d0faab03f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 23 Aug 2026 13:48:17 -0400 Subject: [PATCH 3/4] test: give the registration-error tests a shared registry header Finishes the deduplication started for the capture-diagnostics tests. The three test_class_registration_*.cpp files repeated the same six-line prologue and the same `default_registry::with` definition verbatim; test_checked_registry.hpp now owns the whole recipe, so including it first is all they do and there is no ordering left to get wrong. Teach the PCH scan in test/CMakeLists.txt about the new header, as with test_capture_errors.hpp: those files no longer name the macro, and a force-included PCH would precede the #define and silently bind the macros to default_registry. Verified from build.ninja that all three stay off the shared PCH, and that defeating the override makes the tests fail rather than pass quietly - they assert BOOST_CHECK_THROW(..., missing_class), which needs runtime_checks and throw_error_handler. Co-Authored-By: Claude Opus 5 --- test/CMakeLists.txt | 12 ++++---- test/test_checked_registry.hpp | 28 +++++++++++++++++++ ..._class_registration_missing_base_class.cpp | 9 +----- ..._class_registration_unknown_class_call.cpp | 9 +----- ...s_registration_unknown_class_overrider.cpp | 9 +----- 5 files changed, 38 insertions(+), 29 deletions(-) create mode 100644 test/test_checked_registry.hpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8da121be..c404d5c1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -73,10 +73,11 @@ endfunction() # 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 owns the whole -# recipe for the tests that capture diagnostics, so those name neither the macro -# nor their registry. Miss one and it still compiles: the macros simply bind to -# default_registry and the test's expectations fail at run time. +# 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 @@ -98,7 +99,8 @@ foreach(test_cpp ${test_cpp_files}) file(READ ${test_cpp} test_cpp_contents) set(test_cpp_overrides_registry -1) - foreach(marker "BOOST_OPENMETHOD_DEFAULT_REGISTRY" "test_capture_errors.hpp") + 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}) diff --git a/test/test_checked_registry.hpp b/test/test_checked_registry.hpp new file mode 100644 index 00000000..ad4f28c3 --- /dev/null +++ b/test/test_checked_registry.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2018-2027 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 06415335..fba98f0f 100644 --- a/test/test_class_registration_missing_base_class.cpp +++ b/test/test_class_registration_missing_base_class.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry +#include "test_checked_registry.hpp" -#include #include -#include - -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; #define BOOST_TEST_MODULE class_registration_missing_base_class #include diff --git a/test/test_class_registration_unknown_class_call.cpp b/test/test_class_registration_unknown_class_call.cpp index 64945211..7ba4749a 100644 --- a/test/test_class_registration_unknown_class_call.cpp +++ b/test/test_class_registration_unknown_class_call.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry +#include "test_checked_registry.hpp" -#include #include -#include - -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; #define BOOST_TEST_MODULE class_registration_unknown_class_call #include diff --git a/test/test_class_registration_unknown_class_overrider.cpp b/test/test_class_registration_unknown_class_overrider.cpp index 4843a3f6..05010ab1 100644 --- a/test/test_class_registration_unknown_class_overrider.cpp +++ b/test/test_class_registration_unknown_class_overrider.cpp @@ -3,16 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -struct test_registry; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry +#include "test_checked_registry.hpp" -#include #include -#include - -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; #define BOOST_TEST_MODULE class_registration_unknown_class_overrider #include From 9e0e299dceb8fba273cb1987be8ab01925f35aeb Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 23 Aug 2026 20:43:11 -0400 Subject: [PATCH 4/4] ci: exclude test sources from coverage The codecov report counted test/ alongside include/, so the only red check on this PR was three unreachable lines in test_custom_rtti_deferred.cpp: the cast_aux null return and the Bat/Owl delegation-to-base branches, which exist to make the custom cast well-formed for every pair but that no call reaches. Test code is not the thing the coverage target is measuring. The exclusion was already in the file, commented out, but spelled `test/**/*` as the boost-ci sample ships it. That spelling does not do what it looks like: codecov compiles it to (?s:test/.*/[^\/]*)\Z, which requires a second slash, so it matches only the subdirectories of test/ and leaves every top-level test/test_*.cpp counted - which is where nearly all the test code is. `test/**` compiles to (?s:test/.*)\Z and matches at any depth. Verified against the paths codecov actually reports, which are repo-relative despite boost-ci building inside the superproject. Comment records the trap and the validate endpoint. Co-Authored-By: Claude Opus 5 --- .codecov.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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/**