diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2d97c38..d7518a9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: uses: boostorg/boost-ci/.github/workflows/reusable.yml@master with: exclude_cxxstd: '98,03,0x,11,14' - exclude_compiler: gcc-4.9,gcc-5,gcc-6,gcc-7,clang-3.9,clang-4.0,clang-5.0,clang-6.0,clang-7,clang-16 + exclude_compiler: gcc-4.9,gcc-5,gcc-6,gcc-7,gcc-8,gcc-9,clang-3.9,clang-4.0,clang-5.0,clang-6.0,clang-7,clang-16 # exclude clang-16 because it seems to use the c++14 standard library on ubuntu-24.04: # include/c++/14/bits/stl_pair.h:410:35: error: no matching function for call to 'get' # maybe similar to this: https://github.com/actions/runner-images/issues/9679 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..d476a5f1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,356 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Boost.OpenMethod is a C++17 header-only library implementing open multi-methods (multiple dispatch). Unlike traditional virtual functions where dispatch occurs only on the first (`this`) parameter, open methods dispatch based on the runtime types of multiple arguments. + +**Key Characteristics:** +- C++17 required +- Header-only library +- Part of the Boost ecosystem +- Supports both CMake and Boost.Build (b2) + +## Build System + +### CMake Build + +**Basic build:** +```bash +mkdir build && cd build +cmake .. -DBOOST_SRC_DIR=/path/to/boost +cmake --build . +``` + +**Build with tests:** +```bash +cmake .. -DBOOST_OPENMETHOD_BUILD_TESTS=ON +cmake --build . --target tests +ctest +``` + +**Build with examples:** +```bash +cmake .. -DBOOST_OPENMETHOD_BUILD_TESTS=ON -DBOOST_OPENMETHOD_BUILD_EXAMPLES=ON +cmake --build . +``` + +**Important CMake options:** +- `BOOST_OPENMETHOD_BUILD_TESTS` - Enable tests (default: ON if root project) +- `BOOST_OPENMETHOD_BUILD_EXAMPLES` - Enable examples (requires tests enabled) +- `BOOST_OPENMETHOD_WARNINGS_AS_ERRORS` - Treat warnings as errors +- `BOOST_SRC_DIR` - Path to Boost source directory (default: `../..` or `$BOOST_SRC_DIR` env var) + +### Boost.Build (b2) + +**Build and test:** +```bash +b2 test +``` + +**Quick test (for CI):** +```bash +b2 test//quick +``` + +## Testing + +### Running All Tests (CMake) +```bash +cd build +ctest +``` + +### Running a Single Test (CMake) +```bash +cd build +ctest -R test_dispatch # Run specific test by name +# or directly +./boost_openmethod-test_dispatch +``` + +### Test Structure +- Test files: `test/test_*.cpp` - Standard unit tests using Boost.Test +- Compile-fail tests: `test/compile_fail_*.cpp` - Tests that should fail to compile +- Mixed build test: `test/mix_release_debug/` - Tests mixing debug/release builds +- Dynamic loading test: `test/dynamic_loading/` - Tests shared library support (requires Boost.DLL) +- 21+ test files covering dispatch, policies, virtual_ptr, RTTI, errors, etc. + +### Debug Mode Features +When building in Debug mode (`CMAKE_BUILD_TYPE=Debug`), runtime checks are automatically enabled via `BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS`. + +## Architecture + +### Layered Design + +The library is structured in three conceptual layers: + +1. **Preamble Layer** ([preamble.hpp](include/boost/openmethod/preamble.hpp)) + - Foundational types: `type_id`, `vptr_type`, `virtual_` + - Registry and policy framework + - Error types: `not_initialized`, `bad_call`, `no_overrider`, `ambiguous_call`, etc. + - No executable dispatch code + +2. **Core API** ([core.hpp](include/boost/openmethod/core.hpp)) + - `method` - Method implementation + - `virtual_ptr` - "Wide pointer" combining object pointer + v-table pointer + - Dispatch algorithms: `resolve_uni()` (single dispatch), `resolve_multi_*()` (multiple dispatch) + - Override registration via `override_impl<>` + - Class registration via `use_classes<>` + +3. **Macro Layer** ([macros.hpp](include/boost/openmethod/macros.hpp)) + - `BOOST_OPENMETHOD(name, params, return_type)` - Declare method + - `BOOST_OPENMETHOD_OVERRIDE(name, params, return_type)` - Declare overrider + - `BOOST_OPENMETHOD_CLASSES(classes...)` - Register class hierarchy + - Generates static registrar objects for automatic registration + +### Key Concepts + +**Open Methods**: Functions where dispatch depends on runtime types of multiple parameters, not just the first. + +**Virtual Parameters**: Parameters marked with `virtual_` or `virtual_ptr` that participate in dispatch. + +**Registries**: Template-parameterized contexts holding classes, methods, and policies. Default: `boost::openmethod::default_registry`. + +**Policies**: Pluggable components controlling behavior: +- `rtti` - Type identification (std_rtti, static_rtti, custom) +- `vptr` - V-table storage (vptr_vector, vptr_map) +- `type_hash` - Type ID hashing (fast_perfect_hash with hash_fn function object) +- `error_handler` - Error handling strategy (default_error_handler, throw_error_handler) +- `output` - Diagnostic output destination (stderr_output) +- `attributes` - Visibility/DLL decoration (dllexport, dllimport, local) + +**Dispatch Mechanisms**: +- Single dispatch: Direct v-table lookup `vtbl[slot]` +- Multi-dispatch: Stride-based indexing through multi-dimensional dispatch tables + +**virtual_ptr**: A "wide pointer" combining object pointer with v-table pointer +for efficient dispatch. Key for enabling dispatch on non-polymorphic or smart +pointer types. + +### Component Interaction + +``` +User Code → Macros → Core API → Preamble → Policies + ↓ + Static Registration +``` + +Static initializers generated by macros call core API functions to register +classes, methods, and overriders. The `initialize()` function builds dispatch +tables before first use. + +## Code Conventions + +### Formatting +The project uses clang-format with an LLVM-based style: +- `AlignAfterOpenBracket: AlwaysBreak` +- `AllowShortFunctionsOnASingleLine: false` +- No short blocks, if statements, or loops on single lines + +### Compiler Requirements +Tests require these C++17 features (checked by Boost.Build): +- auto nontype template params +- deduction guides +- fold expressions +- if constexpr +- inline variables +- structured bindings +- ``, ``, `` headers + +## Common Development Patterns + +### Working with Shared Libraries / DLL Support + +**Overview**: The library supports shared library usage on Windows with proper dllexport/dllimport decoration. + +**Key Pattern - Decoratable Static Variables**: +All policy static variables use `BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(name)` macro (in `preamble.hpp`) to enable DLL decoration. This generates three specializations of `static_`: +- Default (no attributes) +- `BOOST_SYMBOL_EXPORT` when registry has dllexport attributes +- `BOOST_SYMBOL_IMPORT` when registry has dllimport attributes + +The attributes are selected via `get_attributes` which uses ADL to call `boost_openmethod_declspec(Guide)`. + +**Affected Policies** (actual symbol names from `nm -D`): +- `stderr_output::fn::os` - Output stream (via `static_os`) +- `default_error_handler::fn::handler` - Error handler function (via `static_handler`) +- `fast_perfect_hash::fn::hash_fn` - Hash factors struct (via `static_hash_fn`) +- `vptr_map::fn::vptrs` - V-table pointer map (via `static_vptrs`) +- `vptr_vector::fn::vptr_vector_vptrs` / `vptr_vector_indirect_vptrs` - V-table vectors +- Registry state itself: `static_st>::st` - class/method/overrider lists + +**`declspec` types** (in `preamble.hpp`): +- `dllexport` — marks the owning library (exports static variables) +- `dllimport` — marks client libraries (imports from the owning library) +- `declspec_none` — no-op; use on non-Windows where dllexport/dllimport are unnecessary + +**Example Usage**: +```cpp +// In header shared between library and client +#if !defined(_MSC_VER) +#define MY_API boost::openmethod::declspec_none +#elif defined(MY_LIBRARY_EXPORTS) +#define MY_API boost::openmethod::dllexport +#else +#define MY_API boost::openmethod::dllimport +#endif + +namespace boost::openmethod { + MY_API boost_openmethod_declspec(default_registry_attributes); +} + +BOOST_OPENMETHOD(my_method, (virtual_ptr), void, MY_API); +``` + +**Critical: Exporting the Registry State**: +`static_st>::st` (the list of registered classes/methods/overriders) is only +instantiated and exported from a shared library when that library compiles code that actually +registers classes or methods (i.e., `BOOST_OPENMETHOD_CLASSES` or `BOOST_OPENMETHOD` macros). +If a library is meant to "own" the registry state, it must include a header that triggers these +registrations — it is not enough to just declare `boost_openmethod_declspec`. + +See `doc/modules/ROOT/examples/shared_libs/` and `test/dynamic_loading/` for complete examples. + +**Dynamic Loading Test** (`test/dynamic_loading/`): +Verifies shared state across libraries. Each library exports a single `dl_XXX_get_policy_ids() -> const void**` (null-terminated array of policy state addresses) plus `dl_XXX_get_method_fn()` where applicable. The array is built at first call using: +```cpp +namespace mp11 = boost::mp11; // alias, not 'using namespace' — avoids detail:: ambiguity +// ... +mp11::mp_for_each([&](auto p) { + using P = decltype(p); + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } +}); +``` +Files: +- `registry.hpp` — sets up `REGISTRY_API` macro and `boost_openmethod_declspec` declaration +- `method.hpp` — sets up `METHOD_API` macro and declares the `speak` method +- `classes.hpp` — class definitions + `BOOST_OPENMETHOD_CLASSES` (included by lib_registry with dllexport to force `st` export) +- `lib_registry.cpp` — compiled with `REGISTRY_API=dllexport`, exports all registry statics +- `lib_method.cpp` — compiled with `METHOD_API=dllexport`, imports registry from lib_registry +- `lib_overrider.cpp` — dynamically loaded at runtime, adds a Dog overrider +- `main.cpp` — loads lib_overrider, compares all `id()` arrays element-by-element, calls `initialize()`, tests dispatch + +### 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/`. + +### Multiple Registries +Registries are completely independent. Use separate registries to: +- Isolate method sets +- Apply different policies to different method families +- Enable coexistence of incompatible configurations + +Registry type must be specified consistently across related methods and classes. + +## File Organization + +- `include/boost/openmethod/` - Public headers + - `core.hpp`, `macros.hpp`, `preamble.hpp` - Main headers + - `initialize.hpp` - Dispatch table construction + - `default_registry.hpp` - Default policy configuration + - `detail/` - Internal implementation details + - `policies/` - Policy implementations + - `interop/` - Interoperability with other systems +- `test/` - Unit tests and compile-fail tests +- `doc/modules/ROOT/examples/` - Example programs +- `doc/modules/ROOT/pages/` - AsciiDoc documentation + +## Dependencies (Boost Libraries) + +Required: +- Boost.Assert +- Boost.Config +- Boost.Core +- Boost.DynamicBitset +- Boost.MP11 (metaprogramming) +- Boost.Preprocessor + +For testing: +- Boost.Test +- Boost.SmartPtr + +For examples: +- Boost.DLL (shared library examples) + +## Development Workflow + +1. Make changes to headers in `include/boost/openmethod/` +2. Build tests: `cmake --build build --target tests` +3. Run tests: `cd build && ctest` +4. For changes affecting examples: enable `BOOST_OPENMETHOD_BUILD_EXAMPLES` +5. Submit PRs against the `develop` branch + +## Important Implementation Details + +### Static Registration +Classes, methods, and overriders register automatically via static constructors. This happens before `main()`. The `initialize()` function must be called before first method invocation to build dispatch tables. + +### Dispatch Table Construction +The `initialize()` function: +1. Collects registered classes and overriders +2. Builds class hierarchy using provided inheritance relationships +3. Constructs dispatch tables using perfect hashing +4. Validates configuration (in debug mode or with runtime_checks policy) + +### Virtual Pointer Mechanics +`virtual_ptr` stores both object pointer and v-table pointer. It can be constructed from: +- Raw pointers (requires prior `use_classes` registration) +- Smart pointers (std::unique_ptr, std::shared_ptr, boost::intrusive_ptr) +- References +- Other virtual_ptr instances + +The v-table pointer enables O(1) method dispatch. + +### Policy Static Variables Pattern + +When adding static variables to policies: + +1. **Declare the variable storage** in `detail` namespace using `BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(variable_name)` (defined in `preamble.hpp`) +2. **Use type alias** in policy's `fn` class: `using var_storage = detail::static_variable_name` +3. **Access via storage**: `var_storage::variable_name` instead of direct static member +4. **Definition is generated** by the macro: `template Type detail::static_variable_name::variable_name;` + +This pattern ensures static variables can be properly decorated with dllexport/dllimport for shared library usage. + +5. **Add an `id()` function** to the policy's `fn` class returning the address of the first byte of the state. Its presence is detectable via `detail::has_id

` (a variable template generated by `BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(has_id, id)` in `preamble.hpp`): +```cpp +static auto id() -> const void* { + return &static_::variable_name; +} +``` +For policies with two possible state variables (e.g., `vptr_vector`), use `if constexpr` to select the active one: +```cpp +static auto id() -> const void* { + if constexpr (Registry::has_indirect_vptr) { + return &static_::vptr_vector_indirect_vptrs; + } else { + return &static_::vptr_vector_vptrs; + } +} +``` +For `stderr_output`, which inherits its state, use the class name: `&fn::os`. + +**Example from fast_perfect_hash**: +```cpp +// In detail namespace +struct hash_fn { + std::size_t mult, shift, min_value, max_value; + auto operator()(type_id type) const -> std::size_t { + return (mult * reinterpret_cast(type)) >> shift; + } +}; +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(hash_fn); // generates static_hash_fn + +// In policy +template +class fn { + using factors_storage = detail::static_hash_fn; +public: + static auto hash(type_id type) -> std::size_t { + return factors_storage::hash_fn(type); // Use via storage + } +}; +``` diff --git a/doc/modules/ROOT/examples/CMakeLists.txt b/doc/modules/ROOT/examples/CMakeLists.txt index 6d1f85b4..722c3198 100644 --- a/doc/modules/ROOT/examples/CMakeLists.txt +++ b/doc/modules/ROOT/examples/CMakeLists.txt @@ -26,6 +26,8 @@ foreach (cpp ${cpp_files}) add_dependencies(tests ${test_target}) endforeach() +add_subdirectory(shared_libs) + function(boost_openmethod_add_step_by_step dir) set(add_test "") if(ARGC GREATER 1) @@ -60,7 +62,3 @@ boost_openmethod_add_step_by_step(ambiguities OFF) boost_openmethod_add_step_by_step(core_api) boost_openmethod_add_step_by_step(custom_rtti) boost_openmethod_add_step_by_step(virtual_ptr_alt) - -if (NOT WIN32) - add_subdirectory(shared_libs) -endif() 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 891a03f7..15875eb1 100644 --- a/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp +++ b/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp @@ -54,18 +54,22 @@ struct custom_rtti : boost::openmethod::policies::rtti { template static auto static_type() { if constexpr (is_polymorphic) { - return reinterpret_cast(T::static_type); + return reinterpret_cast( + static_cast(T::static_type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } template static auto dynamic_type(const T& obj) { if constexpr (is_polymorphic) { - return reinterpret_cast(obj.type); + return reinterpret_cast( + static_cast(obj.type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } }; 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 9c9b37c0..5ebb8259 100644 --- a/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp +++ b/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp @@ -50,18 +50,22 @@ struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { template static auto static_type() { if constexpr (is_polymorphic) { - return reinterpret_cast(T::static_type); + return reinterpret_cast( + static_cast(T::static_type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } template static auto dynamic_type(const T& obj) { if constexpr (is_polymorphic) { - return reinterpret_cast(obj.type); + return reinterpret_cast( + static_cast(obj.type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } }; diff --git a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt index 64aaabf5..228185aa 100644 --- a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt +++ b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt @@ -7,88 +7,55 @@ message(STATUS "Boost.OpenMethod: building shared library examples") add_compile_definitions(BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS) -# All targets output to the same directory so that executables can locate shared -# libraries via boost::dll::program_location().parent_path(). -set(shared_libs_output_dir "${CMAKE_CURRENT_BINARY_DIR}") - -# Helper: add a CTest fixture that builds a CMake target, so that `ctest` alone -# (without a prior `cmake --build`) still works. -function(openmethod_shared_libs_build_fixture target) - add_test(NAME ${target}-build - COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" - --target ${target} --config $) - set_tests_properties(${target}-build PROPERTIES FIXTURES_SETUP ${target}-fixture) -endfunction() - # ------------------------------------------------------------------------------ # static linking -add_library(boost_openmethod-shared SHARED extensions.cpp) -target_link_libraries(boost_openmethod-shared Boost::openmethod) -set_target_properties(boost_openmethod-shared PROPERTIES - ENABLE_EXPORTS ON - OUTPUT_NAME shared - LIBRARY_OUTPUT_DIRECTORY "${shared_libs_output_dir}" - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) +# add_library(boost_openmethod-shared SHARED extensions.cpp) +# target_link_libraries(boost_openmethod-shared Boost::openmethod) +# set_target_properties(boost_openmethod-shared PROPERTIES +# ENABLE_EXPORTS ON +# OUTPUT_NAME shared +# ) -add_executable(boost_openmethod-static static_main.cpp) -target_link_libraries(boost_openmethod-static Boost::openmethod Boost::dll boost_openmethod-shared) -set_target_properties(boost_openmethod-static PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) -openmethod_shared_libs_build_fixture(boost_openmethod-static) -add_test(NAME boost_openmethod-static - COMMAND "${shared_libs_output_dir}/boost_openmethod-static") -set_tests_properties(boost_openmethod-static PROPERTIES - FIXTURES_REQUIRED boost_openmethod-static-fixture) +# add_executable(boost_openmethod-static static_main.cpp) +# target_link_libraries(boost_openmethod-static Boost::openmethod Boost::dll boost_openmethod-shared) +# add_test(NAME boost_openmethod-static COMMAND boost_openmethod-static) # ------------------------------------------------------------------------------ # dynamic loading, direct virtual_ptrs add_executable(boost_openmethod-dynamic dynamic_main.cpp) -set_target_properties(boost_openmethod-dynamic PROPERTIES - ENABLE_EXPORTS ON - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) +set_target_properties(boost_openmethod-dynamic PROPERTIES ENABLE_EXPORTS ON) target_link_libraries(boost_openmethod-dynamic Boost::openmethod Boost::dll) -add_dependencies(boost_openmethod-dynamic boost_openmethod-shared) -if (NOT WIN32) - openmethod_shared_libs_build_fixture(boost_openmethod-shared) - openmethod_shared_libs_build_fixture(boost_openmethod-dynamic) - add_test(NAME boost_openmethod-dynamic - COMMAND "${shared_libs_output_dir}/boost_openmethod-dynamic") - set_tests_properties(boost_openmethod-dynamic PROPERTIES - FIXTURES_REQUIRED "boost_openmethod-shared-fixture;boost_openmethod-dynamic-fixture") -endif() -# ------------------------------------------------------------------------------ -# dynamic loading, indirect virtual_ptrs +add_library(boost_openmethod-shared SHARED extensions.cpp) +target_link_libraries(boost_openmethod-shared PRIVATE Boost::openmethod boost_openmethod-dynamic) +set_target_properties(boost_openmethod-shared PROPERTIES ENABLE_EXPORTS ON) -add_library(boost_openmethod-indirect_shared SHARED indirect_extensions.cpp) -target_compile_definitions( - boost_openmethod-indirect_shared PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) -target_link_libraries(boost_openmethod-indirect_shared PRIVATE Boost::openmethod Boost::dll) -set_target_properties(boost_openmethod-indirect_shared PROPERTIES - ENABLE_EXPORTS ON - OUTPUT_NAME indirect_shared - LIBRARY_OUTPUT_DIRECTORY "${shared_libs_output_dir}" - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) +add_test(NAME boost_openmethod-dynamic_shared COMMAND boost_openmethod-dynamic) -add_executable(boost_openmethod-indirect indirect_main.cpp) -target_compile_definitions( - boost_openmethod-indirect PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) -set_target_properties(boost_openmethod-indirect PROPERTIES - ENABLE_EXPORTS ON - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" +# NOTE: build the following target (or ALL) when working on this. Just building +# boost_openmethod-dynamic will *not* build the DLL. +add_custom_target(boost_openmethod-shared-all ALL + DEPENDS boost_openmethod-dynamic boost_openmethod-shared ) -target_link_libraries(boost_openmethod-indirect PRIVATE Boost::openmethod Boost::dll) -add_dependencies(boost_openmethod-indirect boost_openmethod-indirect_shared) -if (NOT WIN32) - openmethod_shared_libs_build_fixture(boost_openmethod-indirect) - add_test(NAME boost_openmethod-indirect - COMMAND "${shared_libs_output_dir}/boost_openmethod-indirect") - set_tests_properties(boost_openmethod-indirect PROPERTIES - FIXTURES_REQUIRED boost_openmethod-indirect-fixture) -endif() + +# ------------------------------------------------------------------------------ +# dynamic loading, indirect virtual_ptrs + +# add_library(boost_openmethod-indirect_shared SHARED indirect_extensions.cpp) +# target_compile_definitions( +# boost_openmethod-indirect_shared PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) +# target_link_libraries(boost_openmethod-indirect_shared PRIVATE Boost::openmethod Boost::dll) +# set_target_properties(boost_openmethod-indirect_shared PROPERTIES +# ENABLE_EXPORTS ON +# OUTPUT_NAME indirect_shared +# ) + +# add_executable(boost_openmethod-indirect indirect_main.cpp) +# target_compile_definitions( +# boost_openmethod-indirect PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) +# set_target_properties(boost_openmethod-indirect PROPERTIES ENABLE_EXPORTS ON) +# target_link_libraries(boost_openmethod-indirect PRIVATE Boost::openmethod Boost::dll) +# add_dependencies(boost_openmethod-indirect boost_openmethod-indirect_shared) +# add_test(NAME boost_openmethod-indirect COMMAND boost_openmethod-indirect) diff --git a/doc/modules/ROOT/examples/shared_libs/animals.hpp b/doc/modules/ROOT/examples/shared_libs/animals.hpp index e725355c..79d2cea2 100644 --- a/doc/modules/ROOT/examples/shared_libs/animals.hpp +++ b/doc/modules/ROOT/examples/shared_libs/animals.hpp @@ -8,9 +8,33 @@ // clang-format off -// tag::content[] // animals.hpp +#include + +#ifdef BOOST_CLANG +#pragma clang diagnostic ignored "-Wundefined-var-template" +#endif + +#ifdef BOOST_GCC +//#pragma GCC diagnostic ignored "-Wundefined-var-template" +#endif + +// tag::content[] +#ifdef _WIN32 +#ifdef LIBRARY_NAME +#define ANIMALS_API boost::openmethod::dllexport +#else +#define ANIMALS_API boost::openmethod::dllimport +#endif +#else +#define ANIMALS_API boost::openmethod::declspec_none +#endif + +namespace boost::openmethod { + ANIMALS_API boost_openmethod_declspec(default_registry&); +} + #include #include @@ -18,11 +42,16 @@ struct Animal { virtual ~Animal() {} }; struct Herbivore : Animal {}; struct Carnivore : Animal {}; +struct Cow : Herbivore {}; +struct Wolf : Carnivore {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Herbivore, Cow, Carnivore, Wolf); + BOOST_OPENMETHOD( meet, ( boost::openmethod::virtual_ptr, boost::openmethod::virtual_ptr), - std::string); + std::string, ANIMALS_API); // end::content[] #endif diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index b74ea6dc..bb24ac49 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -3,15 +3,8 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef LIBRARY_NAME -#ifdef _MSC_VER -#define LIBRARY_NAME "shared.dll" -#else -#define LIBRARY_NAME "libshared.so" -#endif -#endif +#define LIBRARY_NAME "boost_openmethod-shared" -// tag::before[] // dynamic_main.cpp #include "animals.hpp" @@ -23,12 +16,31 @@ #include #include -using namespace boost::openmethod::aliases; +using namespace boost::openmethod; + +#ifdef _WIN32 + +static_assert(!std::is_same_v< + BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::declspec, + void>); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::declspec, + dllexport>); -struct Cow : Herbivore {}; -struct Wolf : Carnivore {}; +static_assert(!std::is_same_v); +static_assert(std::is_same_v); -BOOST_OPENMETHOD_CLASSES(Animal, Herbivore, Cow, Wolf, Carnivore); +#endif + +// tag::before[] +// dynamic_main.cpp + +BOOST_OPENMETHOD_CLASSES(Herbivore, Cow, Carnivore, Wolf); BOOST_OPENMETHOD_OVERRIDE( meet, (virtual_ptr, virtual_ptr), std::string) { @@ -39,64 +51,75 @@ BOOST_OPENMETHOD_OVERRIDE( int main() { // end::load[] // end::unload[] - std::cout << "Before loading the shared library.\n"; - - boost::openmethod::initialize(); - - std::cout << "cow meets wolf -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - std::cout << "wolf meets cow -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - - // to be continued... - // end::before[] - // tag::load[] - // ... - - std::cout << "\nAfter loading the shared library.\n"; - - boost::dll::shared_library lib( - boost::dll::program_location().parent_path() / LIBRARY_NAME, - boost::dll::load_mode::rtld_now); - boost::openmethod::initialize(); - - std::cout << "cow meets wolf -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // run - std::cout << "wolf meets cow -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // hunt - - auto make_tiger = lib.get("make_tiger"); - std::cout << "cow meets tiger -> " - << meet( - *std::make_unique(), - *std::unique_ptr(make_tiger())) - << "\n"; // hunt - // end::load[] - - // tag::unload[] - // ... - - std::cout << "\nAfter unloading the shared library.\n"; - - lib.unload(); - boost::openmethod::initialize(); - - std::cout << "cow meets wolf -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - std::cout << "wolf meets cow -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - // tag::before[] - // tag::load[] - // tag::unload[] + + try { + std::cout << "Before loading the shared library.\n"; + + boost::openmethod::initialize(trace::from_env()); + BOOST_ASSERT(default_registry::static_vptr != nullptr); + + std::cout << "cow meets wolf -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + std::cout << "wolf meets cow -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + + // to be continued... + // end::before[] + // tag::load[] + // ... + + std::cout << "\nLoading shared object / DLL.\n"; + + boost::dll::shared_library lib( + LIBRARY_NAME, + boost::dll::load_mode::rtld_global | + boost::dll::load_mode::append_decorations); + + boost::openmethod::initialize(trace::from_env()); + + std::cout << "cow meets wolf -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // run + std::cout << "wolf meets cow -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // hunt + + auto make_tiger = lib.get("make_tiger"); + std::cout << "cow meets tiger -> " + << meet( + *std::make_unique(), + *std::unique_ptr(make_tiger())) + << "\n"; // hunt + // end::load[] + + // tag::unload[] + // ... + + std::cout << "\nAfter unloading the shared library.\n"; + + lib.unload(); + boost::openmethod::initialize(trace::from_env()); + + std::cout << "cow meets wolf -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + std::cout << "wolf meets cow -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + // tag::before[] + // tag::load[] + // tag::unload[] + + // end::before[] + // end::load[] + // end::unload[] + + } catch (const std::exception& ex) { + std::cerr << "Exception: " << ex.what() << '\n'; + return 1; + } return 0; } -// end::before[] -// end::load[] -// end::unload[] diff --git a/doc/modules/ROOT/examples/shared_libs/extensions.cpp b/doc/modules/ROOT/examples/shared_libs/extensions.cpp index ab21f041..fb3d7305 100644 --- a/doc/modules/ROOT/examples/shared_libs/extensions.cpp +++ b/doc/modules/ROOT/examples/shared_libs/extensions.cpp @@ -3,14 +3,31 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -// tag::content[] // extensions.cpp #include "animals.hpp" -using namespace boost::openmethod::aliases; +using namespace boost::openmethod; + +#ifdef _WIN32 + +static_assert(std::is_same_v); +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::declspec, + dllimport>); + +#endif + +// tag::content[] BOOST_OPENMETHOD_OVERRIDE( - meet, (virtual_ptr, virtual_ptr), std::string) { + meet, (virtual_ptr a, virtual_ptr b), std::string) { + auto p = BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::next; + BOOST_ASSERT(p); + BOOST_ASSERT(p(a, b) == "greet"); return "run"; } @@ -28,6 +45,7 @@ extern "C" { __declspec(dllexport) #endif auto make_tiger() -> Animal* { + BOOST_ASSERT(default_registry::static_vptr != nullptr); return new Tiger; } } diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index 3fc689e1..92d9e59b 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -36,8 +36,10 @@ If a library only uses its own registries, for example, if using open-methods as an implementation detail, it has its own global data, and there is no need to call `initialize`. -Let's look at an example. The following header is included by the program and -the shared library: +Let's look at an example. The following header is shared between the program and +the dynamically loaded library. It defines the class hierarchy, the `meet` +method, and sets up declspec decoration for Windows compatibility (explained in +the <> section below): [source,c++] ---- @@ -52,8 +54,7 @@ The shared library contains an object that adds two overriders, a new class, include::{shared}/extensions.cpp[tag=content] ---- -The main program adds a couple of classes then calls `meet` method. At this -point, we only have the catch-call overrider: +The main program provides a catch-all overrider, then calls the `meet` method: [source,c++] ---- @@ -79,36 +80,85 @@ include::{shared}/dynamic_main.cpp[tag=unload] ## Windows -If we try the example on Windows, the result is disappointing: - -``` -Before loading the shared library. -cow meets wolf -> greet -wolf meets cow -> greet - -After loading the shared library. -cow meets wolf -> greet -wolf meets cow -> greet -cow meets tiger -> unknown class struct Tiger -``` - -What happens here is that the program and the DLL have their own copies of -"global" variables. When the DLL is loaded, its static constructors run, and -they add overriders to _their_ copy of the method (the `method::fn` static -variable for the given name and signature). They are ignored when the main -program calls `initialize`. - -Likewise, `BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore)` in the DLL adds `Tiger` -to the DLL's copy of the registry. For the perspective of the program's -registry, the class does not exist. - -In theory, this can be fixed by adding `__declspec(dllimport)` and -`__declspec(dllexport)` attributes where needed. However, this is not practical, -because programs and DLLs can both import and export registries and methods. The -underlying objects are instantiated from templates, which complicates the -matter. Research is being done on this subject. However, as of now, dynamic -loading is supported on Windows only if it does not attempt to share a registry -across modules. +On Windows, by default, each module (executable or DLL) receives its own copy of +global variables. Without special measures, the registry state, +dispatch tables, and method function pointers are duplicated: when a DLL's +static constructors register classes and overriders, they populate the DLL's own +copy of the registry, invisible to the main program. + +OpenMethod solves this with _declspec decoration_. Three types are provided in +`namespace boost::openmethod`: + +- `dllexport` -- marks the _owning_ module, which defines and exports shared + state +- `dllimport` -- marks _client_ modules, which import shared state from the + owner +- `declspec_none` -- a no-op for non-Windows platforms, enabling portable + headers + +The `animals.hpp` header shown above demonstrates the pattern. The key elements +are: + +1. A preprocessor block that selects `dllexport` or `dllimport` depending on + which module is being compiled. The owning module (the executable, in this + example) defines a distinguishing macro; client modules (the DLL) do not: ++ +[source,c++] +---- +#ifdef _WIN32 +#ifdef LIBRARY_NAME +#define ANIMALS_API boost::openmethod::dllexport +#else +#define ANIMALS_API boost::openmethod::dllimport +#endif +#else +#define ANIMALS_API boost::openmethod::declspec_none +#endif +---- + +2. A `boost_openmethod_declspec` function declaration in `namespace + boost::openmethod` that tells the library how to decorate the registry's + static variables: ++ +[source,c++] +---- +namespace boost::openmethod { + ANIMALS_API boost_openmethod_declspec(default_registry&); +} +---- + +3. The declspec type passed as a fourth argument to `BOOST_OPENMETHOD`, + decorating the method's function pointer and associated statics: ++ +[source,c++] +---- +BOOST_OPENMETHOD(meet, (...), std::string, ANIMALS_API); +---- + +### CMake Setup + +On Windows, the DLL must link against the executable to resolve the imported +symbols. This is the reverse of the typical linking direction: + +[source,cmake] +---- +add_executable(my_app dynamic_main.cpp) +set_target_properties(my_app PROPERTIES ENABLE_EXPORTS ON) +target_link_libraries(my_app Boost::openmethod Boost::dll) + +add_library(my_plugin SHARED extensions.cpp) +target_link_libraries(my_plugin PRIVATE Boost::openmethod my_app) +---- + +`ENABLE_EXPORTS ON` on the executable tells CMake to generate an import library +(`.lib` on MSVC) that the DLL links against. On POSIX, `ENABLE_EXPORTS` adds +`-rdynamic`, which is the only setup needed. + +NOTE: The owning module must compile code that triggers static registration -- +it must include headers containing `BOOST_OPENMETHOD_CLASSES` or +`BOOST_OPENMETHOD` macros. Simply declaring `boost_openmethod_declspec` is not +sufficient; the registry state is only instantiated and exported when +registration code references it. ## Indirect Vptrs diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 7c2d3837..60894834 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -360,7 +360,7 @@ struct use_class_aux> } // coverity[uninit] - zero-initialized static storage - Registry::classes.push_back(*this); + Registry::static_::st.classes.push_back(*this); } void resolve_type_ids() { @@ -370,7 +370,7 @@ struct use_class_aux> } ~use_class_aux() { - Registry::classes.remove(*this); + Registry::static_::st.classes.remove(*this); } }; @@ -1927,9 +1927,12 @@ struct virtual_traits&, Registry> { // ============================================================================= // Method +auto boost_openmethod_declspec(...) -> void; namespace detail { +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(fn); + template struct select_overrider_virtual_type_aux { using type = void; @@ -1999,9 +2002,8 @@ struct init_bad_call { } }; -template -using method_base = std::conditional_t< - Registry::has_deferred_static_rtti, deferred_method_info, method_info>; +template +class method_base; template struct parameter_traits { @@ -2142,31 +2144,16 @@ template< class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY> class method; -//! Method with a specific id, signature and return type -//! -//! `method` implements an open-method that takes a parameter list - -//! `Parameters` - and returns a `ReturnType`. -//! -//! `Parameters` must contain at least one virtual parameter, i.e. a parameter -//! that has a type in the form `virtual_ptr` or `virtual\_`. -//! The dynamic types of the virtual arguments are taken into account to select -//! the overrider to call. -//! -//! @see method -//! -//! @tparam Id A type representing the method's name -//! @tparam ReturnType The return type of the method -//! @tparam Parameters The types of the parameters -//! @tparam Registry The registry of the method -template< - typename Id, typename... Parameters, typename ReturnType, class Registry> -class method - : public detail::method_base { +namespace detail { + +template +class method_base + : public std::conditional_t< + Registry::has_deferred_static_rtti, deferred_method_info, method_info> { template struct override_aux; - // Aliases used in implementation only. Everything extracted from template - // arguments is capitalized like the arguments themselves. + using MethodType = method; using RegistryType = Registry; using rtti = typename Registry::rtti; using DeclaredParameters = mp11::mp_list; @@ -2179,32 +2166,6 @@ class method -> ReturnType; public: - //! Method singleton - //! - //! The only instance of `method`. Its `operator()` is used to call - //! the method. - static method fn; - - //! Call the method - //! - //! Call the method with `args`. The types of the arguments are the same as - //! the method `Parameters...`, stripped from any `virtual\_` decorators. - //! - //! @param args The arguments for the method call - //! - //! @par Errors - //! - //! If `Registry` contains an @ref error_handler policy, call its `error` - //! function with an object of one of the following types: - //! - //! @li @ref not_implemented: No overrider is applicable. - //! @li @ref ambiguous_call: More than one overrider is applicable, and - //! none is more specialized than all the others. - //! - auto operator()(typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS - StripVirtualDecorator::type... args) const - -> ReturnType; - //! Check if a next most specialized overrider exists //! //! Return `true` if a next most specialized overrider after _Fn_ exists, @@ -2229,6 +2190,10 @@ class method //! `Fn` must be a function that is an overrider of the method. //! //! @tparam Fn A function that is an overrider of the method. + + // 'next' does not need any special treatment for Windows DLLs, because it + // may be called only from within the overrider, registered with a registrar + // in the same module. template static FunctionPointer next; @@ -2307,20 +2272,6 @@ class method vptr_type dispatch, const ArgType& arg, const MoreArgTypes&... more_args) const -> detail::word; - template - FunctionPointer resolve(const ArgType&... args) const; - - template - struct thunk; - - template - struct thunk; - - method(); - method(const method&) = delete; - method(method&&) = delete; - ~method(); - void resolve(); // virtual if Registry contains has_deferred_static_rtti static BOOST_NORETURN auto fn_not_implemented( @@ -2328,6 +2279,9 @@ class method static BOOST_NORETURN auto fn_ambiguous(detail::remove_virtual_... args) -> ReturnType; + template + struct thunk; + template< auto Overrider, typename OverriderReturn, typename... OverriderParameters> @@ -2350,9 +2304,6 @@ class method static type_id vp_type_ids[Arity]; }; - template - struct override_aux; - template struct override_aux { override_aux() { @@ -2361,36 +2312,99 @@ class method static override_impl impl; }; + + protected: + template + FunctionPointer resolve(const ArgType&... args) const; + + method_base(); + method_base(const method_base&) = delete; + method_base(method_base&&) = delete; + ~method_base(); }; +} // namespace detail + +//! Method with a specific id, signature and return type +//! +//! `method` implements an open-method that takes a parameter list - +//! `Parameters` - and returns a `ReturnType`. +//! +//! `Parameters` must contain at least one virtual parameter, i.e. a parameter +//! that has a type in the form `virtual_ptr` or `virtual\_`. +//! The dynamic types of the virtual arguments are taken into account to select +//! the overrider to call. +//! +//! @see method +//! +//! @tparam Id A type representing the method's name +//! @tparam ReturnType The return type of the method +//! @tparam Parameters The types of the parameters +//! @tparam Registry The registry of the method template< typename Id, typename... Parameters, typename ReturnType, class Registry> -method - method::fn; +class method + : public detail::method_base { + public: + //! Method singleton + //! + //! The only instance of `method`. Its `operator()` is used to call + //! the method. + static method fn; + + // `fn` cannot be `inline static` because of MSVC (19.43) bug causing + // a "no appropriate default constructor available". + + //! Call the method + //! + //! Call the method with `args`. The types of the arguments are the same as + //! the method `Parameters...`, stripped from any `virtual\_` decorators. + //! + //! @param args The arguments for the method call + //! + //! @par Errors + //! + //! If `Registry` contains an @ref error_handler policy, call its `error` + //! function with an object of one of the following types: + //! + //! @li @ref not_implemented: No overrider is applicable. + //! @li @ref ambiguous_call: More than one overrider is applicable, and + //! none is more specialized than all the others. + //! + auto operator()(typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + StripVirtualDecorator::type... args) const + -> ReturnType; +}; template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -typename method::FunctionPointer - method::next; +typename detail::method_base:: + FunctionPointer + detail::method_base::next; + +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +method + method::fn; template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -type_id method::override_impl< - Function, FnReturnType>::vp_type_ids[Arity]; +type_id detail::method_base:: + override_impl::vp_type_ids[Arity]; template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -typename method:: +typename detail::method_base:: template override_impl - method::override_aux< - Function, FnReturnType (*)(FnParameters...)>::impl; + detail::method_base:: + override_aux::impl; template< typename Id, typename... Parameters, typename ReturnType, class Registry> -method::method() { +detail::method_base::method_base() { using namespace policies; this->slots_strides_ptr = slots_strides; @@ -2406,14 +2420,16 @@ method::method() { // zero-initalized static variable // coverity[uninit_use] - Registry::methods.push_back(*this); + Registry::static_::st.methods.push_back(*this); } template< typename Id, typename... Parameters, typename ReturnType, class Registry> -void method::resolve_type_ids() { +void detail::method_base:: + resolve_type_ids() { using namespace detail; - this->method_type_id = rtti::template static_type(); + this->method_type_id = + rtti::template static_type>(); this->return_type_id = rtti::template static_type>(); init_type_ids< @@ -2425,8 +2441,8 @@ void method::resolve_type_ids() { template< typename Id, typename... Parameters, typename ReturnType, class Registry> -method::~method() { - Registry::methods.remove(*this); +detail::method_base::~method_base() { + Registry::static_::st.methods.remove(*this); } // ----------------------------------------------------------------------------- @@ -2439,7 +2455,7 @@ method::operator()( typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS StripVirtualDecorator::type... args) const -> ReturnType { using namespace detail; - auto pf = resolve(parameter_traits::peek(args)...); + auto pf = this->resolve(parameter_traits::peek(args)...); return pf(std::forward::type>( args)...); @@ -2448,9 +2464,9 @@ method::operator()( template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -BOOST_FORCEINLINE - typename method::FunctionPointer - method::resolve( +BOOST_FORCEINLINE typename detail::method_base< + Id, ReturnType(Parameters...), Registry>::FunctionPointer + detail::method_base::resolve( const ArgType&... args) const { using namespace detail; @@ -2472,7 +2488,8 @@ BOOST_FORCEINLINE template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -BOOST_FORCEINLINE auto method::vptr( +BOOST_FORCEINLINE auto +detail::method_base::vptr( const ArgType& arg) const -> vptr_type { if constexpr (detail::is_virtual_ptr) { return arg.vptr(); @@ -2485,7 +2502,7 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template BOOST_FORCEINLINE auto -method::resolve_uni( +detail::method_base::resolve_uni( const ArgType& arg, const MoreArgTypes&... more_args) const -> detail::word { @@ -2505,9 +2522,10 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template BOOST_FORCEINLINE auto -method::resolve_multi_first( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { +detail::method_base:: + resolve_multi_first( + const ArgType& arg, + const MoreArgTypes&... more_args) const -> detail::word { using namespace detail; using namespace boost::mp11; @@ -2535,9 +2553,10 @@ template< std::size_t VirtualArg, typename MethodArgList, typename ArgType, typename... MoreArgTypes> BOOST_FORCEINLINE auto -method::resolve_multi_next( - vptr_type dispatch, const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { +detail::method_base:: + resolve_multi_next( + vptr_type dispatch, const ArgType& arg, + const MoreArgTypes&... more_args) const -> detail::word { using namespace detail; using namespace boost::mp11; @@ -2565,7 +2584,8 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template inline auto -method::has_next() -> bool { +detail::method_base::has_next() + -> bool { if (next == fn_not_implemented) { return false; } @@ -2580,13 +2600,14 @@ method::has_next() -> bool { template< typename Id, typename... Parameters, typename ReturnType, class Registry> BOOST_NORETURN auto -method::fn_not_implemented( - detail::remove_virtual_... args) -> ReturnType { +detail::method_base:: + fn_not_implemented( + detail::remove_virtual_... args) -> ReturnType { using namespace policies; if constexpr (Registry::has_error_handler) { no_overrider error; - detail::init_bad_call::fn( + detail::init_bad_call::fn( error, detail::parameter_traits::peek(args)...); Registry::error_handler::error(error); @@ -2598,13 +2619,13 @@ method::fn_not_implemented( template< typename Id, typename... Parameters, typename ReturnType, class Registry> BOOST_NORETURN auto -method::fn_ambiguous( +detail::method_base::fn_ambiguous( detail::remove_virtual_... args) -> ReturnType { using namespace policies; if constexpr (Registry::has_error_handler) { ambiguous_call error; - detail::init_bad_call::fn( + detail::init_bad_call::fn( error, detail::parameter_traits::peek(args)...); Registry::error_handler::error(error); @@ -2713,7 +2734,7 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template< auto Overrider, typename OverriderReturn, typename... OverriderParameters> -auto method:: +auto detail::method_base:: thunk::fn( detail::remove_virtual_... arg) -> ReturnType { using namespace detail; @@ -2730,7 +2751,7 @@ auto method:: template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -method::override_impl< +detail::method_base::override_impl< Function, FnReturnType>::override_impl(FunctionPointer* p_next) { using namespace detail; @@ -2751,7 +2772,7 @@ method::override_impl< // zero-initalized static variable // coverity[uninit_use] if (overrider_info::method) { - BOOST_ASSERT(overrider_info::method == &fn); + BOOST_ASSERT(overrider_info::method == &MethodType::fn); return; } @@ -2763,14 +2784,14 @@ method::override_impl< #pragma GCC diagnostic pop #endif - overrider_info::method = &fn; + overrider_info::method = &MethodType::fn; if constexpr (!Registry::has_deferred_static_rtti) { resolve_type_ids(); } this->next = reinterpret_cast( - p_next ? p_next : &method::next); + p_next ? p_next : &MethodType::template next); using Thunk = thunk; this->pf = reinterpret_cast(Thunk::fn); @@ -2778,14 +2799,14 @@ method::override_impl< this->vp_begin = vp_type_ids; this->vp_end = vp_type_ids + Arity; - fn.overriders.push_back(*this); + MethodType::fn.overriders.push_back(*this); } template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -void method::override_impl< - Function, FnReturnType>::resolve_type_ids() { +void detail::method_base:: + override_impl::resolve_type_ids() { using namespace detail; this->return_type = Registry::rtti::template static_type< diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 38ad1b96..ff044ab1 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -37,9 +37,10 @@ namespace boost::openmethod { //! including those pulled from libraries. struct default_registry : registry< - policies::std_rtti, policies ::vptr_vector, - policies::fast_perfect_hash, policies::default_error_handler, - policies::stderr_output + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::default_error_handler, + policies::stderr_output, + policies::declspec #ifdef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS , policies::runtime_checks diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index b9d56722..066ac284 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,43 @@ struct aggregate_reports, mp11::mp_list<>, Void> { struct type : Reports... {}; }; +// Policy initialization helpers + +#if defined(_MSC_VER) && _MSC_VER <= 1950 +template +struct has_initialize_aux : std::false_type {}; + +template +struct has_initialize_aux< + std::void_t...>)>, + T, Args...> : std::true_type {}; + +template +constexpr bool has_initialize = has_initialize_aux::value; +#else +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(initialize); +#endif + +// Call initialize on a single policy if it has the function +template +void initialize_policy(const Context& ctx, const Options& options) { + using PolicyFn = typename Policy::template fn; + if constexpr (has_initialize) { + PolicyFn::initialize(ctx, options); + } +} + +template +struct initialize_policies; + +template +struct initialize_policies> { + template + static void fn(const Context& ctx, const Options& options) { + (initialize_policy(ctx, options), ...); + } +}; + inline void merge_into(boost::dynamic_bitset<>& a, boost::dynamic_bitset<>& b) { if (b.size() < a.size()) { b.resize(a.size()); @@ -93,8 +131,7 @@ struct generic_compiler { }; struct class_ { - bool is_abstract = false; - std::vector type_ids; + std::vector ci; std::vector transitive_bases; std::vector direct_bases; std::vector direct_derived; @@ -105,22 +142,13 @@ struct generic_compiler { std::size_t first_slot = 0; std::size_t mark = 0; // temporary mark to detect cycles std::vector vtbl; - vptr_type* static_vptr; auto is_base_of(class_* other) const -> bool { return transitive_derived.find(other) != transitive_derived.end(); } - auto vptr() const -> const vptr_type& { - return *static_vptr; - } - - auto type_id_begin() const { - return type_ids.begin(); - } - - auto type_id_end() const { - return type_ids.end(); + auto is_abstract() const -> bool { + return ci[0]->is_abstract; } }; @@ -185,12 +213,80 @@ struct generic_compiler { std::deque classes; + class const_class_iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const detail::class_info*; + using difference_type = std::ptrdiff_t; + using pointer = const detail::class_info**; + using reference = const detail::class_info*&; + + const_class_iterator() = default; + + const_class_iterator( + std::deque::const_iterator class_iter, + std::deque::const_iterator class_end) + : class_iter_(class_iter), class_end_(class_end) { + if (class_iter_ != class_end_) { + ci_iter_ = class_iter_->ci.begin(); + advance_to_valid(); + } + } + auto operator->() const -> const detail::class_info* { + return *ci_iter_; + } + auto operator*() const -> const detail::class_info* { + return *ci_iter_; + } + + auto operator++() -> const_class_iterator& { + ++ci_iter_; + advance_to_valid(); + return *this; + } + + auto operator++(int) -> const_class_iterator { + const_class_iterator tmp = *this; + ++(*this); + return tmp; + } + + auto operator==(const const_class_iterator& other) const -> bool { + if (class_iter_ != other.class_iter_) { + return false; + } + if (class_iter_ == class_end_) { + return true; + } + return ci_iter_ == other.ci_iter_; + } + + auto operator!=(const const_class_iterator& other) const -> bool { + return !(*this == other); + } + + private: + void advance_to_valid() { + while (class_iter_ != class_end_ && + ci_iter_ == class_iter_->ci.end()) { + ++class_iter_; + if (class_iter_ != class_end_) { + ci_iter_ = class_iter_->ci.begin(); + } + } + } + + std::deque::const_iterator class_iter_; + std::deque::const_iterator class_end_; + std::vector::const_iterator ci_iter_; + }; + auto classes_begin() const { - return classes.begin(); + return const_class_iterator(classes.begin(), classes.end()); } auto classes_end() const { - return classes.end(); + return const_class_iterator(classes.end(), classes.end()); } std::vector methods; @@ -248,7 +344,7 @@ template auto operator<<(trace_stream& tr, const generic_compiler::class_& cls) -> trace_stream& { if constexpr (Compiler::has_trace) { - tr << type_name(cls.type_ids[0]); + tr << type_name(cls.ci[0]->type); } return tr; @@ -298,7 +394,20 @@ auto operator<<(trace_stream& tr, const spec_name& sn) } template -struct range; +struct range { + range(Iterator first, Iterator last) : first(first), last(last) { + } + + Iterator first, last; + + auto begin() const -> Iterator { + return first; + } + + auto end() const -> Iterator { + return last; + } +}; template auto write_range(trace_stream& tr, range range, F fn) -> auto& { @@ -425,8 +534,8 @@ struct registry::compiler : detail::generic_compiler { static void select_dominant_overriders( std::vector& dominants, std::size_t& pick, std::size_t& remaining); - static auto - is_more_specific(const overrider* a, const overrider* b) -> bool; + static auto is_more_specific(const overrider* a, const overrider* b) + -> bool; static auto is_base(const overrider* a, const overrider* b) -> bool; std::tuple options; @@ -473,7 +582,7 @@ template void registry::compiler::initialize() { compile(); install_global_tables(); - registry::initialized = true; + registry::static_::st.initialized = true; } #ifdef _MSC_VER @@ -542,7 +651,7 @@ void registry::compiler::augment_classes() { // The standard does not guarantee that there is exactly one // type_info object per class. However, it guarantees that the // type_index for a class has a unique value. - for (auto& cr : registry::classes) { + for (auto& cr : registry::static_::st.classes) { if constexpr (has_deferred_static_rtti) { static_cast(cr).resolve_type_ids(); } @@ -550,21 +659,28 @@ void registry::compiler::augment_classes() { { indent _(tr); ++tr << type_name(cr.type) << ": " - << range{cr.first_base, cr.last_base} << "\n"; + << range{cr.first_base, cr.last_base} + << ", type = " << cr.type << ", &vptr = " << &cr.vptr() + << "\n"; } auto& rtc = class_map[rtti::type_index(cr.type)]; if (rtc == nullptr) { rtc = &classes.emplace_back(); - rtc->is_abstract = cr.is_abstract; - rtc->static_vptr = cr.static_vptr; } - if (std::find( - rtc->type_ids.begin(), rtc->type_ids.end(), cr.type) == - rtc->type_ids.end()) { - rtc->type_ids.push_back(cr.type); + bool new_type_id = true; + + for (auto ci : rtc->ci) { + if (ci->type == cr.type) { + new_type_id = false; + break; + } + } + + if (new_type_id) { + rtc->ci.push_back(&cr); } } } @@ -572,7 +688,7 @@ void registry::compiler::augment_classes() { // All known classes now have exactly one associated class_* in the // map. Collect the bases. - for (auto& cr : registry::classes) { + for (auto& cr : registry::static_::st.classes) { auto rtc = class_map[rtti::type_index(cr.type)]; for (auto& base : range{cr.first_base, cr.last_base}) { @@ -701,14 +817,14 @@ void registry::compiler::augment_methods() { using namespace policies; using namespace detail; - methods.resize(registry::methods.size()); + methods.resize(registry::static_::st.methods.size()); ++tr << "Methods:\n"; indent _(tr); auto meth_iter = methods.begin(); - for (auto& meth_info : registry::methods) { + for (auto& meth_info : registry::static_::st.methods) { if constexpr (has_deferred_static_rtti) { static_cast(meth_info).resolve_type_ids(); } @@ -842,8 +958,8 @@ void registry::compiler::augment_methods() { if (!vp->is_base_of(overrider.vp[param_index])) { missing_base error; - error.base = overrider.vp[param_index]->type_ids[0]; - error.derived = vp->type_ids[0]; + error.base = overrider.vp[param_index]->ci[0]->type; + error.derived = vp->ci[0]->type; if constexpr (has_error_handler) { error_handler::error(error); @@ -1052,7 +1168,7 @@ void registry::compiler::build_dispatch_tables() { auto& group = dim_group[mask]; group.classes.push_back(covariant_class); group.has_concrete_classes = group.has_concrete_classes || - !covariant_class->is_abstract; + !covariant_class->is_abstract(); ++tr << "-> mask: " << mask << "\n"; } @@ -1083,7 +1199,7 @@ void registry::compiler::build_dispatch_tables() { for (auto cls : group.classes) { indent _(tr); - ++tr << type_name(cls->type_ids[0]) << "\n"; + ++tr << type_name(cls->ci[0]->type) << "\n"; auto& entry = cls->vtbl[m.slots[dim] - cls->first_slot]; entry.method_index = &m - &methods[0]; entry.vp_index = dim; @@ -1152,7 +1268,7 @@ void registry::compiler::build_dispatch_table( << "\n"; indent _(tr); for (auto cls : range{group.classes.begin(), group.classes.end()}) { - ++tr << type_name(cls->type_ids[0]) << "\n"; + ++tr << type_name(cls->ci[0]->type) << "\n"; } } @@ -1367,7 +1483,9 @@ void registry::compiler::write_global_data() { ++tr << "Initializing v-tables at " << gv_iter << "\n"; for (auto& cls : classes) { - *cls.static_vptr = gv_iter - cls.first_slot; + for (auto& ci : cls.ci) { + *ci->static_vptr = gv_iter - cls.first_slot; + } ++tr << rflush(4, gv_iter - gv_first) << " " << gv_iter << " vtbl for " << cls << " slots " << cls.first_slot << "-" @@ -1407,11 +1525,9 @@ void registry::compiler::write_global_data() { ++tr << rflush(4, dispatch_data_size) << " " << gv_iter << " end\n"; - if constexpr (has_vptr) { - vptr::initialize(*this, options); - } + detail::initialize_policies::fn(*this, options); - new_dispatch_data.swap(dispatch_data); + new_dispatch_data.swap(static_::st.dispatch_data); } template @@ -1630,14 +1746,7 @@ inline auto initialize(Options&&... options) { namespace detail { -template -struct has_finalize_aux : std::false_type {}; - -template -struct has_finalize_aux< - std::void_t>()))>, - Policy, Options...> : std::true_type {}; +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(finalize); } // namespace detail @@ -1647,13 +1756,13 @@ auto registry::finalize(Options... opts) -> void { std::tuple options(opts...); // gcc-8 doesn't like CTAD here mp11::mp_for_each([&options](auto policy) { using fn = typename decltype(policy)::template fn; - if constexpr (detail::has_finalize_aux::value) { + if constexpr (detail::has_finalize&>) { fn::finalize(options); } }); - dispatch_data.clear(); - initialized = false; + static_::dispatch_data.clear(); + static_::initialized = false; } //! Release resources held by registry. diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 3a574ca9..f011056d 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -25,16 +25,27 @@ struct enable_forwarder< template struct va_args; -template -struct va_args { +template +struct va_args { using return_type = ReturnType; - using registry = Registry; + using registry = + std::conditional_t, Other, macro_default_registry>; + using declspec_type = + std::conditional_t, Other, void>; +}; + +template +struct va_args { + using return_type = ReturnType; + using registry = std::conditional_t, T, U>; + using declspec_type = std::conditional_t, T, U>; }; template struct va_args { using return_type = ReturnType; using registry = macro_default_registry; + using declspec_type = registry::declspec; }; template @@ -64,8 +75,14 @@ inline constexpr bool method_not_found = false; ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type ARGS, \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::registry> +#define BOOST_OPENMETHOD_DETAIL_STORAGE_CLASS(NAME, ARGS, ...) \ + auto boost_openmethod_declspec( \ + BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__) &) \ + -> ::boost::openmethod::detail::va_args<__VA_ARGS__>::declspec_type; + #define BOOST_OPENMETHOD(NAME, ARGS, ...) \ struct BOOST_OPENMETHOD_ID(NAME); \ + BOOST_OPENMETHOD_DETAIL_STORAGE_CLASS(NAME, ARGS, __VA_ARGS__); \ template \ typename ::boost::openmethod::detail::enable_forwarder< \ void, BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index 342fc7b5..f0153737 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -11,7 +11,15 @@ #include #include -namespace boost::openmethod::policies { +namespace boost::openmethod { + +namespace detail { + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(handler); + +} // namespace detail + +namespace policies { //! Calls a std::function with the error. //! @@ -77,6 +85,11 @@ struct default_error_handler : error_handler { //! The type of the error handler function object. using function_type = std::function; + using static_ = detail::static_handler< + Registry, function_type, + typename Registry::template policy< + policies::declspec_policy>::guide_type>; + //! Calls a function with the error object, wrapped in an @ref //! error_variant. //! @@ -84,6 +97,8 @@ struct default_error_handler : error_handler { //! @param error The error object. template static auto error(const Error& error) -> void { + auto handler = + static_::handler ? static_::handler : default_handler; handler(error_variant(error)); } @@ -96,9 +111,16 @@ struct default_error_handler : error_handler { //! @return The previous function. // coverity[auto_causes_copy] static auto set(function_type new_handler) -> function_type { - return std::exchange(handler, std::move(new_handler)); + auto prev = std::exchange(static_::handler, std::move(new_handler)); + return prev ? prev : default_handler; } + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR + static auto id() -> const void* { + return &static_::handler; + } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR + //! The default error handler function. //! //! @param error A variant containing the error. @@ -115,16 +137,10 @@ struct default_error_handler : error_handler { Registry::output::os << "\n"; } } - - private: - static function_type handler; }; }; -template -typename default_error_handler::fn::function_type - default_error_handler::fn::handler = default_handler; - -} // namespace boost::openmethod::policies +} // namespace policies +} // namespace boost::openmethod #endif diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index e91f8f86..f536aea6 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4702) // unreachable code @@ -30,8 +32,19 @@ using uintptr = std::size_t; constexpr uintptr uintptr_max = (std::numeric_limits::max)(); #endif -template -std::vector fast_perfect_hash_control; +struct hash_fn { + std::size_t mult; + std::size_t shift; + std::size_t min_value; + std::size_t max_value; + + auto operator()(type_id type) const -> std::size_t { + return (mult * reinterpret_cast(type)) >> shift; + } +}; + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(hash_fn); +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(origin); } // namespace detail @@ -73,20 +86,23 @@ struct fast_perfect_hash : type_hash { //! @tparam Registry The registry containing this policy template class fn { - static std::size_t mult; - static std::size_t shift; - static std::size_t min_value; - static std::size_t max_value; - + using guide_type = typename Registry::template policy< + policies::declspec_policy>::guide_type; + using static_ = + detail::static_hash_fn; + using control = + detail::static_origin, guide_type>; static void check(std::size_t index, type_id type); template - static void initialize( + static void initialize_aux( const InitializeContext& ctx, std::vector& buckets, const std::tuple& options); public: - //! Find the hash factors + using declspec = typename static_::declspec; + + //! Finds the hash factors //! //! Attempts to find suitable values for the multiplication factor `M` //! and the shift amount `S` to that do not result in collisions for the @@ -100,16 +116,22 @@ struct fast_perfect_hash : type_hash { //! @return A pair containing the minimum and maximum hash values. template static auto - initialize(const Context& ctx, const std::tuple& options) { + initialize(const Context& ctx, const std::tuple& options) + -> void { if constexpr (Registry::has_runtime_checks) { - initialize( - ctx, detail::fast_perfect_hash_control, options); + initialize_aux(ctx, control::origin, options); } else { std::vector buckets; - initialize(ctx, buckets, options); + initialize_aux(ctx, buckets, options); } + } - return std::pair{min_value, max_value}; + //! Returns the hash range + //! + //! @return A pair containing the minimum and maximum hash values. + static auto hash_range() -> std::pair { + return std::pair{ + static_::hash_fn.min_value, static_::hash_fn.max_value}; } //! Hash a type id @@ -127,8 +149,7 @@ struct fast_perfect_hash : type_hash { //! @return The hash value BOOST_FORCEINLINE static auto hash(type_id type) -> std::size_t { - auto index = - (mult * reinterpret_cast(type)) >> shift; + auto index = static_::hash_fn(type); if constexpr (Registry::has_runtime_checks) { check(index, type); @@ -144,26 +165,22 @@ struct fast_perfect_hash : type_hash { //! @param options Zero or more option objects. template static auto finalize(const std::tuple&) -> void { - detail::fast_perfect_hash_control.clear(); + if constexpr (Registry::has_runtime_checks) { + control::origin.clear(); + } } + + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR + static auto id() -> const void* { + return &static_::hash_fn; + } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; -template -std::size_t fast_perfect_hash::fn::mult; - -template -std::size_t fast_perfect_hash::fn::shift; - -template -std::size_t fast_perfect_hash::fn::min_value; - -template -std::size_t fast_perfect_hash::fn::max_value; - template template -void fast_perfect_hash::fn::initialize( +void fast_perfect_hash::fn::initialize_aux( const InitializeContext& ctx, std::vector& buckets, const std::tuple& options) { (void)options; @@ -171,7 +188,10 @@ void fast_perfect_hash::fn::initialize( const auto N = std::distance(ctx.classes_begin(), ctx.classes_end()); if constexpr (mp11::mp_contains, trace>::value) { - Registry::output::os << "Finding hash factor for " << N << " types\n"; + if (std::get(options).on) { + Registry::output::os << "Finding hash factor for " << N + << " types\n"; + } } std::default_random_engine rnd(13081963); @@ -184,11 +204,11 @@ void fast_perfect_hash::fn::initialize( std::uniform_int_distribution uniform_dist; - for (std::size_t pass = 0; pass < 4; ++pass, ++M) { - shift = 8 * sizeof(type_id) - M; + for (std::size_t pass = 0; pass < 5; ++pass, ++M) { + static_::hash_fn.shift = 8 * sizeof(type_id) - M; auto hash_size = 1 << M; - min_value = (std::numeric_limits::max)(); - max_value = (std::numeric_limits::min)(); + static_::hash_fn.min_value = (std::numeric_limits::max)(); + static_::hash_fn.max_value = (std::numeric_limits::min)(); if constexpr (InitializeContext::template has_option) { ctx.tr << " trying with M = " << M << ", " << hash_size @@ -198,21 +218,23 @@ void fast_perfect_hash::fn::initialize( std::size_t attempts = 0; buckets.resize(hash_size); - while (attempts < 100000) { + while (attempts < 1'00'000) { std::fill( buckets.begin(), buckets.end(), type_id(detail::uintptr_max)); ++attempts; ++total_attempts; - mult = uniform_dist(rnd) | 1; + static_::hash_fn.mult = uniform_dist(rnd) | 1; for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); ++iter) { for (auto type_iter = iter->type_id_begin(); type_iter != iter->type_id_end(); ++type_iter) { auto type = *type_iter; - auto index = (detail::uintptr(type) * mult) >> shift; - min_value = (std::min)(min_value, index); - max_value = (std::max)(max_value, index); + auto index = static_::hash_fn(type); + static_::hash_fn.min_value = + (std::min)(static_::hash_fn.min_value, index); + static_::hash_fn.max_value = + (std::max)(static_::hash_fn.max_value, index); if (detail::uintptr(buckets[index]) != detail::uintptr_max) { @@ -224,9 +246,10 @@ void fast_perfect_hash::fn::initialize( } if constexpr (InitializeContext::template has_option) { - ctx.tr << " found " << mult << " after " << total_attempts - << " attempts; span = [" << min_value << ", " - << max_value << "]\n"; + ctx.tr << " found " << static_::hash_fn.mult << " after " + << total_attempts << " attempts; span = [" + << static_::hash_fn.min_value << ", " + << static_::hash_fn.max_value << "]\n"; } return; @@ -248,8 +271,8 @@ void fast_perfect_hash::fn::initialize( template void fast_perfect_hash::fn::check(std::size_t index, type_id type) { - if (index < min_value || index > max_value || - detail::fast_perfect_hash_control[index] != type) { + if (index < static_::hash_fn.min_value || + index > static_::hash_fn.max_value || control::origin[index] != type) { if constexpr (Registry::has_error_handler) { missing_class error; @@ -263,8 +286,8 @@ void fast_perfect_hash::fn::check(std::size_t index, type_id type) { template auto fast_perfect_hash::search_error::write(Stream& os) const -> void { - os << "could not find hash factors after " << attempts << "s using " - << buckets << " buckets\n"; + os << "could not find hash factors after " << attempts + << " attempts using up to " << buckets << " buckets\n"; } } // namespace policies diff --git a/include/boost/openmethod/policies/stderr_output.hpp b/include/boost/openmethod/policies/stderr_output.hpp index 68db1fb8..18a67e7e 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -9,7 +9,15 @@ #include #include -namespace boost::openmethod::policies { +namespace boost::openmethod { + +namespace detail { + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(os); + +} // namespace detail + +namespace policies { //! @ref Writes to the C standard error stream. //! @@ -17,15 +25,22 @@ namespace boost::openmethod::policies { struct stderr_output : output { //! An OutputFn metafunction. template - struct fn { + struct fn : detail::static_os< + Registry, detail::ostderr, + typename Registry::template policy< + policies::declspec_policy>::guide_type> { //! A @ref LightweightOuputStream. - static detail::ostderr os; + // static detail::ostderr os; // now inherited from static_os + + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR + static auto id() -> const void* { + return &fn::os; + } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; -template -detail::ostderr stderr_output::fn::os; - -} // namespace boost::openmethod::policies +} // namespace policies +} // namespace boost::openmethod #endif diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index c26e5de2..d2decf9e 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -12,6 +12,12 @@ namespace boost::openmethod { +namespace detail { + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(vptrs); + +} // namespace detail + namespace policies { //! Stores v-table pointers in a map keyed by `type_id`s. @@ -33,7 +39,10 @@ class vptr_map : public vptr { class fn { using Value = std::conditional_t< Registry::has_indirect_vptr, const vptr_type*, vptr_type>; - static inline typename MapFn::template fn vptrs; + using static_ = detail::static_vptrs< + Registry, typename MapFn::template fn, + typename Registry::template policy< + policies::declspec_policy>::guide_type>; public: //! Stores the v-table pointers. @@ -45,7 +54,7 @@ class vptr_map : public vptr { template static void initialize(const Context& ctx, const std::tuple&) { - decltype(vptrs) new_vptrs; + decltype(static_::vptrs) new_vptrs; for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); ++iter) { @@ -60,7 +69,7 @@ class vptr_map : public vptr { } } - vptrs.swap(new_vptrs); + static_::vptrs.swap(new_vptrs); } //! Returns a reference to a v-table pointer for an object. @@ -80,10 +89,10 @@ class vptr_map : public vptr { template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { auto type = Registry::rtti::dynamic_type(arg); - auto iter = vptrs.find(type); + auto iter = static_::vptrs.find(type); if constexpr (Registry::has_runtime_checks) { - if (iter == vptrs.end()) { + if (iter == static_::vptrs.end()) { if constexpr (Registry::has_error_handler) { missing_class error; error.type = type; @@ -111,8 +120,14 @@ class vptr_map : public vptr { //! @param options A tuple of option objects. template static auto finalize(const std::tuple&) -> void { - vptrs.clear(); + static_::vptrs.clear(); + } + + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR + static auto id() -> const void* { + return &static_::vptrs; } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index 1b1a2768..33cffd05 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -15,11 +15,8 @@ namespace boost::openmethod { namespace detail { -template -inline std::vector vptr_vector_vptrs; - -template -inline std::vector vptr_vector_indirect_vptrs; +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(vptr_vector_vptrs); +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(vptr_vector_indirect_vptrs); } // namespace detail @@ -52,6 +49,15 @@ struct vptr_vector : vptr { typename Registry::template policy; static constexpr auto has_type_hash = !std::is_same_v; + using static_ = std::conditional_t< + Registry::has_indirect_vptr, + detail::static_vptr_vector_indirect_vptrs< + Registry, std::vector, + typename Registry::declspec_guide>, + detail::static_vptr_vector_vptrs< + Registry, std::vector, + typename Registry::declspec_guide>>; + //! Stores the v-table pointers. //! //! If `Registry` contains a @ref type_hash policy, its `initialize` @@ -63,13 +69,14 @@ struct vptr_vector : vptr { //! @param ctx A Context object. //! @param options A tuple of option objects. template - static auto initialize( - const Context& ctx, const std::tuple& options) -> void { + static auto + initialize(const Context& ctx, const std::tuple& options) + -> void { std::size_t size; (void)options; if constexpr (has_type_hash) { - auto [_, max_value] = type_hash::initialize(ctx, options); + auto [_, max_value] = type_hash::hash_range(); size = max_value + 1; } else { size = 0; @@ -86,9 +93,9 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs.resize(size); + static_::vptr_vector_indirect_vptrs.resize(size); } else { - detail::vptr_vector_vptrs.resize(size); + static_::vptr_vector_vptrs.resize(size); } for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); @@ -104,11 +111,10 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs[index] = + static_::vptr_vector_indirect_vptrs[index] = &iter->vptr(); } else { - detail::vptr_vector_vptrs[index] = - iter->vptr(); + static_::vptr_vector_vptrs[index] = iter->vptr(); } } } @@ -144,10 +150,9 @@ struct vptr_vector : vptr { std::size_t max_index = 0; if constexpr (Registry::has_indirect_vptr) { - max_index = - detail::vptr_vector_indirect_vptrs.size(); + max_index = static_::vptr_vector_indirect_vptrs.size(); } else { - max_index = detail::vptr_vector_vptrs.size(); + max_index = static_::vptr_vector_vptrs.size(); } if (index >= max_index) { @@ -163,9 +168,9 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - return *detail::vptr_vector_indirect_vptrs[index]; + return *static_::vptr_vector_indirect_vptrs[index]; } else { - return detail::vptr_vector_vptrs[index]; + return static_::vptr_vector_vptrs[index]; } } @@ -179,11 +184,21 @@ struct vptr_vector : vptr { using namespace policies; if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs.clear(); + static_::vptr_vector_indirect_vptrs.clear(); + } else { + static_::vptr_vector_vptrs.clear(); + } + } + + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR + static auto id() -> const void* { + if constexpr (Registry::has_indirect_vptr) { + return &static_::vptr_vector_indirect_vptrs; } else { - detail::vptr_vector_vptrs.clear(); + return &static_::vptr_vector_vptrs; } } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 39b82c6f..3c180f01 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -3,8 +3,10 @@ #include +#include #include #include +#include #include #include @@ -18,6 +20,9 @@ namespace boost::openmethod { +// ----------------------------------------------------------------------------- +// word + namespace detail { union word { @@ -37,6 +42,9 @@ union word { } // namespace detail +// ----------------------------------------------------------------------------- +// public aliases + //! Alias to v-table pointer type. //! //! `vptr_type` is an alias to the type of a v-table pointer. @@ -49,6 +57,9 @@ using vptr_type = const detail::word*; //! `std::type_info` object), or as an opaque integer type. using type_id = const void*; +// ----------------------------------------------------------------------------- +// virtual types and traits + //! Decorator for virtual parameters. //! //! `virtual_` marks a formal parameter of a method as virtual. It is a @em @@ -70,7 +81,7 @@ struct virtual_; template struct virtual_traits; -// ----------------------------------------------------------------------------- +// ============================================================================= // Error handling //! Base class for all OpenMethod errors. @@ -284,23 +295,8 @@ struct final_error : openmethod_error { namespace detail { -struct empty {}; - -template -struct range { - range(Iterator first, Iterator last) : first(first), last(last) { - } - - Iterator first, last; - - auto begin() const -> Iterator { - return first; - } - - auto end() const -> Iterator { - return last; - } -}; +// ============================================================================= +// generic registrars // ----------------------------------------------------------------------------- // class info @@ -311,8 +307,8 @@ struct class_info : static_list::static_link { type_id *first_base, *last_base; bool is_abstract{false}; - auto vptr() const { - return static_vptr; + auto vptr() const -> const vptr_type& { + return *static_vptr; } auto type_id_begin() const { @@ -369,12 +365,17 @@ struct deferred_overrider_info : overrider_info { virtual void resolve_type_ids() = 0; }; -struct unspecified {}; - } // namespace detail +// ============================================================================= +// initialize options + #ifdef __MRDOCS__ +namespace detail { +struct unspecified {}; +} // namespace detail + //! Blueprint for a lightweight output stream (exposition only). //! //! Classes used as output streams in policies must provide the operations @@ -395,6 +396,9 @@ struct LightweightOutputStream { #endif +// ----------------------------------------------------------------------------- +// n2216 + //! N2216 ambiguity resolution. //! //! If `n2216` is present in @ref initialize\'s `Options`, additional steps are @@ -412,6 +416,9 @@ struct LightweightOutputStream { //! the same program. struct n2216 {}; +// ----------------------------------------------------------------------------- +// trace + //! Enable `initialize` tracing. //! //! If `trace` is passed to @ref initialize, tracing code is added to various @@ -452,6 +459,9 @@ inline trace trace::from_env() { #endif } +// ============================================================================= +// policies + //! Namespace for policies. //! //! Classes with snake case names are "blueprints", i.e. exposition-only classes @@ -562,6 +572,9 @@ struct RttiFn { #endif +// ----------------------------------------------------------------------------- +// rtti + //! Policy for manipulating type information. //! //! `rtti` policies are responsible for type information acquisition and dynamic @@ -601,6 +614,9 @@ struct rtti { }; }; +// ----------------------------------------------------------------------------- +// deferred rtti + //! Policy for deferred type id collection. //! //! Some custom RTTI systems rely on static constructors to assign type ids. @@ -610,6 +626,9 @@ struct rtti { //! of type ids to be deferred until the first call to @ref update. struct deferred_static_rtti : rtti {}; +// ----------------------------------------------------------------------------- +// error handler + #ifdef __MRDOCS__ //! Blueprint for @ref error_handler metafunctions (exposition only). template @@ -639,6 +658,9 @@ struct error_handler { using category = error_handler; }; +// ----------------------------------------------------------------------------- +// vptr + #ifdef __MRDOCS__ //! Blueprint for `vptr` metafunctions (exposition only). @@ -704,6 +726,9 @@ struct indirect_vptr final { struct fn {}; }; +// ----------------------------------------------------------------------------- +// type_hash + #ifdef __MRDOCS__ //! Blueprint for @ref type_hash metafunctions (exposition only). //! @@ -716,8 +741,8 @@ struct TypeHashFn { //! blueprint. //! @return A pair containing the minimum and maximum hash values. template - static auto - initialize(const Context& ctx) -> std::pair; + static auto initialize(const Context& ctx) + -> std::pair; //! Hash a `type_id`. //! @@ -763,6 +788,9 @@ struct OutputFn { #endif +// ----------------------------------------------------------------------------- +// output + //! Policy for writing diagnostics and trace. //! //! If an `output` policy is present, the default error handler uses it to write @@ -780,6 +808,9 @@ struct output { using category = output; }; +// ----------------------------------------------------------------------------- +// runtime_checks + //! Policy for post-initialize runtime checks. //! //! If this policy is present, performs the following checks: @@ -795,31 +826,60 @@ struct runtime_checks final { } // namespace policies +// ----------------------------------------------------------------------------- +// registry and policy helpers + namespace detail { struct registry_base {}; +template +struct registry_state { + static_list classes; + static_list methods; + bool initialized; + std::vector dispatch_data; +}; + template constexpr bool is_registry = std::is_base_of_v; template constexpr bool is_not_void = !std::is_same_v; -template< - class Registry, class Index, - class Size = mp11::mp_size> +template +struct find_first_derived_of_aux; + +template +using find_first_derived_of = + typename find_first_derived_of_aux::type; + +template +struct find_first_derived_of_aux, Default> { + using type = Default; +}; + +template +struct find_first_derived_of_aux, Default> { + using type = std::conditional_t< + std::is_base_of_v, First, + find_first_derived_of, Default>>; +}; + +template struct get_policy_aux { - using type = typename mp11::mp_at< - typename Registry::policy_list, Index>::template fn; + using type = typename Policy::template fn; }; -template -struct get_policy_aux { +template +struct get_policy_aux { using type = void; }; -using class_catalog = detail::static_list; -using method_catalog = detail::static_list; +template +struct get_policy_aux { + using type = typename Default::template fn; +}; template struct with_aux; @@ -869,6 +929,119 @@ struct initialize_aux; } // namespace detail +#define BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(FN) \ + template \ + struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux)) : std::false_type {}; \ + template \ + struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))< \ + std::void_t()...))>, T, Args...> \ + : std::true_type {}; \ + template \ + constexpr bool BOOST_PP_CAT(has_, FN) = \ + BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))::value + +// ----------------------------------------------------------------------------- +// import/export + +struct declspec {}; +struct declspec_none : declspec {}; + +#if defined(__MRDOCS__) || defined(_WIN32) || defined(__CYGWIN__) +struct dllexport : declspec {}; +struct dllimport : declspec {}; +#endif + +namespace detail { + +#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, ...) \ + template \ + struct BOOST_PP_CAT(static_, ID) { \ + using declspec = declspec_none; \ + static Type ID __VA_ARGS__; \ + }; \ + \ + template \ + Type BOOST_PP_CAT(static_, ID)::ID; + +// Clang warns at every usage site of a dllimport variable template because the +// definition is deliberately absent (it lives in the exporting DLL). Suppress +// this false positive using _Pragma inside the dllimport struct so the pragma +// is emitted in whatever file expands BOOST_OPENMETHOD_DETAIL_MAKE_STATICS. +#if defined(__clang__) +#define BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wundefined-var-template\"") +#define BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR \ + _Pragma("clang diagnostic pop") +#else +#define BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR +#define BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ + BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, __VA_ARGS__) \ + \ + template \ + struct BOOST_PP_CAT(static_, ID)< \ + Registry, Type, Guide, \ + std::enable_if_t, dllexport>>> { \ + using declspec = dllexport; \ + static BOOST_SYMBOL_EXPORT Type ID __VA_ARGS__; \ + }; \ + \ + template \ + Type BOOST_PP_CAT(static_, ID)< \ + Registry, Type, Guide, \ + std::enable_if_t, dllexport>>>::ID; \ + \ + template \ + struct BOOST_PP_CAT(static_, ID)< \ + Registry, Type, Guide, \ + std::enable_if_t, dllimport>>> { \ + using declspec = dllimport; \ + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR \ + static BOOST_SYMBOL_IMPORT Type ID; \ + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR \ + } +#else +#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ + BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, __VA_ARGS__) +#endif + +template +using get_declspec = decltype(boost_openmethod_declspec(std::declval())); + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(st); + +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(id); + +} // namespace detail + +namespace policies { + +struct declspec_policy { + using category = declspec_policy; +}; + +template +struct declspec : declspec_policy { + template + struct fn { + using guide_type = GuideType; + }; +}; + +template<> +struct declspec : declspec_policy { + template + struct fn { + struct guide_type {}; + }; +}; + +} // namespace policies + //! Methods, classes and policies. //! //! Methods exist in the context of a registry. Any class used as a method or @@ -918,22 +1091,58 @@ struct initialize_aux; //! @li `Policy` must contain a `fn` metafunction. //! //! @see @ref policies +namespace detail { +template +class method_base; +} // namespace detail + template -class registry : detail::registry_base { - static detail::class_catalog classes; - static detail::method_catalog methods; +class registry : public detail::registry_base { + public: + //! List of policies selected in a registry. + //! + //! `policy_list` is a Boost.Mp11 list containing the policies passed to the + //! @ref registry clas template. + //! + //! @tparam Class A registered class. + using policy_list = mp11::mp_list; + + //! Find a policy by category. + //! + //! `policy` searches for a policy that derives from the specified @ref + //! Category. If none is found, it aliases to `void`. Otherwise, it aliases + //! to the policy's `fn` metafunction, applied to the registry. + //! + //! @tparam A policy. + template + using policy = typename detail::get_policy_aux< + registry, detail::find_first_derived_of, + Default>::type; + using declspec_guide = typename policy< + policies::declspec_policy, policies::declspec>::guide_type; + + private: template friend struct detail::use_class_aux; template friend class method; + template + friend class detail::method_base; - static std::vector dispatch_data; - static bool initialized; + using static_ = detail::static_st< + registry, detail::registry_state>, declspec_guide>; public: //! The type of this registry. using registry_type = registry; + using declspec = typename static_::declspec; + + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR + static const void* id() { + return static_cast(&static_::st.classes); + } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR template struct compiler; @@ -959,30 +1168,7 @@ class registry : detail::registry_base { //! //! @tparam Class A registered class. template - static vptr_type static_vptr; - - //! List of policies selected in a registry. - //! - //! `policy_list` is a Boost.Mp11 list containing the policies passed to the - //! @ref registry clas template. - //! - //! @tparam Class A registered class. - using policy_list = mp11::mp_list; - - //! Find a policy by category. - //! - //! `policy` searches for a policy that derives from the specified @ref - //! Category. If none is found, it aliases to `void`. Otherwise, it aliases - //! to the policy's `fn` metafunction, applied to the registry. - //! - //! @tparam A policy. - template - using policy = typename detail::get_policy_aux< - registry, - mp11::mp_find_if_q< - policy_list, - mp11::mp_bind_front_q< - mp11::mp_quote_trait, Category>>>::type; + inline static vptr_type static_vptr; //! Add or replace policies. //! @@ -1040,26 +1226,10 @@ class registry : detail::registry_base { !std::is_same_v, void>; }; -template -detail::class_catalog registry::classes; - -template -detail::method_catalog registry::methods; - -template -std::vector registry::dispatch_data; - -template -bool registry::initialized; - -template -template -vptr_type registry::static_vptr; - template void registry::require_initialized() { if constexpr (registry::has_runtime_checks) { - if (!initialized) { + if (!static_::st.initialized) { if constexpr (registry::has_error_handler) { error_handler::error(not_initialized()); } @@ -1091,6 +1261,7 @@ auto final_error::write(Stream& os) const { Registry::rtti::type_name(dynamic_type, os); } +struct default_registry; } // namespace boost::openmethod #ifdef _MSC_VER diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 06c2573e..ff0379ab 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,6 +53,16 @@ if (BOOST_OPENMETHOD_WARNINGS_AS_ERRORS) endif() endif() +function(boost_openmethod_add_test name) + add_test(NAME ${name} COMMAND ${name}) + if (WIN32) + set_property( + TEST ${name} + PROPERTY ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endfunction() + file(GLOB test_cpp_files "test_*.cpp") foreach(test_cpp ${test_cpp_files}) @@ -60,13 +70,13 @@ foreach(test_cpp ${test_cpp_files}) set(test_target "boost_openmethod-${test}") add_executable(${test_target} EXCLUDE_FROM_ALL ${test_cpp}) target_link_libraries(${test_target} PRIVATE Boost::openmethod Boost::unit_test_framework) - add_test(NAME ${test_target} COMMAND ${test_target}) + boost_openmethod_add_test(${test_target}) add_dependencies(tests ${test_target}) endforeach() add_executable(boost_openmethod-test_mix_release_debug EXCLUDE_FROM_ALL mix_release_debug/main.cpp mix_release_debug/lib.cpp) target_link_libraries(boost_openmethod-test_mix_release_debug PRIVATE Boost::openmethod Boost::unit_test_framework) -add_test(NAME boost_openmethod-test_mix_release_debug COMMAND boost_openmethod-test_mix_release_debug) +boost_openmethod_add_test(boost_openmethod-test_mix_release_debug) add_dependencies(tests boost_openmethod-test_mix_release_debug) function(openmethod_compile_fail_test testname fail_regex) @@ -104,3 +114,7 @@ openmethod_compile_fail_test( compile_fail_repeated_inheritance "repeated inheritance") openmethod_compile_fail_test( compile_fail_override_method_not_found "cannot find 'speak' method that accepts the same arguments as the overrider") + +if (TARGET Boost::dll) + add_subdirectory(dynamic_loading) +endif() diff --git a/test/Jamfile b/test/Jamfile index a4becf8c..dccc3a72 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -46,6 +46,8 @@ for local src in [ glob compile_fail_*.cpp ] compile-fail $(src) ; } +build-project dynamic_loading ; + # quick (for CI) alias quick : test_dispatch ; explicit quick ; diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt new file mode 100644 index 00000000..b8d84809 --- /dev/null +++ b/test/dynamic_loading/CMakeLists.txt @@ -0,0 +1,77 @@ +# Copyright (c) 2018-2025 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) + +if (BUILD_SHARED_LIBS) + message(STATUS "Building shared library tests") +else() + return() +endif() + +# lib_registry: exports the default registry's static policy state +add_library(boost_openmethod-dl_test_registry SHARED registry.cpp) +target_link_libraries( + boost_openmethod-dl_test_registry + PUBLIC Boost::openmethod PRIVATE Boost::dll) + +# lib_method: exports the speak method; imports registry state from lib_registry +add_library(boost_openmethod-dl_test_method SHARED method.cpp) +target_link_libraries( + boost_openmethod-dl_test_method + PUBLIC Boost::openmethod boost_openmethod-dl_test_registry PRIVATE Boost::dll) + +# lib_overrider: adds a Dog overrider; imported at runtime via Boost.DLL +add_library(boost_openmethod-dl_test_overrider SHARED overrider.cpp) +target_link_libraries( + boost_openmethod-dl_test_overrider + PRIVATE Boost::openmethod boost_openmethod-dl_test_method Boost::dll) + +# Test executable: links against lib_method (and lib_registry transitively); +# dynamically loads lib_overrider at runtime. +add_executable( + boost_openmethod-test_dynamic_loading main.cpp) +target_link_libraries( + boost_openmethod-test_dynamic_loading + PUBLIC + Boost::openmethod boost_openmethod-dl_test_registry + Boost::openmethod boost_openmethod-dl_test_method + PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) +add_dependencies( + boost_openmethod-test_dynamic_loading + boost_openmethod-dl_test_registry + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider) + +# Put all outputs in the same directory so Boost.DLL can locate the shared +# libraries relative to the test executable at runtime. +# ENABLE_EXPORTS is required so that symbols from each shared library are +# visible to other shared libraries loaded at runtime (e.g. boost_openmethod-dl_test_overrider +# can resolve symbols from boost_openmethod-dl_test_method/boost_openmethod-dl_test_registry). +# CXX_VISIBILITY_PRESET must be "default" so that the template static variables +# used as the shared registry state (policy statics) are exported with DEFAULT +# ELF visibility. With hidden visibility (as set by BoostRoot.cmake in the +# super-project build) they become UNIQUE HIDDEN and are not deduplicated by +# the dynamic linker, breaking cross-DSO state sharing. +foreach( + target + boost_openmethod-dl_test_registry + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider + boost_openmethod-test_dynamic_loading) + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ENABLE_EXPORTS ON + CXX_VISIBILITY_PRESET default + VISIBILITY_INLINES_HIDDEN OFF) +endforeach() + +if (TARGET tests) + add_dependencies(tests boost_openmethod-test_dynamic_loading) +endif() + +boost_openmethod_add_test(boost_openmethod-test_dynamic_loading) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile new file mode 100644 index 00000000..001157ee --- /dev/null +++ b/test/dynamic_loading/Jamfile @@ -0,0 +1,101 @@ +# Boost.OpenMethod Library – dynamic_loading test Jamfile +# +# Copyright 2025 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 + +# Reproduce the CMake dynamic-loading test in Boost.Build. +# +# Build graph (all shared libraries): +# +# lib_registry ──▶ lib_method ──▶ lib_overrider +# │ (runtime-loaded) +# └──────────────────────┐ +# test executable ──linked──▶ lib_registry +# ──dlopen──▶ lib_method, lib_overrider + +import testing ; +import os ; + +local os-name = [ os.name ] ; +local visibility-env = [ os.environ B2_VISIBILITY ] ; +local windir = [ os.environ WINDIR ] ; + +# -rdynamic: export exe symbols to dynamically loaded libs so that +# dlopen'd overrider can resolve symbols from the already-loaded method lib. +local RDYNAMIC = + linux:"-rdynamic" + freebsd:"-rdynamic" + android:"-rdynamic" + darwin,gcc:"-dynamic" + darwin,clang:"-rdynamic" + qnxnto,gcc:"-rdynamic" + solaris:"-Bdynamic" ; + +# Default ELF symbol visibility: template static variables used as shared +# registry state must be exported with DEFAULT visibility so the dynamic +# linker deduplicates them across DSOs. MSVC ignores this property. +local VISIBILITY = global ; + +# lib_registry: owns and exports the default registry's static policy state. +# registry.cpp defines EXPORT_REGISTRY and INCLUDED_FROM itself. +lib boost_openmethod-dl_test_registry + : registry.cpp + : shared + $(VISIBILITY) + /boost/dll//boost_dll/off + dynamic_loading.test + ; + +# lib_method: exports the speak() open-method; imports registry from +# lib_registry. method.cpp defines EXPORT_METHOD and INCLUDED_FROM itself. +lib boost_openmethod-dl_test_method + : method.cpp + : shared + $(VISIBILITY) + /boost/dll//boost_dll/off + boost_openmethod-dl_test_registry + dynamic_loading.test + ; + +# lib_overrider: adds the Dog overrider; dynamically loaded at runtime. +# overrider.cpp defines INCLUDED_FROM itself. +lib boost_openmethod-dl_test_overrider + : overrider.cpp + : shared + $(VISIBILITY) + /boost/dll//boost_dll/off + boost_openmethod-dl_test_method + windows:boost_openmethod-dl_test_registry + cygwin:boost_openmethod-dl_test_registry + dynamic_loading.test + ; + +# Test executable: +# sources (linked) : main.cpp, lib_registry, unit_test_framework, boost_dll +# runtime-loaded : lib_method, lib_overrider +# All shared libraries are placed alongside the test executable via +# dynamic_loading.test, so main.cpp can find them by scanning +# dll::program_location().parent_path() on all platforms. +run main.cpp + boost_openmethod-dl_test_registry + /boost/test//boost_unit_test_framework/off + /boost/dll//boost_dll/off + : + : + : shared + $(RDYNAMIC) + $(VISIBILITY) + /boost/filesystem//boost_filesystem/off + linux:"-ldl" + freebsd:"-ldl" + android:"-ldl" + windows:boost_openmethod-dl_test_registry + windows:boost_openmethod-dl_test_method + cygwin:boost_openmethod-dl_test_method + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider + : dynamic_loading + ; diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp new file mode 100644 index 00000000..88a65c10 --- /dev/null +++ b/test/dynamic_loading/classes.hpp @@ -0,0 +1,18 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +static auto make_dog() { + return boost::openmethod::make_unique_virtual(); +} \ No newline at end of file diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp new file mode 100644 index 00000000..b8a0dba0 --- /dev/null +++ b/test/dynamic_loading/main.cpp @@ -0,0 +1,131 @@ +// Copyright (c) 2018-2025 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) + +#define BOOST_TEST_MODULE dynamic_loading +#include + +#define INCLUDED_FROM "main.cpp" + +#include "registry.hpp" +#include "method.hpp" + +#include +#include +#include +#include + +#include +#include + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +using policy_ids_fn = const void**(); +using method_fn = const void*(); + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +boost::filesystem::path +find_lib(const boost::filesystem::path& dir, const char* name_fragment) { + for (auto entry : boost::filesystem::directory_iterator(dir)) { + auto fname = entry.path().filename().string(); + if (fname.find(name_fragment) != std::string::npos) { + return entry.path(); + } + } + throw std::runtime_error(std::string("lib not found: ") + name_fragment); +} + +bool same_ids(const void** ids1, const void** ids2) { + using std::setw; + BOOST_TEST_MESSAGE( + setw(60) << "registry state" << ": " << *ids1++ << " " << *ids2++); + + int diffs = 0; + + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + + if constexpr (detail::has_id>) { + BOOST_TEST_MESSAGE( + setw(60) << boost::core::demangle(typeid(P).name()) << ": " + << *ids1 << " " << *ids2); + + if (*ids1 != *ids2) { + ++diffs; + } + + ++ids1; + ++ids2; + } + }); + + return diffs == 0; +} + +BOOST_AUTO_TEST_CASE(test_shared_state) { + namespace dll = boost::dll; + + // Load all three shared libraries via Boost.DLL. + // Use rtld_global for registry and method so their symbols (fn, policy + // statics) are visible globally and win over any locally-instantiated + // copies when the overrider library is subsequently loaded. + + constexpr auto load_mode = dll::load_mode::rtld_global; + + auto search_dir = boost::dll::program_location().parent_path(); + BOOST_TEST_MESSAGE("search_dir: " << search_dir); + auto method_path = find_lib(search_dir, "test_method"); + dll::shared_library method_lib(method_path, load_mode); + auto method_get_ids = method_lib.get("method_get_ids"); + auto method_speak = + method_lib.get)>("method_call_speak"); + auto method_make_dog = + method_lib.get&)>("method_make_dog"); + auto method_get_fn = method_lib.get("method_get_fn"); + + BOOST_TEST(same_ids(get_ids(), method_get_ids())); + BOOST_TEST(get_fn() == method_get_fn()); + + initialize(); + + auto main_dog = make_dog(); + unique_virtual_ptr method_dog; + method_make_dog(method_dog); + BOOST_TEST(main_dog.vptr() == method_dog.vptr()); +#if defined(_WIN32) || defined(__CYGWIN__) + { + Animal* p1 = main_dog.get(); + Animal* p2 = method_dog.get(); + BOOST_TEST(&typeid(*p1) != &typeid(*p2)); + } +#endif + BOOST_TEST(method_speak(main_dog) == "?"); + BOOST_TEST(method_speak(method_dog) == "?"); + + dll::shared_library overrider_lib( + find_lib(search_dir, "test_overrider"), load_mode); + auto overrider_get_ids = + overrider_lib.get("overrider_get_ids"); + auto overrider_speak = overrider_lib.get)>( + "overrider_call_speak"); + auto overrider_make_dog = + overrider_lib.get&)>( + "overrider_make_dog"); + auto overrider_get_fn = overrider_lib.get("overrider_get_fn"); + + BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); + BOOST_TEST(get_fn() == overrider_get_fn()); + + initialize(); + unique_virtual_ptr overrider_dog; + overrider_make_dog(overrider_dog); + main_dog = make_dog(); // because its vptr was invalidated by initialize() + method_make_dog(method_dog); // ditto + BOOST_TEST(main_dog.vptr() == overrider_dog.vptr()); + BOOST_TEST(std::string(overrider_speak(main_dog)) == "woof"); + BOOST_TEST(std::string(overrider_speak(method_dog)) == "woof"); + BOOST_TEST(std::string(overrider_speak(overrider_dog)) == "woof"); +} diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp new file mode 100644 index 00000000..6a25d99e --- /dev/null +++ b/test/dynamic_loading/method.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2018-2025 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) + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#define EXPORT_METHOD + +#include "registry.hpp" +#include "method.hpp" + +#include + +#include + +#if defined(_WIN32) || defined(__CYGWIN__) +#include +#else +#include +#endif + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +#if defined(_WIN32) || defined(__CYGWIN__) +static_assert(std::is_same_v); +#endif + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "?"; +} + +extern "C" { + +BOOST_SYMBOL_EXPORT const void* method_get_ids() { + return get_ids(); +} + +BOOST_SYMBOL_EXPORT const void* method_get_fn() { + return get_fn(); +} + +BOOST_SYMBOL_EXPORT void +method_make_dog(unique_virtual_ptr& p) { + p = make_dog(); +} + +BOOST_SYMBOL_EXPORT const char* +method_call_speak(boost::openmethod::virtual_ptr animal) { + return speak(animal); +} + +} diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp new file mode 100644 index 00000000..bda305ee --- /dev/null +++ b/test/dynamic_loading/method.hpp @@ -0,0 +1,34 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "registry.hpp" +#include "classes.hpp" + +#include + +#if defined(_WIN32) || defined(__CYGWIN__) +#if defined(EXPORT_METHOD) +#define METHOD_DECLSPEC boost::openmethod::dllexport +#else +#define METHOD_DECLSPEC boost::openmethod::dllimport +#endif +#else +#define METHOD_DECLSPEC boost::openmethod::declspec_none +#endif + +BOOST_OPENMETHOD( + speak, (boost::openmethod::virtual_ptr), const char*, + METHOD_DECLSPEC); + +BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR +inline auto get_fn() { + return static_cast(&BOOST_OPENMETHOD_TYPE( + speak, (boost::openmethod::virtual_ptr), const char*)::fn); +} +BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR + +inline auto call_speak(boost::openmethod::virtual_ptr animal) { + return speak(animal); +} \ No newline at end of file diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp new file mode 100644 index 00000000..43914880 --- /dev/null +++ b/test/dynamic_loading/overrider.cpp @@ -0,0 +1,46 @@ +// Copyright (c) 2018-2025 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) + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#include "method.hpp" + +#if defined(_WIN32) || defined(__CYGWIN__) +#include +#else +#include +#endif + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "woof"; +} + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +extern "C" { + +BOOST_SYMBOL_EXPORT const void* overrider_get_ids() { + return get_ids(); +} + +BOOST_SYMBOL_EXPORT const void* overrider_get_fn() { + return get_fn(); +} + +BOOST_SYMBOL_EXPORT void +overrider_make_dog(unique_virtual_ptr& p) { + p = make_dog(); +} + +BOOST_SYMBOL_EXPORT const char* +overrider_call_speak(boost::openmethod::virtual_ptr animal) { + return speak(animal); +} +} diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp new file mode 100644 index 00000000..6509ea7d --- /dev/null +++ b/test/dynamic_loading/registry.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2018-2025 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) + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#define EXPORT_REGISTRY + +#include "registry.hpp" +#include "classes.hpp" + +#include + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +#if defined(_WIN32) || defined(__CYGWIN__) +#include +static_assert(std::is_same_v); +#else +#include +#endif + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +extern "C" { + BOOST_SYMBOL_EXPORT const void* registry_get_ids = (const void*)&get_ids; + BOOST_SYMBOL_EXPORT const void* registry_make_dog = (const void*)&make_dog; +} + +void registry_initialize() { + boost::openmethod::initialize(trace::from_env()); +} diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp new file mode 100644 index 00000000..4c84ef00 --- /dev/null +++ b/test/dynamic_loading/registry.hpp @@ -0,0 +1,46 @@ +// Copyright (c) 2018-2025 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_DYNAMIC_LOADING_REGISTRY_HPP +#define BOOST_OPENMETHOD_TEST_DYNAMIC_LOADING_REGISTRY_HPP + +#include + +#if defined(_WIN32) || defined(__CYGWIN__) +#if defined(EXPORT_REGISTRY) +#define REGISTRY_DECLSPEC boost::openmethod::dllexport +#else +#define REGISTRY_DECLSPEC boost::openmethod::dllimport +#endif + +namespace boost::openmethod { +REGISTRY_DECLSPEC +boost_openmethod_declspec(default_registry&); +} // namespace boost::openmethod + +#endif + +#include + +static auto get_ids() -> const void** { + using namespace boost::openmethod; + namespace mp11 = boost::mp11; + + constexpr auto n_policies = mp11::mp_size::value; + static const void* ids[1 + n_policies + 1] = {default_registry::id()}; + std::size_t i = 1; + + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } + }); + + return ids; +} + +#endif diff --git a/test/test_class_registration.cpp b/test/test_class_registration.cpp index 55b39f8f..119409a7 100644 --- a/test/test_class_registration.cpp +++ b/test/test_class_registration.cpp @@ -22,6 +22,13 @@ struct Animal { struct Dog : Animal {}; struct Bulldog : Dog {}; +BOOST_AUTO_TEST_CASE(registry_own_state) { + using r1 = test_registry_<__COUNTER__>; + using r2 = test_registry_<__COUNTER__>; + static_assert(!std::is_same_v); + BOOST_CHECK_NE(r1::id(), r2::id()); +} + namespace TEST_NS { struct registry : test_registry_<__COUNTER__>::with< diff --git a/test/test_compiler.cpp b/test/test_compiler.cpp index a6e77323..5908d358 100644 --- a/test/test_compiler.cpp +++ b/test/test_compiler.cpp @@ -22,7 +22,7 @@ using overrider = detail::generic_compiler::overrider; auto operator<<(std::ostream& os, const class_* cls) -> std::ostream& { return os - << reinterpret_cast(cls->type_ids[0])->name(); + << reinterpret_cast(cls->ci[0]->type)->name(); } std::string empty = "{}"; diff --git a/test/test_core.cpp b/test/test_core.cpp index ab5e90b1..2523632b 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -126,8 +126,8 @@ static_assert( is_virtual, mp11::mp_list, b, virtual_>>>>, mp11::mp_list>); -struct registry1 : default_registry::with> {}; -struct registry2 : default_registry::with> {}; +using registry1 = test_registry_<__COUNTER__>; +using registry2 = test_registry_<__COUNTER__>; struct non_polymorphic_inplace_vptr {}; diff --git a/test/test_dispatch.cpp b/test/test_dispatch.cpp index 5cd36396..5c803e4b 100644 --- a/test/test_dispatch.cpp +++ b/test/test_dispatch.cpp @@ -335,17 +335,6 @@ BOOST_AUTO_TEST_CASE(simple) { BOOST_TEST( times(diag, 2) == string_pair(DIAGONAL_SCALAR, MATRIX_SCALAR)); } - - if constexpr (std::is_same_v) { - BOOST_TEST( - !detail::vptr_vector_vptrs.empty()); - finalize(); - static_assert(detail::has_finalize_aux< - void, test_registry::policy, - std::tuple<>>::value); - BOOST_TEST( - detail::vptr_vector_vptrs.empty()); - } } } // namespace TEST_NS diff --git a/test/test_policies.cpp b/test/test_policies.cpp index 41317cea..79f2f61d 100644 --- a/test/test_policies.cpp +++ b/test/test_policies.cpp @@ -36,8 +36,8 @@ static_assert(detail::is_registry); struct not_a_policy {}; static_assert(!detail::is_registry); -struct registry1 : default_registry::with> {}; -struct registry2 : default_registry::with> {}; +using registry1 = test_registry_<__COUNTER__>; +using registry2 = test_registry_<__COUNTER__>; struct foo { using category = foo; @@ -70,3 +70,13 @@ BOOST_AUTO_TEST_CASE(test_registry) { BOOST_TEST(®istry2::static_vptr != ®istry1::static_vptr); // BOOST_TEST(®istry2::dispatch_data != ®istry1::dispatch_data); } + +static_assert(has_initialize< + vptr_vector::fn, registry1::compiler>, + std::tuple<>>); +static_assert(!has_initialize< + std_rtti::fn, registry1::compiler>, + std::tuple<>>); +static_assert(has_initialize< + fast_perfect_hash::fn, + registry1::compiler>, std::tuple<>>); diff --git a/test/test_runtime_errors.cpp b/test/test_runtime_errors.cpp index fcd57c9c..e3ed95e4 100644 --- a/test/test_runtime_errors.cpp +++ b/test/test_runtime_errors.cpp @@ -113,7 +113,8 @@ BOOST_AUTO_TEST_CASE(initialize_unknown_class) { { registry::capture capture; BOOST_CHECK_THROW(initialize(), missing_class); - BOOST_TEST(capture().find("unknown class") != std::string::npos); + auto s = capture(); + BOOST_TEST(s.find("unknown class") != std::string::npos); } } } diff --git a/test/test_util.hpp b/test/test_util.hpp index 5562a620..ddbdabdb 100644 --- a/test/test_util.hpp +++ b/test/test_util.hpp @@ -11,19 +11,19 @@ #include #include -template -struct unique final { - using category = unique; +struct unique_category { + using category = unique_category; +}; + +template +struct unique final : unique_category { template struct fn {}; }; template -struct test_registry_ : boost::openmethod::default_registry::with< - unique>, Policies...> { - using registry_type = boost::openmethod::default_registry::with< - unique>, Policies...>; -}; +struct test_registry_ + : boost::openmethod::default_registry::with, Policies...> {}; #define TEST_NS BOOST_PP_CAT(test, __COUNTER__)