diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..22811e48 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,80 @@ +{ + "permissions": { + "allow": [ + "Bash(gh api:*)", + "Bash(where gh:*)", + "WebFetch(domain:github.com)", + "WebFetch(domain:api.github.com)", + "Bash(winget install:*)", + "Bash(export PATH=\"/c/Program Files/GitHub CLI:$PATH\")", + "Bash(gh auth:*)", + "Bash(export PATH=\"/c/Program Files/GitHub CLI:/usr/bin:$PATH\")", + "Bash(c:/msys64/mingw64/bin/g++ -E -x c++ -std=c++17 -I d:/boost/libs/config/include -)", + "Read(//c/msys64/**)", + "Bash(c:/msys64/ucrt64/bin/g++ -c -x c++ -std=c++17 -Id:/boost/libs/config/include /tmp/test_sym.cpp)", + "Bash(c:/msys64/ucrt64/bin/g++ -c -x c++ -std=c++17 -Id:/boost/libs/config/include -o /dev/null /tmp/test_sym.cpp)", + "Bash(c:/msys64/ucrt64/bin/g++.exe -c -x c++ -std=c++17 -Id:/boost/libs/config/include -o /tmp/test_sym.o /tmp/test_sym.cpp)", + "Bash(echo \"exit: $?\")", + "Bash(c:/msys64/ucrt64/bin/g++.exe -c -x c++ -std=c++17 -Id:/boost/libs/config/include /tmp/test_sym.cpp -o /tmp/test_sym.o)", + "Bash(echo \"RC=$?\")", + "Read(//d/boost/libs/config/include/boost/config/**)", + "Bash(ls d:/boost/libs/openmethod/build/stage/bin/*/boost_openmethod-test_dynamic_loading.exe)", + "Bash(d:/boost/libs/openmethod/build/stage/bin/Debug/boost_openmethod-test_dynamic_loading.exe --log_level=message)", + "Bash(echo \"EXIT=$?\")", + "Bash(ls d:/boost/libs/openmethod/build/stage/bin/Debug/*.dll)", + "Bash(ls -la d:/boost/libs/openmethod/build/stage/bin/Debug/*registry*.dll)", + "Bash(PATH=\".:$PATH\" ./boost_openmethod-test_dynamic_loading.exe --log_level=message)", + "Bash(ldd boost_openmethod-test_dynamic_loading.exe)", + "Bash(ctest -R dynamic_loading -V)", + "Bash(ls -la *.dll)", + "Bash(ctest -R dynamic_loading -V --rerun-failed --output-on-failure)", + "Bash(ldd libboost_openmethod-dl_test_registry.dll)", + "Bash(c:/msys64/ucrt64/bin/file.exe d:/boost/libs/openmethod/build/stage/bin/Debug/boost_openmethod-test_dynamic_loading.exe d:/boost/libs/openmethod/build/stage/bin/Debug/libboost_openmethod-dl_test_registry.dll)", + "Bash(ldd d:/boost/libs/openmethod/build/stage/bin/Debug/boost_openmethod-test_dynamic_loading.exe)", + "Bash(PATH=\"/c/Windows/System32\" ./boost_openmethod-test_dynamic_loading.exe)", + "Bash(./boost_openmethod-test_dynamic_loading.exe --log_level=message)", + "Bash(echo $PATH)", + "Bash(PATH=\"c:/msys64/ucrt64/bin:$PATH\" cmd //c \"boost_openmethod-test_dynamic_loading.exe --log_level=message\")", + "Bash(PATH=\"c:/msys64/ucrt64/bin:$PATH\" d:/boost/libs/openmethod/build/stage/bin/Debug/boost_openmethod-test_dynamic_loading.exe --log_level=message)", + "Bash(ldd d:/boost/libs/openmethod/build/stage/bin/Debug/libboost_openmethod-dl_test_registry.dll)", + "Bash(gcc --version)", + "Read(//mingw64/**)", + "Bash(c:/msys64/ucrt64/bin/g++.exe --version)", + "Bash(ls /mingw64/bin/libgcc*)", + "Bash(ls c:/msys64/ucrt64/bin/libgcc*)", + "Bash(PATH=\"c:/msys64/ucrt64/bin:$PATH\" ctest -R \"dynamic_loading$\" -V --output-on-failure)", + "Bash(c:/msys64/ucrt64/bin/ntldd.exe boost_openmethod-test_dynamic_loading.exe)", + "Bash(python3 -c \":*)", + "Bash(c:/msys64/ucrt64/bin/python3 -c \":*)", + "Read(//d/boost/libs/dll/include/boost/dll/**)", + "Bash(c:/msys64/ucrt64/bin/g++.exe -DBOOST_DLL_FORCE_ALIAS_INSTANTIATION -DBOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS -Dboost_openmethod_dl_test_registry_EXPORTS @CMakeFiles/boost_openmethod-dl_test_registry.dir/includes_CXX.rsp -g -fvisibility=default -c D:/boost/libs/openmethod/test/dynamic_loading/registry.cpp -o /tmp/test.o)", + "Read(//c/Users/jl/AppData/Local/Temp/**)", + "Bash(cmake .)", + "Bash(c:/msys64/ucrt64/bin/g++.exe -DBOOST_DLL_FORCE_ALIAS_INSTANTIATION -DBOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS -DEXPORT_REGISTRY -DINCLUDED_FROM='\"\"registry.cpp\"\"' -ID:/boost/libs/openmethod/include -ID:/boost/libs/assert/include -ID:/boost/libs/config/include -ID:/boost/libs/core/include -ID:/boost/libs/throw_exception/include -ID:/boost/libs/dynamic_bitset/include -ID:/boost/libs/container_hash/include -ID:/boost/libs/describe/include -ID:/boost/libs/mp11/include -ID:/boost/libs/preprocessor/include -ID:/boost/libs/smart_ptr/include -ID:/boost/libs/dll/include -ID:/boost/libs/predef/include -ID:/boost/libs/system/include -ID:/boost/libs/compat/include -ID:/boost/libs/variant2/include -ID:/boost/libs/winapi/include -ID:/boost/libs/type_index/include -ID:/boost/libs/filesystem/include -ID:/boost/libs/detail/include -ID:/boost/libs/type_traits/include -ID:/boost/libs/io/include -ID:/boost/libs/iterator/include -ID:/boost/libs/concept_check/include -ID:/boost/libs/fusion/include -ID:/boost/libs/function_types/include -ID:/boost/libs/mpl/include -ID:/boost/libs/utility/include -ID:/boost/libs/tuple/include -ID:/boost/libs/typeof/include -ID:/boost/libs/functional/include -ID:/boost/libs/function/include -ID:/boost/libs/bind/include -ID:/boost/libs/optional/include -g -fvisibility=default -c D:/boost/libs/openmethod/test/dynamic_loading/registry.cpp -o /tmp/test.o)", + "Bash(c:/msys64/ucrt64/bin/g++.exe -DBOOST_DLL_FORCE_ALIAS_INSTANTIATION -DBOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS -ID:/boost/libs/openmethod/include -ID:/boost/libs/config/include -ID:/boost/libs/core/include -ID:/boost/libs/throw_exception/include -ID:/boost/libs/dynamic_bitset/include -ID:/boost/libs/container_hash/include -ID:/boost/libs/describe/include -ID:/boost/libs/mp11/include -ID:/boost/libs/preprocessor/include -ID:/boost/libs/smart_ptr/include -ID:/boost/libs/dll/include -ID:/boost/libs/predef/include -ID:/boost/libs/system/include -ID:/boost/libs/compat/include -ID:/boost/libs/variant2/include -ID:/boost/libs/winapi/include -ID:/boost/libs/type_index/include -ID:/boost/libs/filesystem/include -ID:/boost/libs/assert/include -ID:/boost/libs/detail/include -ID:/boost/libs/type_traits/include -ID:/boost/libs/io/include -ID:/boost/libs/iterator/include -ID:/boost/libs/concept_check/include -ID:/boost/libs/fusion/include -ID:/boost/libs/function_types/include -ID:/boost/libs/mpl/include -ID:/boost/libs/utility/include -ID:/boost/libs/tuple/include -ID:/boost/libs/typeof/include -ID:/boost/libs/functional/include -ID:/boost/libs/function/include -ID:/boost/libs/bind/include -ID:/boost/libs/optional/include -g -fvisibility=default -fsyntax-only D:/boost/libs/openmethod/test/dynamic_loading/registry.cpp)", + "Bash(echo \"RC=$PIPESTATUS[0]\")", + "Bash(c:/msys64/ucrt64/bin/g++.exe -DBOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS -ID:/boost/libs/openmethod/include -ID:/boost/libs/config/include -ID:/boost/libs/core/include -ID:/boost/libs/throw_exception/include -ID:/boost/libs/dynamic_bitset/include -ID:/boost/libs/container_hash/include -ID:/boost/libs/describe/include -ID:/boost/libs/mp11/include -ID:/boost/libs/preprocessor/include -ID:/boost/libs/smart_ptr/include -ID:/boost/libs/dll/include -ID:/boost/libs/predef/include -ID:/boost/libs/system/include -ID:/boost/libs/compat/include -ID:/boost/libs/variant2/include -ID:/boost/libs/winapi/include -ID:/boost/libs/type_index/include -ID:/boost/libs/filesystem/include -ID:/boost/libs/assert/include -ID:/boost/libs/detail/include -ID:/boost/libs/type_traits/include -ID:/boost/libs/io/include -ID:/boost/libs/iterator/include -ID:/boost/libs/concept_check/include -ID:/boost/libs/fusion/include -ID:/boost/libs/function_types/include -ID:/boost/libs/mpl/include -ID:/boost/libs/utility/include -ID:/boost/libs/tuple/include -ID:/boost/libs/typeof/include -ID:/boost/libs/functional/include -ID:/boost/libs/function/include -ID:/boost/libs/bind/include -ID:/boost/libs/optional/include -g -fvisibility=default -fsyntax-only D:/boost/libs/openmethod/test/dynamic_loading/registry.cpp)", + "Bash(c:/msys64/ucrt64/bin/g++.exe -x c++ -fsyntax-only -)", + "Bash(echo RC=$?)", + "Bash(c:/msys64/ucrt64/bin/g++.exe -fsyntax-only /tmp/test_min.cpp)", + "Bash(echo 'static_assert\\(false, \"\"hello\"\"\\);')", + "Bash(c:/msys64/ucrt64/bin/g++.exe -std=c++17 -fsyntax-only /tmp/test_min.cpp)", + "Bash(cmd //c \"c:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -std=c++17 -fsyntax-only /tmp/test_min.cpp 2>&1\")", + "Bash(c:/msys64/ucrt64/bin/g++.exe -std=c++17 -fsyntax-only C:/Users/jl/AppData/Local/Temp/test_min.cpp)", + "Bash(cmd //c \"cd /d D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test\\\\dynamic_loading && C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -DBOOST_FILESYSTEM_NO_LIB -DBOOST_FILESYSTEM_STATIC_LINK=1 -DBOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS -Dboost_openmethod_dl_test_registry_EXPORTS @CMakeFiles\\\\boost_openmethod-dl_test_registry.dir\\\\includes_CXX.rsp -g -fvisibility=default -c D:\\\\boost\\\\libs\\\\openmethod\\\\test\\\\dynamic_loading\\\\registry.cpp -o NUL 2> D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\gcc_err.txt\")", + "Bash(cmd //c \"echo int main\\(\\){} > D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp && C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -std=c++17 -c D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp -o D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.o 2> D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\gcc_err2.txt && echo SUCCESS || echo FAILED\")", + "Bash(cmd //c \"C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe --version 2>&1\")", + "Bash(cmd //c \"echo int main\\(\\){} > D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp && C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -std=c++17 -fsyntax-only D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp 2>&1 && echo SUCCESS || echo FAILED\")", + "Bash(cmd //c \"C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -std=c++17 -fsyntax-only D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp 2>&1\")", + "Bash(cmd //c \"type D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp\")", + "Bash(cmd //c \"C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -v -std=c++17 -fsyntax-only D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp 2>&1\")", + "Bash(cmd //c \"C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -std=c++17 -c D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp -o D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.o 2>&1 && echo SUCCESS || echo FAILED\")", + "Bash(cmd //c \"C:\\\\msys64\\\\ucrt64\\\\bin\\\\g++.exe -v -std=c++17 -c D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.cpp -o D:\\\\boost\\\\libs\\\\openmethod\\\\build\\\\test_min.o 2>&1\")", + "Read(//d/boost/libs/dll/test/**)", + "Bash(ls -la d:/boost/libs/openmethod/build/doc/modules/ROOT/examples/shared_libs/boost_openmethod-dynamic.exe d:/boost/libs/openmethod/build/doc/modules/ROOT/examples/shared_libs/*shared*)" + ], + "additionalDirectories": [ + "\\tmp" + ] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99e8d8ac..63434c43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,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..ff02edd0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,368 @@ +# 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 across modules by sharing the registry's +state through an export/import decoration of a single symbol. On Windows (and Cygwin) the decoration +is dllexport/dllimport; on ELF it is `visibility("default")` on the export side. In the common case +off Windows the decoration can be omitted entirely — the state then has ordinary external linkage +and is shared by the dynamic linker — but that only works if the program is *not* built with hidden +visibility. Under `-fvisibility=hidden` (e.g. the Boost super-project's `BoostRoot.cmake`) an +implicitly instantiated `st` is a COMDAT that gets internalized to a per-module local symbol, so +the export/import macros must be used on ELF too (they emit a single strong, default-visibility +explicit instantiation that the other modules import). + +**One shared state variable**: All of a registry's mutable state — the class/method/overrider +lists *and* every stateful policy's `state` (held together in the `registry_state_type::policies` +tuple) — lives in a single variable, `registry_state::st` of type +`detail::registry_state_type`. A registry reaches it through `Registry::state()`. Sharing +a registry across a DLL boundary therefore means sharing this one symbol. + +`registry_state` (in `boost::openmethod`) is a deliberately thin, function-free class whose only +member is the static `st`. It is kept *separate* from `registry_state_type` (the struct holding the +actual fields, in `detail`) because MSVC only honors `dllexport`/`dllimport` on a *whole-class* +explicit instantiation — not on a variable template (clients silently get a private copy) nor on a +static-data-member instantiation (error C2720) — and dllexporting `registry_state_type` directly +would also decorate its member functions and the policies' nested `state` types, which MSVC rejects +(error C2513). A one-member, function-free class is the only shape MSVC will export as a whole and +import via `extern template`. + +**Mechanism — `extern template` / explicit instantiation**: the shared symbol is +`registry_state::st`, where `registry_type` is the `registry` +*base* of the registry struct (that is what `registry::state()` uses — never key on the derived +struct). The owning module compiles, in exactly one TU, an exported explicit instantiation +definition; clients compile an imported explicit instantiation declaration, so they reference +the owner's symbol instead of instantiating their own copy: +```cpp +// owner (one TU): +template struct BOOST_SYMBOL_EXPORT registry_state; +// clients: +extern template struct BOOST_SYMBOL_IMPORT registry_state; +``` +`BOOST_SYMBOL_EXPORT`/`BOOST_SYMBOL_IMPORT` are dllexport/dllimport on Windows and +`visibility("default")` / empty on ELF, so the same two lines serve both platforms. This is no +longer guarded by `_WIN32`: on ELF the pair is what makes the state shareable under hidden +visibility. + +**Registries are structs, not aliases — do not "simplify" this**: `default_registry` (and the +documented custom-registry pattern) is deliberately a *struct deriving from* `registry`, +never a type alias. The short struct name keeps mangled/linker names short for everything keyed on +the registry (methods, virtual_ptrs, `static_vptr`, registrars...); an alias would expand to the +full policy list in all of those symbols. This is also why the `::registry_type` spelling in the +explicit instantiations above cannot be avoided: an explicit instantiation instantiates exactly the +specialization written, so making `registry_state` work would require +`default_registry` to *be* its base (an alias) — rejected for the mangled-name reason. Only the +shared state symbol carries the full policy list, which is accepted. + +**Usage (predefined registries)**: define exactly one of the macros *before* including +`` (or ``): +`BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY` in **exactly one TU** of the module that owns the +state (it emits an explicit instantiation *definition*, which may appear only once in the +program), `BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY` in every client TU. `indirect_registry` has +its own state and its own analogous pair, `BOOST_OPENMETHOD_{EXPORT,IMPORT}_INDIRECT_REGISTRY`. +With neither macro, the state has ordinary linkage — fine off Windows only when the program is not +built with hidden visibility. + +**Custom registries**: the library provides no general macro. Users write the explicit +instantiation / `extern template` declaration themselves (as in the snippet above, possibly +wrapped in their own per-registry macros), after the registry's definition and before any use. + +**Methods need no decoration**: method objects are *consolidated* across modules at `initialize()` +time, not shared via a single symbol, so `BOOST_OPENMETHOD(...)` takes no declspec argument. + +See `doc/modules/ROOT/examples/shared_libs/` and `test/dynamic_loading/` for complete examples. + +**Dynamic Loading Test** (`test/dynamic_loading/`): verifies that the registry state is a single +shared symbol across modules. `get_ids()` (in `registry.hpp`) returns the registry-state address +(`default_registry::id()`) followed by any stateful-policy `id()`s; `main.cpp`'s `same_ids()` +asserts these addresses are identical across modules. (Policy state now lives inside +`registry_state_type`, so in practice the registry-state address is the one shared symbol.) Files: +- `registry.hpp` — maps `EXPORT_REGISTRY` → the `BOOST_OPENMETHOD_{EXPORT,IMPORT}_{DEFAULT,INDIRECT}_REGISTRY` pair matching the registry under test (indirect iff `BOOST_OPENMETHOD_DEFAULT_REGISTRY` is defined on the command line) before including `default_registry.hpp`; defines `get_ids()` +- `classes.hpp` — `Animal`/`Dog` definitions + `make_dog` +- `method.hpp` — declares the `speak`/`meet` methods (no declspec arguments) +- `registry.cpp` — compiled with `EXPORT_REGISTRY`; the shared library that owns and exports the registry state +- `method.cpp` — client (imports the registry state); defines base overriders, exports C entry points +- `overrider.cpp` — dynamically loaded at runtime; adds a Dog overrider +- `main.cpp` — links the registry + method libs, loads the overrider lib, checks `same_ids`, calls `initialize()`, tests cross-module 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 State Pattern + +Stateful policies keep their data in a nested `struct state` inside `fn` and reach it +through the registry's shared state. `registry_state_type` automatically gathers every +policy's `state` into its `policies` tuple, so a policy's state is part of the single shared +`registry_state::st` variable — no per-policy DLL decoration, `MAKE_STATICS` macro, or +`id()` function is needed (those were all removed). + +To add state to a policy's `fn`: + +1. **Declare a public `struct state`** with the data members: + ```cpp + struct state { + detail::hash_fn fn; + std::vector control; + }; + ``` +2. **Add a private accessor** returning this policy's slot in the registry's tuple: + ```cpp + static auto& st() { + return Registry::template state(); + } + ``` + `Registry::state

()` (a templated overload of `Registry::state()`, alongside the + non-template overload that returns the whole `registry_state_type`) returns + `P::fn::state&` via `detail::get` (get-by-type) on the `policies` tuple. +3. **Use `st()`** wherever the state is read or written: `st().fn`, `st().control`, etc. (name it + `st()` so it does not shadow the `state` type). + +`registry_state_type` (in `preamble.hpp`) builds its `policies` tuple by instantiating each +policy's `fn`, keeping those that have a nested `state` (`detail::has_policy_state`), and +storing one of each: +```cpp +mp_apply, Registry::policy_list>>>> +``` +`detail::tuple` (defined in `preamble.hpp`) is a minimal tuple used instead of `std::tuple` — +which is very expensive to instantiate with MSVC — for the policy-state tuple, the `use_classes` +registrar tuple, and `method::override::impl`. It holds each element in a `tuple_element` base +class (flat multiple inheritance, O(1) instantiation depth); `detail::get` retrieves an element by +type via a base-class cast. Element types must therefore be unique: lists that may contain +duplicates (`use_classes` with a class listed twice, `override`) are deduplicated with +`mp_unique` before instantiating the tuple. The `initialize()`/`finalize()` *options* tuple +deliberately remains `std::tuple`: it is a documented policy-API signature. + +Only the registry itself has an `id()` (returning `&state().classes`), detected via +`detail::has_id` (generated by `BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(id)`); the dynamic_loading +test uses it to compare the shared state address across modules. diff --git a/announce.md b/announce.md new file mode 100644 index 00000000..459ab22d --- /dev/null +++ b/announce.md @@ -0,0 +1,129 @@ +# YOMM2 is reborn as Boost.OpenMethod + +In early 2025, I submitted YOMM2 for inclusion in the Boost libraries, under the +name OpenMethod. The library underwent formal review, and it was accepted with +conditions. OpenMethod became part of Boost in version 1.90. + +As a consequence, I am discontinuing work on YOMM2. + +[Boost.OpenMethod](https://www.boost.org/doc/libs/latest/libs/openmethod/doc/html/openmethod/index.html) is available to download from: + +- the [Boost website](https://www.boost.org) +- [vcpkg](https://vcpkg.roundtrip.dev/ports/boost-openmethod) as a modular + package with dependencies to the required Boost libraries +- [Conan](https://conan.io/center/recipes/boost) as part of the whole Boost + package + +OpenMethod is available on [Compiler Explorer](https://godbolt.org/z/W6684Tfaz) - make sure to select Boost 1.90 (or above) in Libraries. + +I encourage YOMM2 users to switch to OpenMethod as quickly as convenient. +OpenMethod is not directly backward compatible with YOMM2. However, migrating +from one to the other should be painless for all basic uses of the library - see +an example at the end of this post. If you used advanced features such as +policies and facets, a little more work may be required, but the underlying +ideas remain the same, yet presented in a more ergonomic way. + +## What Has Changed and Why? + +On the surface, a lot has changed, but, just underneath, it is the same library, +only better. Much better, in my (biased) opinion. This is due to: + +- The freedom to clean up and rework a library that has evolved over seven + years, without being bound by backward compatibility. + +- The feedback - comments, suggestions, criticisms - gathered during the Boost + formal review. + +I will go through the major changes in this section, starting with the most +basic features, then going into more advanced ones. + +There was a lot of renaming, obviously. `yorel::yomm2` is now +`boost::openmethod`. `setup` becomes `initialize`. Method specializations are +now called "overriders". + +`declare_method` and `define_method` become `BOOST_OPENMETHOD` and +`BOOST_OPENMETHOD_OVERRIDE`, and the return type moves from first macro +parameter to third - i.e., just after the method's parameter list. This is not +gratuitous, nor an attempt at "looking modern". This solves a minor irritation: +return types can now contain commas, without the need for workarounds such as +using a typedef or `BOOST_IDENTITY_TYPE`. + +`virtual_ptr` was an afterthought in YOMM2. In OpenMethod, it becomes the +preferred way of passing virtual arguments. It also now supports all the +operations normally expected on a smart pointer. `virtual_` still exists, but it +is dedicated to more advanced use cases like embedding a vptr in an object. + +No names (excepting macros) are injected in the global namespace anymore. The most frequently used constructs can be imported in the current namespace with `using namespace boost::openmethod::aliases`. + +Constructs that were previously undocumented have been cleaned up and made +public. The most important is `virtual_traits`, which governs what can be used +as a virtual parameter, how to extract the polymorphic part of an argument (e.g. +a plain reference, a smart pointer to an object, ...), how to cast virtual +arguments to the types expected by the overriders, etc. This makes it possible +to use your favorite smart pointer in virtual parameters. + +"Policies" and "facets" are now called "registries" and "policies". That part of +YOMM2 relied heavily on the CRTP. Policies (ex-facets) are now MP11-style quoted +metafunctions that take a registry. So, CRTP is still there, but it is not an +eyesore anymore. The policies/facets that were used only in setup/initialize +(like tracing the construction of dispatch data) are now optional arguments of +`initialize`. + +The most recent experiment in YOMM2 revolved around moving stuff to compile +time: method dispatch tables (for reduced footprint); and method offsets in the +v-tables (for scraping the last bit of performance). It did not make it into +OpenMethod. I have not lost interest in the feature though. It will reappear at +some point in the future, hopefully in a more convenient manner. + +## Porting from YOMM2 to OpenMethod + +Many of the examples can be ported in a quick-and-dirty manner using a +compatibility header such as: + +```c++ +// + +#include +#include + +#define register_classes BOOST_OPENMETHOD_CLASSES +#define declare_method(RETURN, ID, ARGS) \ + BOOST_OPENMETHOD(ID, ARGS, RETURN) +#define define_method(RETURN, ID, ARGS) \ + BOOST_OPENMETHOD_OVERRIDE(ID, ARGS, RETURN) +using boost::openmethod::virtual_; + +namespace yorel { + namespace yomm2 { + void update() { + boost::openmethod::initialize(); + } + } +} +``` + +For example, here is the "adventure" example [on Compiler +Explorer](https://godbolt.org/z/1G5jjzzfz) using the compatibility header. + +A proper port takes little more effort: + +1. Move the return types using a simple regex substitution. +2. Change the initialization (typically only in `main`'s translation unit). +3. Switch virtual arguments from `virtual_` to `virtual_ptr` (not mandatory but + highly recommended). + +Here is "adventure", fully ported to Boost.OpenMethod, [on Compiler +Explorer](https://godbolt.org/z/5TjxM85Ej). + +## Support + +Support is available on a purely voluntary, non-committal basis from the author. +The Boost community as a whole has a good record of welcoming requests and +suggestions from their users. Please reach out to: + +- The [Boost Users mailing + list](https://lists.boost.org/mailman3/lists/boost-users.lists.boost.org/) - + please put [openmethod] at the beginning of the subject +- The [#boost-openmethod + channel](https://app.slack.com/client/T21Q22G66/C0A16HR702G?selected_team_id=T21Q22G66) + on the CppLang Slack \ No newline at end of file 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..7eefd99d 100644 --- a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt +++ b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt @@ -7,88 +7,91 @@ 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}" -) +# The shared library owns the registry state (static_extensions.cpp defines +# BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY); the executable imports it. This +# keeps the link dependency in the usual direction on Windows: the exe links +# the library's import library. +add_library(boost_openmethod-static_shared SHARED static_extensions.cpp) +target_link_libraries(boost_openmethod-static_shared PRIVATE Boost::openmethod) 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) +target_link_libraries(boost_openmethod-static + PRIVATE Boost::openmethod boost_openmethod-static_shared) +# Co-locate the library with the exe so it is found at run time on Windows, +# where there is no rpath. +set_target_properties(boost_openmethod-static_shared PROPERTIES + LIBRARY_OUTPUT_DIRECTORY $ + RUNTIME_OUTPUT_DIRECTORY $) + +add_test(NAME boost_openmethod-static COMMAND boost_openmethod-static) # ------------------------------------------------------------------------------ # dynamic loading, direct virtual_ptrs +# The executable owns the registry state (dynamic_main.cpp defines +# BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY); the runtime-loaded library imports +# it, so it links against the exe - the reverse of the usual direction. On +# Windows this needs the exe's import library, generated because of +# ENABLE_EXPORTS; on other platforms ENABLE_EXPORTS adds -rdynamic (or +# equivalent). 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() + +add_library(boost_openmethod-shared SHARED extensions.cpp) +target_link_libraries(boost_openmethod-shared PRIVATE Boost::openmethod boost_openmethod-dynamic) +# Co-locate the library with the exe on all platforms. On Windows a DLL is a +# runtime output and already lands next to the exe, but on other platforms a +# shared library is a *library* output and would stay in this subdirectory, +# where the test below cannot find it. +set_target_properties(boost_openmethod-shared PROPERTIES + ENABLE_EXPORTS ON + LIBRARY_OUTPUT_DIRECTORY $) + +# Run from the directory holding the freshly-built exe and its co-located DLL +# (CMAKE_RUNTIME_OUTPUT_DIRECTORY). dynamic_main.cpp loads the DLL by relative +# name, which boost::dll resolves against the working directory; without this +# the test would pick up a stale copy from the source/binary tree. +add_test( + NAME boost_openmethod-dynamic_shared + COMMAND boost_openmethod-dynamic + WORKING_DIRECTORY $) # ------------------------------------------------------------------------------ # dynamic loading, indirect virtual_ptrs +# Same layout as the previous example, with every target compiled with +# indirect_registry as the default registry. indirect_main.cpp and +# indirect_extensions.cpp use the BOOST_OPENMETHOD_{EXPORT,IMPORT}_INDIRECT_REGISTRY +# macro pair accordingly. +add_executable(boost_openmethod-indirect indirect_main.cpp) +target_compile_definitions(boost_openmethod-indirect + PRIVATE 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_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) +target_compile_definitions(boost_openmethod-indirect_shared + PRIVATE BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) +target_link_libraries(boost_openmethod-indirect_shared + PRIVATE Boost::openmethod boost_openmethod-indirect) 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}" -) + LIBRARY_OUTPUT_DIRECTORY $ + RUNTIME_OUTPUT_DIRECTORY $) -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}" +add_test( + NAME boost_openmethod-indirect + COMMAND boost_openmethod-indirect + WORKING_DIRECTORY $) + +# NOTE: build the following target (or ALL) when working on this. Just building +# the executables will *not* build the runtime-loaded libraries. +add_custom_target(boost_openmethod-shared-all ALL + DEPENDS + boost_openmethod-static boost_openmethod-static_shared + boost_openmethod-dynamic boost_openmethod-shared + boost_openmethod-indirect boost_openmethod-indirect_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() diff --git a/doc/modules/ROOT/examples/shared_libs/animals.hpp b/doc/modules/ROOT/examples/shared_libs/animals.hpp index e725355c..a20c9b8b 100644 --- a/doc/modules/ROOT/examples/shared_libs/animals.hpp +++ b/doc/modules/ROOT/examples/shared_libs/animals.hpp @@ -8,6 +8,8 @@ // clang-format off +#include + // tag::content[] // animals.hpp @@ -18,6 +20,11 @@ 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, diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index b74ea6dc..1f2314c7 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -3,18 +3,13 @@ // 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 - // tag::before[] // dynamic_main.cpp +#define BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY + #include "animals.hpp" +// end::before[] #include #include @@ -23,12 +18,11 @@ #include #include -using namespace boost::openmethod::aliases; +using namespace boost::openmethod; -struct Cow : Herbivore {}; -struct Wolf : Carnivore {}; +// tag::before[] -BOOST_OPENMETHOD_CLASSES(Animal, Herbivore, Cow, Wolf, Carnivore); +BOOST_OPENMETHOD_CLASSES(Herbivore, Cow, Carnivore, Wolf); BOOST_OPENMETHOD_OVERRIDE( meet, (virtual_ptr, virtual_ptr), std::string) { @@ -39,64 +33,74 @@ 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( + "boost_openmethod-shared", + 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"; // run + // 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..18271414 100644 --- a/doc/modules/ROOT/examples/shared_libs/extensions.cpp +++ b/doc/modules/ROOT/examples/shared_libs/extensions.cpp @@ -5,12 +5,34 @@ // tag::content[] // extensions.cpp + +// end::content[] + +// static_extensions.cpp and indirect_extensions.cpp reuse this file, defining +// a different export/import macro before including it. +#if !defined(BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY) && \ + !defined(BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY) +// tag::content[] +#define BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY +// end::content[] +#endif + +// tag::content[] + #include "animals.hpp" +// end::content[] -using namespace boost::openmethod::aliases; +using namespace boost::openmethod; + +// 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"; } @@ -24,10 +46,8 @@ struct Tiger : Carnivore {}; BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore); extern "C" { -#ifdef _WIN32 -__declspec(dllexport) -#endif -auto make_tiger() -> Animal* { +BOOST_SYMBOL_EXPORT auto make_tiger() -> Animal* { + BOOST_ASSERT(default_registry::static_vptr != nullptr); return new Tiger; } } diff --git a/doc/modules/ROOT/examples/shared_libs/indirect_extensions.cpp b/doc/modules/ROOT/examples/shared_libs/indirect_extensions.cpp index 95bc1745..d8e96922 100644 --- a/doc/modules/ROOT/examples/shared_libs/indirect_extensions.cpp +++ b/doc/modules/ROOT/examples/shared_libs/indirect_extensions.cpp @@ -1 +1,12 @@ +// 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) + +// indirect_extensions.cpp + +// This library is compiled with +// -DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry; import the matching +// registry state. +#define BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY #include "extensions.cpp" diff --git a/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp b/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp index 555435e0..9fe3eb47 100644 --- a/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp @@ -3,39 +3,31 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifdef _MSC_VER -#define LIBRARY_NAME "indirect_shared.dll" -#else -#define LIBRARY_NAME "libindirect_shared.so" -#endif +// This program is compiled with +// -DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry, so the registry +// under use is indirect_registry, and the INDIRECT macro pair applies. // tag::content[] // indirect_main.cpp +#define BOOST_OPENMETHOD_EXPORT_INDIRECT_REGISTRY + #include "animals.hpp" #include #include #include #include -#include #include using namespace boost::openmethod::aliases; -struct Cow : Herbivore {}; -struct Wolf : Carnivore {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Herbivore, Cow, Wolf, Carnivore); - BOOST_OPENMETHOD_OVERRIDE( meet, (virtual_ptr, virtual_ptr), std::string) { return "greet"; } auto main() -> int { - using namespace boost::openmethod::aliases; - std::cout << "Before loading the shared library.\n"; boost::openmethod::initialize(); @@ -44,21 +36,20 @@ auto main() -> int { std::cout << "cow meets wolf -> " << meet(*gracie, *willy) << "\n"; // greet std::cout << "wolf meets cow -> " << meet(*willy, *gracie) << "\n"; // greet - std::cout << "cow.vptr() = " << gracie.vptr() << "\n"; // 0x5d3121d22be8 - std::cout << "wolf.vptr() = " << willy.vptr() << "\n"; // 0x5d3121d22bd8 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-indirect_shared", + boost::dll::load_mode::rtld_global | + boost::dll::load_mode::append_decorations); boost::openmethod::initialize(); + // The virtual_ptrs made before the reload still dispatch correctly: std::cout << "cow meets wolf -> " << meet(*gracie, *willy) << "\n"; // run std::cout << "wolf meets cow -> " << meet(*willy, *gracie) << "\n"; // hunt - std::cout << "cow.vptr() = " << gracie.vptr() << "\n"; // 0x5d3121d21998 - std::cout << "wolf.vptr() = " << willy.vptr() << "\n"; // 0x5d3121d21988 return 0; } +// end::content[] diff --git a/doc/modules/ROOT/examples/shared_libs/static_main.cpp b/doc/modules/ROOT/examples/shared_libs/static_main.cpp index a285a2a2..6b7f6687 100644 --- a/doc/modules/ROOT/examples/shared_libs/static_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/static_main.cpp @@ -6,17 +6,17 @@ // tag::content[] // static_main.cpp +// The shared library (static_extensions.cpp) owns the registry state; the +// executable imports it. +#define BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY + #include "animals.hpp" #include #include +#include using namespace boost::openmethod::aliases; -struct Cow : Herbivore {}; -struct Wolf : Carnivore {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Herbivore, Cow, Wolf, Carnivore); - BOOST_OPENMETHOD_OVERRIDE( meet, (virtual_ptr, virtual_ptr), std::string) { return "greet"; diff --git a/doc/modules/ROOT/pages/ref_macros.adoc b/doc/modules/ROOT/pages/ref_macros.adoc index 1fc54a72..90720c06 100644 --- a/doc/modules/ROOT/pages/ref_macros.adoc +++ b/doc/modules/ROOT/pages/ref_macros.adoc @@ -43,4 +43,12 @@ The following macros are for advanced uses of the library. | Expands to core `method` specialization. | xref:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER] | Creates a registrar object. +| xref:BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY] +| Exports the state of `default_registry` from the module that owns it. +| xref:BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY] +| Imports the state of `default_registry` from the module that owns it. +| xref:BOOST_OPENMETHOD_EXPORT_INDIRECT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_INDIRECT_REGISTRY] +| Exports the state of `indirect_registry` from the module that owns it. +| xref:BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY] +| Imports the state of `indirect_registry` from the module that owns it. |=== diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index b83f4fe1..89101a45 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -72,7 +72,7 @@ the definition of `default_registry`, copied from struct default_registry : registry< policies::std_rtti, - policies::vptr_vector, policies::fast_perfect_hash, + policies::fast_perfect_hash, policies::vptr_vector, policies::default_error_handler, policies::stderr_output #ifdef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS @@ -87,6 +87,16 @@ When defining a new registry, it is recommended to define a new class, derived from `registry<...>`, rather than via a typedef, which would create excessively long symbol names and make debugging harder. +The order of the policies matters. When cpp:initialize[] runs, it calls each +policy's `initialize` in the order the policies appear in the registry, from +left to right; cpp:finalize[] calls each policy's `finalize` in the reverse +order. A policy that depends on another policy having been initialized must +therefore be listed _after_ its dependency. In particular, `vptr_vector` reads +the state of the `type_hash` policy, so the `type_hash` policy must come before +`vptr_vector` — as in `default_registry` above, where `fast_perfect_hash` +precedes `vptr_vector`. This particular requirement is enforced with a +`static_assert`. + A registry can also be created by copying an existing registry's policies, using the cpp:with[] and cpp:without[] nested templates. For example, `indirect_registry` is a tweak of `default_registry`: diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index 3fc689e1..a00c8f01 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -5,12 +5,51 @@ This section discusses how OpenMethod interoperates with shared libraries on Linux, other POSIX-like platforms, and Windows. +OpenMethod uses global data to keep track of methods, overriders and classes, +all managed by static constructors and destructors. cpp:initialize[] uses that +information to set up dispatch tables. For a program and its shared libraries +to contribute classes, methods and overriders to the same registry, that data +must be truly global: a single copy, shared by all the modules. + +All of a registry's mutable state lives in a single variable. Sharing a +registry across modules therefore means sharing that one symbol: the _owning_ +module exports it, and every _client_ module imports it. For +`default_registry`, two macros control this: + +- `BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY` -- defined in _exactly one_ + translation unit of the owning module, it emits an explicit instantiation of + the registry state, decorated for export. +- `BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY` -- defined in every client + translation unit, it emits an `extern template` declaration, decorated for + import, so clients reference the owner's symbol instead of instantiating + their own copy. + +Either macro must be defined _before_ including +`` (directly or via +``). The same two macros work on all platforms; the +<> section explains what they expand to, and when they can be +omitted. + +Nothing else needs decoration. In particular, methods declared with +`BOOST_OPENMETHOD` require no export or import: method objects are consolidated +across modules when `initialize` runs (see <>). + +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 +for any of this - nor to call `initialize` again. + ## Static Linking -Statically linking to a shared library behaves the same way as linking to a -static library on all platforms. Objects from the program and the shared -libraries can contribute classes, methods and overriders to the default -registry, or any registries. +When a program links against a shared library at build time, all the modules +are loaded before `main` starts, their static constructors run, and a single +call to `initialize` sets up the dispatch tables. The only requirement is the +one stated above: one module owns the registry state, the others import it. + +The natural arrangement is for the shared library to own the registry: the +program already links against the library, so, on Windows, the import library +dependency runs in the usual direction. The `static_main.cpp` and +`static_extensions.cpp` programs in the examples directory demonstrate this +arrangement. ## Dynamic Linking @@ -18,46 +57,38 @@ By "dynamic linking", we mean a program loading a shared library after it has started, and accessing its content. A common application of dynamic linking is to implement plugin architectures. -OpenMethod uses global data to keep track of methods, overriders and classes, -all managed by static constructors and destructors. cpp:initialize[] uses that -information to set up dispatch tables. These variables must be truly global and -unique for the library to operate correctly. +A dynamically loaded library can add classes, methods and overriders to an +existing registry. `initialize` must be called to rebuild the dispatch tables +after loading or unloading a shared library. -Under this condition, a shared library can dynamically add classes, methods and -overriders to an existing registry. `initialize` must be called to rebuild the -dispatch tables after loading or unloading a shared library. +Here the program is the natural owner of the registry state: it must be able +to run before the library is loaded, and regardless of whether it is loaded at +all. -Dynamic linking on Linux and POSIX-like platforms fulfills the unicity -requirement with little effort from the programmer. On Linux, it is just a -matter of tossing in `-rdynamic`, or adding `ENABLE_EXPORTS ON` to the library's -target properties if using `cmake`. - -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 and the +`meet` method: [source,c++] ---- include::{shared}/animals.hpp[tag=content] ---- -The shared library contains an object that adds two overriders, a new class, -`Tiger`, and a factory function: +The main program owns the registry state, so it defines the `EXPORT` macro - +in its only translation unit, satisfying the exactly-one rule. It provides a +catch-all overrider, then calls the `meet` method: [source,c++] ---- -include::{shared}/extensions.cpp[tag=content] +include::{shared}/dynamic_main.cpp[tag=before] ---- -The main program adds a couple of classes then calls `meet` method. At this -point, we only have the catch-call overrider: +The shared library imports the registry state. It adds two overriders, a new +class, `Tiger`, and a factory function: [source,c++] ---- -include::{shared}/dynamic_main.cpp[tag=before] +include::{shared}/extensions.cpp[tag=content] ---- We load the shared library using Boost.DLL. After calling `initialize`, the new @@ -77,38 +108,35 @@ overriders provided by the shared library are removed from the method. include::{shared}/dynamic_main.cpp[tag=unload] ---- -## Windows +NOTE: `dlclose` does not necessarily unload the library when using gcc's +`-rdynamic` option. The library may remain loaded until the program exits, so +the output of the last `meet` call may still be the same as the previous one. -If we try the example on Windows, the result is disappointing: +### CMake Setup -``` -Before loading the shared library. -cow meets wolf -> greet -wolf meets cow -> greet +Since the executable owns the registry state, the shared library must link +against the executable to resolve the imported symbol. This is the reverse of +the typical linking direction: -After loading the shared library. -cow meets wolf -> greet -wolf meets cow -> greet -cow meets tiger -> unknown class struct Tiger -``` +[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) -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`. +add_library(my_plugin SHARED extensions.cpp) +target_link_libraries(my_plugin PRIVATE Boost::openmethod my_app) +---- -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. +On Windows, `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` (or the platform's equivalent), which makes +the executable's symbols visible to the libraries it loads. -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. +NOTE: `BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY` produces an explicit +instantiation _definition_, which may appear only once in the program. Define +it in exactly one translation unit of the owning module; all other translation +units, in every module, use the `IMPORT` macro. ## Indirect Vptrs @@ -125,8 +153,13 @@ that has the same policies as `default_registry`, plus `indirect_vptr`. We can use it to override the default registry, for example using a compiler command-line switch (`-DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry`). +cpp:indirect_registry[] has its own state, controlled by the analogous +`BOOST_OPENMETHOD_EXPORT_INDIRECT_REGISTRY` and +`BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY` pair. + Here is an example of a program that carries `virtual_ptr`{empty}s across -`initialize` calls: +`initialize` calls. Note that, since the registry in use is now +`indirect_registry`, the `INDIRECT` macro pair is used to share its state: [source,c++] ---- @@ -134,4 +167,106 @@ include::{shared}/indirect_main.cpp[tag=content] ---- This program loads a shared library that is itself compiled with -`-DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry`. +`-DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry`, and imports the +registry state with `BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY`. + +## Custom Registries + +A custom registry is shared the same way, but the library does not provide +macros for it. Write the explicit instantiation and the `extern template` +declaration yourself (possibly wrapped in your own per-registry macros), after +the registry's definition: + +[source,c++] +---- +struct my_registry : boost::openmethod::registry {}; + +#ifdef MY_MODULE_OWNS_REGISTRY // in exactly one TU of the owning module +template struct BOOST_SYMBOL_EXPORT + boost::openmethod::registry_state; +#else +extern template struct BOOST_SYMBOL_IMPORT + boost::openmethod::registry_state; +#endif +---- + +[#implementation_notes] +## Implementation Notes + +### One state variable per registry + +Everything mutable in a registry is agglomerated into a single variable. The +registry's class and method lists, the dispatch tables, and the state of every +stateful policy (for example, the hash function parameters of +cpp:fast_perfect_hash[], or the vector of v-table pointers of +cpp:vptr_vector[]) are all members of one object, of which there is exactly +one instance per registry. Policies do not keep their own globals: their state +is gathered, by template machinery, into a tuple inside that same object. + +This is what makes sharing a registry across modules tractable: there is +exactly _one_ symbol to export and import, whatever the registry's policies. +The variable is the sole member of cpp:registry_state[], a deliberately thin, +function-free class template. That shape is dictated by MSVC: +`dllexport`/`dllimport` are only honored on a _whole-class_ explicit +instantiation - not on a variable template, nor on the instantiation of a +lone static data member - and exporting a class wholesale also decorates its +member functions and nested types, which the state-holding class itself could +not tolerate. A one-member wrapper class with no functions is the one shape +that can be exported and imported on every platform. + +### Registrars are consolidated, not shared + +The export/import mechanism is deliberately limited to that single state +variable. No attempt is made to share the _registrars_ - the static objects +generated by `BOOST_OPENMETHOD`, `BOOST_OPENMETHOD_OVERRIDE` and +`BOOST_OPENMETHOD_CLASSES` - across modules. Each module keeps its own copies +of the method objects, of the registration records, and of the per-class +v-table pointers used for dispatch. + +Instead, the copies are consolidated at runtime. When a module is loaded, its +static constructors append its class and method records to the lists in the +shared registry state, each record pointing back into the module that +registered it. When `initialize` runs, records that describe the same method +or class - typically, one per module, keyed by type - are grouped together. +The dispatch tables are computed once, from the consolidated information; then +the resulting slots and strides are copied back into _every_ module's copy of +each method object, and every module's v-table pointers are updated through +the pointers captured at registration time. Each module thus dispatches +through its own statics, at full speed, but all the copies agree because they +were derived from the same computation. + +This is why method declarations need no `dllexport`/`dllimport` arguments, and +why modules do not need to agree on who "owns" a method: ownership only exists +for the registry state. + +### Platform Details + +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. The `EXPORT` and `IMPORT` +macros expand to `+__declspec(dllexport)+` and `+__declspec(dllimport)+` +decorations on an explicit instantiation of the registry state, making the +owner's copy the only one. + +On Linux and other ELF platforms, the macros can sometimes be omitted. Without +them, the registry state is an implicitly instantiated template variable with +ordinary external linkage, and the dynamic linker collapses all the modules' +copies into one - provided the symbol is visible, which is what `-rdynamic` +(`ENABLE_EXPORTS ON` in CMake) ensures for an executable. However, if the +program is built with `-fvisibility=hidden`, each module's copy is internalized +to a module-local symbol, and the modules end up with separate registries, just +like on Windows. The macros handle this case: `EXPORT` emits an explicit +instantiation with default visibility; `IMPORT` emits a plain `extern template` +declaration, which suppresses the client's own instantiation, forcing it to +reference the owner's symbol. Since the macros are harmless when they are not +needed, portable code should simply always use them. + +The platforms also differ in _when_ the imported symbol must be resolved. On +ELF, a shared library may be linked with unresolved references, which the +dynamic linker satisfies at load time; a plugin can thus import the registry +state from the executable that will load it. Mach-O (macOS) and PE (Windows) +require every reference to be satisfied at link time: a client library must +link against the owning module - hence the reverse link in the CMake setup +above. diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 7c2d3837..0570da62 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -224,11 +224,11 @@ BOOST_OPENMETHOD_CLOSE_NAMESPACE_DETAIL_UNLESS_MRDOCS //! //! `virtual_traits` must be specialized for each type that can be used as a //! virtual parameters. It enables methods to: -//! @li find the type of the object the argument refers to (e.g. `Node` from -//! `Node&`) -//! @li obtain a non-modifiable reference to that object (e.g. a `const Node&` from -//! `Node&`) -//! @li cast the argument to another type (e.g. cast a `Node&` to a `Plus&`) +//! @li find the type of the object the argument refers to (e.g. @c Node from +//! @c Node&) +//! @li obtain a non-modifiable reference to that object (e.g. a @c const +//! @c Node& from @c Node&) +//! @li cast the argument to another type (e.g. cast a @c Node& to a @c Plus&) //! //! @par Requirements //! @@ -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); } }; @@ -380,14 +380,15 @@ type_id use_class_aux< template using use_classes_tuple_type = boost::mp11::mp_apply< - std::tuple, + detail::tuple, boost::mp11::mp_transform_q< boost::mp11::mp_bind_front< detail::use_class_aux, typename detail::extract_registry::registry>, boost::mp11::mp_apply< detail::inheritance_map, - typename detail::extract_registry::others>>>; + boost::mp11::mp_unique< + typename detail::extract_registry::others>>>>; } // namespace detail @@ -655,9 +656,9 @@ inline auto final_virtual_ptr(Arg&& obj) { //! //! @par Requirements //! -//! @li @ref virtual_traits must be specialized for `Class&`. -//! @li `Class` must be a class type, possibly cv-qualified, registered in -//! `Registry`. +//! @li @ref virtual_traits must be specialized for @c Class&. +//! @li @c Class must be a class type, possibly cv-qualified, registered in +//! @c Registry. //! //! @tparam Class The class of the object, possibly cv-qualified //! @tparam Registry The registry in which `Class` is registered @@ -749,9 +750,9 @@ class virtual_ptr { //! @endcode //! //! @par Requirements - //! @li `Other` must be a polymorphic class, according to `Registry`'s - //! `rtti` policy. - //! @li `Other\*` must be constructible from `Class\*`. + //! @li @c Other must be a polymorphic class, according to the @c rtti + //! policy of @c Registry. + //! @li @c Other* must be constructible from @c Class*. //! //! @par Errors //! @@ -798,10 +799,10 @@ class virtual_ptr { //! //! @par Requirements //! - //! @li `Other` must be a polymorphic class, according to `Registry`'s - //! `rtti` policy. + //! @li @c Other must be a polymorphic class, according to the @c rtti + //! policy of @c Registry. //! - //! @li `Other\*` must be constructible from `Class\*`. + //! @li @c Other* must be constructible from @c Class*. //! //! @par Errors //! @@ -873,7 +874,7 @@ class virtual_ptr { //! @param other A virtual_ptr to a type-compatible object //! //! @par Requirements - //! @li `Other`\'s object pointer must be assignable to a `Class\*`. + //! @li @c Other's object pointer must be assignable to a @c Class*. template< class Other, typename = std::enable_if_t` pointing to the same object //! @par Requirements - //! @li `Other` must be a base or derived class of `Class`. + //! @li @c Other must be a base or derived class of @c Class. template< class Other, typename = std::enable_if_t< @@ -1255,11 +1256,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a smart pointer to a polymorphic class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `const Other&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a smart pointer to a polymorphic class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c const @c Other&. template< class Other, typename = std::enable_if_t< @@ -1302,11 +1303,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a smart pointer to a polymorphic class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a smart pointer to a polymorphic class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&. template< class Other, typename = std::enable_if_t< @@ -1352,11 +1353,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a smart pointer to a polymorphic class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a smart pointer to a polymorphic class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&&. template< class Other, typename = std::enable_if_t< @@ -1400,11 +1401,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a virtual pointer to a class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a virtual pointer to a class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&. template< class Other, typename = std::enable_if_t< @@ -1440,11 +1441,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a smart pointer to a class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a smart pointer to a class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&&. template< class Other, typename = std::enable_if_t< @@ -1504,11 +1505,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a smart pointer to a polymorphic class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `const Other&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a smart pointer to a polymorphic class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c const @c Other&. template< class Other, typename = std::enable_if_t< @@ -1544,11 +1545,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a smart pointer to a polymorphic class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a smart pointer to a polymorphic class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&&. template< class Other, typename = std::enable_if_t< @@ -1589,11 +1590,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a virtual pointer to a class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a virtual pointer to a class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&. template< class Other, typename = std::enable_if_t< @@ -1633,11 +1634,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a virtual pointer to a class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a virtual pointer to a class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&. template< class Other, typename = std::enable_if_t< @@ -1678,11 +1679,11 @@ class virtual_ptr< //! @endcode //! //! @par Requirements - //! @li `SmartPtr` and `Other` must be instantiated from the same template - - //! e.g. both `std::shared_ptr` or both `std::unique_ptr`. - //! @li `Other` must be a smart pointer to a class derived from - //! `element_type`. - //! @li `SmartPtr` must be constructible from `Other&&`. + //! @li @c SmartPtr and @c Other must be instantiated from the same template - + //! e.g. both @c std::shared_ptr or both @c std::unique_ptr. + //! @li @c Other must be a smart pointer to a class derived from + //! @c element_type. + //! @li @c SmartPtr must be constructible from @c Other&&. template< class Other, typename = std::enable_if_t< @@ -1729,7 +1730,7 @@ class virtual_ptr< //! @tparam Other The target class of the cast //! @return A `virtual_ptr` pointing to the same object //! @par Requirements - //! @li `Other` must be a base or a derived class of `Class`. + //! @li @c Other must be a base or a derived class of @c Class. template< class Other, typename = std::enable_if_t< @@ -1872,8 +1873,8 @@ struct virtual_traits, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto - cast(const virtual_ptr& ptr) -> decltype(auto) { + static auto cast(const virtual_ptr& ptr) + -> decltype(auto) { return ptr.template cast(); } @@ -1918,8 +1919,8 @@ struct virtual_traits&, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto - cast(const virtual_ptr& ptr) -> decltype(auto) { + static auto cast(const virtual_ptr& ptr) + -> decltype(auto) { return ptr.template cast< typename std::remove_reference_t::element_type>(); } @@ -2229,6 +2230,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; @@ -2243,14 +2248,14 @@ class method //! //! @li Have the same number of formal parameters as the method. //! - //! @li Each `virtual_ptr` in the method's parameter list must have a - //! corresponding `virtual_ptr` parameter in the same position in the - //! overrider's parameter list, such that `U` is the same as `T`, or has - //! `T` as an accessible unambiguous base. + //! @li Each @c virtual_ptr in the method's parameter list must have a + //! corresponding @c virtual_ptr parameter in the same position in the + //! overrider's parameter list, such that @c U is the same as @c T, or has + //! @c T as an accessible unambiguous base. //! - //! @li Each `virtual_` in the method's parameter list must have a - //! corresponding `U` parameter in the same position in the overrider's - //! parameter list, such that `U` is the same as `T`, or has `T` as an + //! @li Each @c virtual_ in the method's parameter list must have a + //! corresponding @c U parameter in the same position in the overrider's + //! parameter list, such that @c U is the same as @c T, or has @c T as an //! accessible unambiguous base. //! //! @li All other formal parameters must have the same type as the method's @@ -2263,7 +2268,11 @@ class method //! @tparam Fn One or more functions to the overrider list template class override { - std::tuple...> impl; + boost::mp11::mp_apply< + detail::tuple, + boost::mp11::mp_unique< + boost::mp11::mp_list...>>> + impl; }; private: @@ -2297,8 +2306,8 @@ class method template auto resolve_multi_first( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word; + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word; template< std::size_t VirtualArg, typename MethodArgList, typename ArgType, @@ -2323,8 +2332,9 @@ class method void resolve(); // virtual if Registry contains has_deferred_static_rtti - static BOOST_NORETURN auto fn_not_implemented( - detail::remove_virtual_... args) -> ReturnType; + static BOOST_NORETURN auto + fn_not_implemented(detail::remove_virtual_... args) + -> ReturnType; static BOOST_NORETURN auto fn_ambiguous(detail::remove_virtual_... args) -> ReturnType; @@ -2332,8 +2342,8 @@ class method auto Overrider, typename OverriderReturn, typename... OverriderParameters> struct thunk { - static auto - fn(detail::remove_virtual_... arg) -> ReturnType; + static auto fn(detail::remove_virtual_... arg) + -> ReturnType; using OverriderVirtualParameters = detail::overrider_virtual_types< DeclaredParameters, mp11::mp_list, Registry>; @@ -2363,17 +2373,17 @@ class method }; }; -template< - typename Id, typename... Parameters, typename ReturnType, class Registry> -method - method::fn; - template< typename Id, typename... Parameters, typename ReturnType, class Registry> template typename method::FunctionPointer method::next; +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +method + method::fn; + template< typename Id, typename... Parameters, typename ReturnType, class Registry> template @@ -2406,7 +2416,7 @@ method::method() { // zero-initalized static variable // coverity[uninit_use] - Registry::methods.push_back(*this); + Registry::static_::st.methods.push_back(*this); } template< @@ -2426,7 +2436,7 @@ void method::resolve_type_ids() { template< typename Id, typename... Parameters, typename ReturnType, class Registry> method::~method() { - Registry::methods.remove(*this); + Registry::static_::st.methods.remove(*this); } // ----------------------------------------------------------------------------- @@ -2441,8 +2451,9 @@ method::operator()( using namespace detail; auto pf = resolve(parameter_traits::peek(args)...); - return pf(std::forward::type>( - args)...); + return pf( + std::forward::type>( + args)...); } template< @@ -2472,8 +2483,9 @@ BOOST_FORCEINLINE template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -BOOST_FORCEINLINE auto method::vptr( - const ArgType& arg) const -> vptr_type { +BOOST_FORCEINLINE auto +method::vptr(const ArgType& arg) const + -> vptr_type { if constexpr (detail::is_virtual_ptr) { return arg.vptr(); } else { @@ -2486,8 +2498,8 @@ template< template BOOST_FORCEINLINE auto method::resolve_uni( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word { using namespace detail; using namespace policies; @@ -2506,8 +2518,8 @@ template< template BOOST_FORCEINLINE auto method::resolve_multi_first( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word { using namespace detail; using namespace boost::mp11; @@ -2564,8 +2576,8 @@ method::resolve_multi_next( template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -inline auto -method::has_next() -> bool { +inline auto method::has_next() + -> bool { if (next == fn_not_implemented) { return false; } @@ -2751,7 +2763,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 == &method::fn); return; } @@ -2763,7 +2775,7 @@ method::override_impl< #pragma GCC diagnostic pop #endif - overrider_info::method = &fn; + overrider_info::method = &method::fn; if constexpr (!Registry::has_deferred_static_rtti) { resolve_type_ids(); @@ -2778,7 +2790,7 @@ method::override_impl< this->vp_begin = vp_type_ids; this->vp_end = vp_type_ids + Arity; - fn.overriders.push_back(*this); + method::fn.overriders.push_back(*this); } template< diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 38ad1b96..dae32b2c 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -23,9 +23,9 @@ namespace boost::openmethod { //! @li @ref policies::std_rtti: Use standard RTTI. //! @li @ref policies::fast_perfect_hash: Use a fast perfect hash function to //! map type ids to indices. -//! @li @ref policies::vptr_vector: Store v-table pointers in a `std::vector`. +//! @li @ref policies::vptr_vector: Store v-table pointers in a @c std::vector. //! @li @ref policies::default_error_handler: Write short diagnostic messages. -//! @li @ref policies::stderr_output: Write messages to `stderr`. +//! @li @ref policies::stderr_output: Write messages to @c stderr. //! //! If //! {{BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS}} @@ -35,10 +35,18 @@ namespace boost::openmethod { //! inconsistent use of the macro can cause ODR violations. If defined, it must //! be in all the translation units in the program that use `default_registry`, //! including those pulled from libraries. +//! +//! For a program and its shared libraries to contribute to the same +//! `default_registry`, its state must be shared across the modules: the +//! owning module exports it by defining +//! {{BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY}} in exactly one translation +//! unit, and every client module imports it by defining +//! {{BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY}}, in both cases before +//! including this header. struct default_registry : registry< - policies::std_rtti, policies ::vptr_vector, - policies::fast_perfect_hash, policies::default_error_handler, + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::default_error_handler, policies::stderr_output #ifdef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS , @@ -47,6 +55,37 @@ struct default_registry > { }; +// Share the registry state across modules via an explicit instantiation of +// registry_state (the one-member wrapper holding the shared `st`): the owning +// module exports the instantiation definition, clients declare an imported +// instantiation ("extern template") so they reference the owner's symbol +// instead of instantiating their own copy. The state lives in +// registry_state (the registry<...> base class), not +// registry_state. +// +// On Windows the export/import decoration is dllexport/dllimport. On ELF it is +// visibility("default") on the EXPORT side (BOOST_SYMBOL_EXPORT); the IMPORT +// side is a plain `extern template` (BOOST_SYMBOL_IMPORT is empty) that +// suppresses implicit instantiation so the client references the exported +// symbol. This matters under -fvisibility=hidden: an implicitly instantiated, +// COMDAT `st` would be internalized to a per-module local symbol and could not +// be shared; the explicit instantiation definition emits a single strong, +// default-visibility global that the other modules import. Without the macros +// (the common case, off Windows) `st` keeps ordinary external linkage and is +// shared by the dynamic linker, as long as the program is not built with hidden +// visibility. +// +// An explicit instantiation definition may appear only once in the program, +// so BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY must be compiled in exactly one +// translation unit of the owning module. +#if defined(BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY) +template struct BOOST_SYMBOL_EXPORT + registry_state; +#elif defined(BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY) +extern template struct BOOST_SYMBOL_IMPORT + registry_state; +#endif + namespace detail { static odr_check default_registry_odr_check_instance; @@ -58,9 +97,27 @@ static odr_check default_registry_odr_check_instance; //! `indirect_registry` is a predefined @ref registry that uses the same //! policies as @ref default_registry, plus the @ref indirect_vptr policy. //! +//! `indirect_registry` has its own state, separate from `default_registry`'s. +//! To share it across shared libraries, use +//! {{BOOST_OPENMETHOD_EXPORT_INDIRECT_REGISTRY}} and +//! {{BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY}}. +//! //! @see indirect_vptr. struct indirect_registry : default_registry::with {}; +// indirect_registry has its own state (its policy list differs from +// default_registry's), shared across modules with its own macro pair, +// BOOST_OPENMETHOD_{EXPORT,IMPORT}_INDIRECT_REGISTRY. As with the default +// registry, the EXPORT macro must be compiled in exactly one translation unit +// of the owning module. +#if defined(BOOST_OPENMETHOD_EXPORT_INDIRECT_REGISTRY) +template struct BOOST_SYMBOL_EXPORT + registry_state; +#elif defined(BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY) +extern template struct BOOST_SYMBOL_IMPORT + registry_state; +#endif + } // namespace boost::openmethod #endif diff --git a/include/boost/openmethod/detail/ostdstream.hpp b/include/boost/openmethod/detail/ostdstream.hpp index bb9bd6cd..69b043e1 100644 --- a/include/boost/openmethod/detail/ostdstream.hpp +++ b/include/boost/openmethod/detail/ostdstream.hpp @@ -43,8 +43,6 @@ struct ostderr : ostdstream { } }; -inline ostdstream cerr; - inline auto operator<<(ostdstream& os, const char* str) -> ostdstream& { if (os.stream) { fputs(str, os.stream); diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index b9d56722..1c467946 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,64 @@ struct aggregate_reports, mp11::mp_list<>, Void> { struct type : Reports... {}; }; +// Policy initialization helpers + +#ifdef BOOST_MSVC +#if BOOST_MSVC > 1951 +#pragma message( \ + "boost/openmethod: this MSVC version is newer than 19.51, the last " \ + "one confirmed to need the has_initialize/has_finalize SFINAE " \ + "workaround below (wrong instantiation of the enclosing " \ + "boost::openmethod::initialize function template during member-call " \ + "SFINAE). Check whether this compiler still has the bug.") +#endif +// Do not probe with a call expression (the generic +// BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN form): while evaluating +// decltype(T::initialize(...)) for a T that has no such member, MSVC +// (19.44 and later, still present in 19.51) wrongly instantiates the body of +// the enclosing-namespace boost::openmethod::initialize function template +// (for its auto return type), which re-enters this probe with new types - +// unbounded recursion. 19.44 stops with C1202/C7752; 19.51 exhausts all +// memory (C1060) instead. Take the address of a specialization instead: it +// only involves T's members. +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) { + // Policies are initialized in `policy_list` order (left to right). A + // policy that depends on another policy having been initialized (e.g. + // vptr_vector reading a type_hash policy's state) must therefore appear + // *after* its dependency in the list. finalize() runs in reverse order. + (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 +152,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 +163,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; } }; @@ -153,7 +202,12 @@ struct generic_compiler { static void accumulate(const method_report& partial, report& total); struct method { - detail::method_info* info; + std::size_t index; + // All method_info instances for this logical method, one per module + // that instantiated the method's `fn`. `info` is one of these. Used to + // propagate slots_strides to every copy after the dispatch table is + // built. + std::vector infos; std::vector vp; class_* covariant_return_type = nullptr; std::vector overriders; @@ -173,8 +227,10 @@ struct generic_compiler { const method* operator[](const detail::method_info& info) const { auto iter = std::find_if( - methods.begin(), methods.end(), - [&info](const method& m) { return m.info == &info; }); + methods.begin(), methods.end(), [&info](const method& m) { + return std::find(m.infos.begin(), m.infos.end(), &info) != + m.infos.end(); + }); if (iter != methods.end()) { return &*iter; @@ -185,15 +241,83 @@ 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; + std::deque methods; std::size_t class_mark = 0; bool compilation_done = false; }; @@ -207,7 +331,7 @@ struct trace_stream { if constexpr (Compiler::has_trace) { if (on) { for (std::size_t i = 0; i < indentation_level; ++i) { - Compiler::Registry::output::os << " "; + Compiler::Registry::output::stream() << " "; } } } @@ -248,7 +372,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 +422,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& { @@ -322,7 +459,7 @@ template auto operator<<(trace_stream& tr, const T& value) -> auto& { if constexpr (Compiler::has_trace) { if (tr.on) { - Compiler::Registry::output::os << value; + Compiler::Registry::output::stream() << value; } } return tr; @@ -360,7 +497,7 @@ auto operator<<(trace_stream& tr, const boost::dynamic_bitset<>& bits) auto i = bits.size(); while (i != 0) { --i; - Compiler::Registry::output::os << bits[i]; + Compiler::Registry::output::stream() << bits[i]; } } } @@ -425,8 +562,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 +610,7 @@ template void registry::compiler::initialize() { compile(); install_global_tables(); - registry::initialized = true; + registry::static_::st.initialized = true; } #ifdef _MSC_VER @@ -542,7 +679,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 +687,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 +716,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,31 +845,58 @@ void registry::compiler::augment_methods() { using namespace policies; using namespace detail; - methods.resize(registry::methods.size()); + // Resolve deferred type ids first so consolidation keys are correct + if constexpr (has_deferred_static_rtti) { + for (auto& meth_info : registry::static_::st.methods) { + static_cast(meth_info).resolve_type_ids(); + } + } - ++tr << "Methods:\n"; - indent _(tr); + // Group method copies across modules (DLL boundaries) by type_index. + // The "local" entry is the first one we encounter — the method_info that + // lives in the module performing initialize(). It owns the compiler's + // working state for that method; the others get their slots_strides + // copied from it later. + std::map method_map; + + for (auto& info : registry::static_::st.methods) { + auto key = rtti::type_index(info.method_type_id); + method* pm; + + if (method_map.find(key) == method_map.end()) { + methods.emplace_back(); + pm = &methods.back(); + method_map[key] = pm; + } else { + pm = method_map[key]; + } - auto meth_iter = methods.begin(); + pm->infos.push_back(&info); + } - for (auto& meth_info : registry::methods) { - if constexpr (has_deferred_static_rtti) { - static_cast(meth_info).resolve_type_ids(); - } + std::size_t method_index = 0; - ++tr << type_name(meth_info.method_type_id) << " " - << range{meth_info.vp_begin, meth_info.vp_end} << "\n"; + ++tr << "Methods:\n"; + indent _(tr); + for (auto& method : methods) { + method.index = method_index++; + auto first_info = method.infos[0]; + BOOST_ASSERT(first_info); + method.vp.reserve(first_info->arity()); + method.slots.resize(first_info->arity()); + + ++tr << type_name(first_info->method_type_id) << " " + << range{first_info->vp_begin, first_info->vp_end} << "\n"; indent _(tr); - meth_iter->info = &meth_info; - meth_iter->vp.reserve(meth_info.arity()); - meth_iter->slots.resize(meth_info.arity()); - + // Build the virtual-parameter classes once, from the local + // method_info. All copies describe the same method, so any one of them + // yields the same vp list. { std::size_t param_index = 0; - for (auto ti : range{meth_info.vp_begin, meth_info.vp_end}) { + for (auto ti : range{first_info->vp_begin, first_info->vp_end}) { auto class_ = class_map[rtti::type_index(ti)]; if (!class_) { ++tr << "unknown class " << ti << "(" << type_name(ti) @@ -740,52 +911,68 @@ void registry::compiler::augment_methods() { abort(); } - meth_iter->vp.push_back(class_); + method.vp.push_back(class_); + ++param_index; } } - if (rtti::type_index(meth_info.return_type_id) != + if (rtti::type_index(first_info->return_type_id) != rtti::type_index(rtti::template static_type())) { auto covariant_return_iter = - class_map.find(rtti::type_index(meth_info.return_type_id)); + class_map.find(rtti::type_index(first_info->return_type_id)); if (covariant_return_iter != class_map.end()) { - meth_iter->covariant_return_type = - covariant_return_iter->second; + method.covariant_return_type = covariant_return_iter->second; + } + } + + // Collect overriders from every module copy of this method. + std::unordered_set seen_specs; + std::vector all_specs; + std::size_t module_index = 0; + + for (auto info : method.infos) { + indent _(tr); + ++tr << "module " << module_index++ << "\n"; + + for (auto& spec : info->overriders) { + if (seen_specs.find(&spec) == seen_specs.end()) { + all_specs.push_back(&spec); + seen_specs.insert(&spec); + } } } // initialize the function pointer in the synthetic not_implemented // overrider - const auto method_index = meth_iter - methods.begin(); - auto spec_size = meth_info.overriders.size(); - meth_iter->not_implemented.pf = meth_iter->info->not_implemented; - meth_iter->not_implemented.method_index = method_index; - meth_iter->not_implemented.spec_index = spec_size; - meth_iter->ambiguous.pf = meth_iter->info->ambiguous; - meth_iter->ambiguous.method_index = method_index; - meth_iter->ambiguous.spec_index = spec_size + 1; - - meth_iter->overriders.resize(spec_size); - auto spec_iter = meth_iter->overriders.begin(); - - for (auto& overrider_info : meth_info.overriders) { + auto spec_size = all_specs.size(); + method.not_implemented.pf = first_info->not_implemented; + method.not_implemented.method_index = method.index; + method.not_implemented.spec_index = spec_size; + method.ambiguous.pf = first_info->ambiguous; + method.ambiguous.method_index = method.index; + method.ambiguous.spec_index = spec_size + 1; + + method.overriders.resize(spec_size); + auto spec_iter = method.overriders.begin(); + + for (auto overrider_info : all_specs) { if constexpr (has_deferred_static_rtti) { - static_cast(overrider_info) + static_cast(*overrider_info) .resolve_type_ids(); } - spec_iter->method_index = method_index; - spec_iter->spec_index = spec_iter - meth_iter->overriders.begin(); + spec_iter->method_index = method.index; + spec_iter->spec_index = spec_iter - method.overriders.begin(); - ++tr << type_name(overrider_info.type) << " (" << overrider_info.pf - << ")\n"; - spec_iter->info = &overrider_info; - spec_iter->vp.reserve(meth_info.arity()); + ++tr << type_name(overrider_info->type) << " (" + << overrider_info->pf << ")\n"; + spec_iter->info = overrider_info; + spec_iter->vp.reserve(first_info->arity()); std::size_t param_index = 0; for (auto type : - range{overrider_info.vp_begin, overrider_info.vp_end}) { + range{overrider_info->vp_begin, overrider_info->vp_end}) { indent _(tr); auto class_ = class_map[rtti::type_index(type)]; @@ -806,16 +993,16 @@ void registry::compiler::augment_methods() { spec_iter->vp.push_back(class_); } - if (meth_iter->covariant_return_type) { + if (method.covariant_return_type) { auto covariant_return_iter = class_map.find( - rtti::type_index(overrider_info.return_type)); + rtti::type_index(overrider_info->return_type)); if (covariant_return_iter != class_map.end()) { spec_iter->covariant_return_type = covariant_return_iter->second; } else { missing_class error; - error.type = overrider_info.return_type; + error.type = overrider_info->return_type; if constexpr (has_error_handler) { error_handler::error(error); @@ -827,8 +1014,6 @@ void registry::compiler::augment_methods() { ++spec_iter; } - - ++meth_iter; } for (auto& method : methods) { @@ -842,8 +1027,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); @@ -915,7 +1100,7 @@ void registry::compiler::assign_tree_slots( for (const auto& mp : cls.used_by_vp) { ++tr << " in " << cls << " for " - << type_name(mp.method->info->method_type_id) << " parameter " + << type_name(mp.method->infos[0]->method_type_id) << " parameter " << mp.param << ": " << next_slot << "\n"; mp.method->slots[mp.param] = next_slot++; } @@ -943,8 +1128,8 @@ void registry::compiler::assign_lattice_slots( if (!cls.used_by_vp.empty()) { for (const auto& mp : cls.used_by_vp) { ++tr << " in " << cls << " for " - << type_name(mp.method->info->method_type_id) << " parameter " - << mp.param << "\n"; + << type_name(mp.method->infos[0]->method_type_id) + << " parameter " << mp.param << "\n"; indent _(tr); @@ -1012,8 +1197,9 @@ void registry::compiler::build_dispatch_tables() { using namespace detail; for (auto& m : methods) { + auto first_info = m.infos[0]; ++tr << "Building dispatch table for " - << type_name(m.info->method_type_id) << "\n"; + << type_name(first_info->method_type_id) << "\n"; indent _(tr); auto dims = m.arity(); @@ -1052,7 +1238,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,9 +1269,9 @@ 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.method_index = m.index; entry.vp_index = dim; entry.group_index = group_num; } @@ -1152,7 +1338,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"; } } @@ -1313,18 +1499,20 @@ void registry::compiler::write_global_data() { ++tr << "Initializing multi-method dispatch tables at " << gv_iter << "\n"; for (auto& m : methods) { - if (m.info->arity() == 1) { + auto first_info = m.infos[0]; + + if (first_info->arity() == 1) { // Uni-methods just need an index in the method table. - m.info->slots_strides_ptr[0] = m.slots[0]; + first_info->slots_strides_ptr[0] = m.slots[0]; } else { auto strides_iter = std::copy( - m.slots.begin(), m.slots.end(), m.info->slots_strides_ptr); + m.slots.begin(), m.slots.end(), first_info->slots_strides_ptr); std::copy(m.strides.begin(), m.strides.end(), strides_iter); if constexpr (has_trace) { ++tr << rflush(4, dispatch_data_size) << " " << " method #" << m.dispatch_table[0]->method_index << " " - << type_name(m.info->method_type_id) << "\n"; + << type_name(first_info->method_type_id) << "\n"; indent _(tr); for (auto& entry : m.dispatch_table) { @@ -1341,11 +1529,29 @@ void registry::compiler::write_global_data() { } } + // Propagate slots_strides values from the local method_info to all + // other module copies of the same method. Each module's `fn` has its + // own slots_strides[] array; dispatch reads it directly, so every + // copy must hold the same values. + for (auto& m : methods) { + auto count = 2 * m.infos[0]->arity() - 1; + for (auto copy : m.infos) { + if (copy == m.infos[0]) { + continue; + } + std::copy( + m.infos[0]->slots_strides_ptr, + m.infos[0]->slots_strides_ptr + count, copy->slots_strides_ptr); + } + } + ++tr << "Setting 'next' pointers\n"; for (auto& m : methods) { + auto first_info = m.infos[0]; indent _(tr); - ++tr << "method #" << " " << type_name(m.info->method_type_id) << "\n"; + ++tr << "method #" << " " << type_name(first_info->method_type_id) + << "\n"; for (auto& overrider : m.overriders) { if (overrider.next) { @@ -1367,7 +1573,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 << "-" @@ -1382,7 +1590,7 @@ void registry::compiler::write_global_data() { auto spec = method.dispatch_table[entry.group_index]; tr << "spec #" << spec->spec_index << "\n"; indent _(tr); - ++tr << type_name(method.info->method_type_id) << "\n"; + ++tr << type_name(method.infos[0]->method_type_id) << "\n"; ++tr << spec_name(method, spec); BOOST_ASSERT(gv_iter + 1 <= gv_last); *gv_iter++ = spec->pf; @@ -1390,7 +1598,7 @@ void registry::compiler::write_global_data() { tr << "vp #" << entry.vp_index << " group #" << entry.group_index << "\n"; indent _2(tr); - ++tr << type_name(method.info->method_type_id); + ++tr << type_name(method.infos[0]->method_type_id); BOOST_ASSERT(gv_iter + 1 <= gv_last); if (entry.vp_index == 0) { @@ -1407,11 +1615,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 @@ -1463,10 +1669,9 @@ void registry::compiler::select_dominant_overriders( if (candidates[i]->covariant_return_type->is_base_of( candidates[j]->covariant_return_type)) { candidates[i] = nullptr; - } else if (candidates[j] - ->covariant_return_type->is_base_of( - candidates[i] - ->covariant_return_type)) { + } else if ( + candidates[j]->covariant_return_type->is_base_of( + candidates[i]->covariant_return_type)) { candidates[j] = nullptr; } } @@ -1560,17 +1765,24 @@ void registry::compiler::print( //! The function returns an object of an unspecified type that contains a //! `report` member, itself an object of an unspecified type, thatcontains the //! following members: -//! @li `std::size_t cells`: The number of cells in all multi-method dispatch +//! @li @c std::size_t @c cells: The number of cells in all multi-method dispatch //! tables. -//! @li `std::size_t not_implemented`: The number of multi-method dispatch tables that +//! @li @c std::size_t @c not_implemented: The number of multi-method dispatch tables that //! contain at least one not implemented entry. -//! @li `std::size_t ambiguous`: The number of multi-method dispatch tables that contain at +//! @li @c std::size_t @c ambiguous: The number of multi-method dispatch tables that contain at //! least one ambiguous entry. //! //! @note //! A translation unit that calls `initialize` must include the //! `` header. //! +//! @note +//! Each policy's `initialize` is called in the order the policies appear in the +//! registry's `policy_list` (left to right). If a policy depends on another +//! policy having been initialized first, the dependency must appear *earlier* +//! in the list; ensuring this is the user's responsibility. @ref finalize +//! finalizes the policies in the reverse order. +//! //! @tparam Registry The registry to initialize. //! @tparam Options... Zero or more option types, deduced from the function //! arguments. @@ -1630,14 +1842,57 @@ inline auto initialize(Options&&... options) { namespace detail { -template +#ifdef BOOST_MSVC +#if BOOST_MSVC > 1951 +#pragma message( \ + "boost/openmethod: this MSVC version is newer than 19.51, the last " \ + "one confirmed to need the has_initialize/has_finalize SFINAE " \ + "workaround below (wrong instantiation of the enclosing " \ + "boost::openmethod::finalize function template during member-call " \ + "SFINAE). Check whether this compiler still has the bug; if not, this " \ + "workaround (and the matching one for has_initialize) can be removed.") +#endif +// Same MSVC workaround as for has_initialize above: an enclosing-namespace +// boost::openmethod::finalize function template exists, so a call-expression +// probe is unsafe. +template struct has_finalize_aux : std::false_type {}; -template +template struct has_finalize_aux< - std::void_t>()))>, - Policy, Options...> : std::true_type {}; + std::void_t...>)>, T, + Args...> : std::true_type {}; + +template +constexpr bool has_finalize = has_finalize_aux::value; +#else +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(finalize); +#endif + +// Call finalize on a single policy if it has the function. Mirror of +// initialize_policy. +template +void finalize_policy(const Options& options) { + using PolicyFn = typename Policy::template fn; + if constexpr (has_finalize) { + PolicyFn::finalize(options); + } +} + +// Policies are finalized in the reverse of `policy_list` order — the mirror of +// initialize_policies — so a policy is torn down before the ones it depends on. +template< + typename Registry, + typename Policies = mp11::mp_reverse> +struct finalize_policies; + +template +struct finalize_policies> { + template + static void fn(const Options& options) { + (finalize_policy(options), ...); + } +}; } // namespace detail @@ -1645,15 +1900,9 @@ template template 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) { - fn::finalize(options); - } - }); - - dispatch_data.clear(); - initialized = false; + detail::finalize_policies::fn(options); + static_::st.dispatch_data.clear(); + static_::st.initialized = false; } //! Release resources held by registry. @@ -1665,6 +1914,11 @@ auto registry::finalize(Options... opts) -> void { //! A translation unit that contains a call to `finalize` must include the //! `` header. //! +//! @note +//! Each policy's `finalize` is called in the reverse of the registry's +//! `policy_list` order, the mirror of @ref initialize, so a policy is finalized +//! before the policies it depends on. +//! //! @tparam Registry The registry to finalize. //! @tparam Options... Zero or more option types, deduced from the function //! arguments. diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 3a574ca9..7f031909 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -25,18 +25,18 @@ struct enable_forwarder< template struct va_args; -template -struct va_args { - using return_type = ReturnType; - using registry = Registry; -}; - template struct va_args { using return_type = ReturnType; using registry = macro_default_registry; }; +template +struct va_args { + using return_type = ReturnType; + using registry = Registry; +}; + template inline constexpr bool method_not_found = false; diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index 342fc7b5..0d6ca54c 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -11,7 +11,9 @@ #include #include -namespace boost::openmethod::policies { +namespace boost::openmethod { + +namespace policies { //! Calls a std::function with the error. //! @@ -77,6 +79,19 @@ struct default_error_handler : error_handler { //! The type of the error handler function object. using function_type = std::function; + //! The policy's state: the error handler function object. Held in + //! the registry's shared state (see @ref registry_state). + struct state { + function_type handler; + }; + + private: + static auto& st() { + return Registry::template state(); + } + + public: + //! Calls a function with the error object, wrapped in an @ref //! error_variant. //! @@ -84,6 +99,8 @@ struct default_error_handler : error_handler { //! @param error The error object. template static auto error(const Error& error) -> void { + auto handler = + st().handler ? st().handler : default_handler; handler(error_variant(error)); } @@ -96,7 +113,8 @@ 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(st().handler, std::move(new_handler)); + return prev ? prev : default_handler; } //! The default error handler function. @@ -109,22 +127,17 @@ struct default_error_handler : error_handler { if constexpr (Registry::has_output) { std::visit( [](auto&& error) { - error.template write(Registry::output::os); + error.template write( + Registry::output::stream()); }, error); - Registry::output::os << "\n"; + Registry::output::stream() << "\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..635c780b 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -10,6 +10,9 @@ #include #include +#include +#include +#include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4702) // unreachable code @@ -30,8 +33,16 @@ 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; + } +}; } // namespace detail @@ -68,25 +79,45 @@ struct fast_perfect_hash : type_hash { using errors = std::variant; + //! `state` layout when runtime checks are disabled. + struct no_checks { + detail::hash_fn fn; + }; + + //! `state` layout when runtime checks are enabled: adds the table of + //! registered type ids used to validate hashed types. + struct with_checks : no_checks { + std::vector control; + }; + //! A TypeHashFn metafunction. //! //! @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; + public: + //! The policy's state: the hash factors (@ref no_checks), plus the + //! control table if the registry has runtime checks enabled (@ref + //! with_checks). Held in the registry's shared state (see @ref + //! registry_state). + using state = std::conditional_t< + Registry::has_runtime_checks, with_checks, no_checks>; + + private: + static auto& st() { + return Registry::template state(); + } 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 + + //! 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 +131,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, st().control, 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{ + st().fn.min_value, st().fn.max_value}; } //! Hash a type id @@ -127,8 +164,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 = st().fn(type); if constexpr (Registry::has_runtime_checks) { check(index, type); @@ -144,26 +180,17 @@ 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) { + st().control.clear(); + } } + }; }; -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 +198,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::stream() << "Finding hash factor for " << N + << " types\n"; + } } std::default_random_engine rnd(13081963); @@ -184,11 +214,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) { + st().fn.shift = 8 * sizeof(type_id) - M; auto hash_size = 1 << M; - min_value = (std::numeric_limits::max)(); - max_value = (std::numeric_limits::min)(); + st().fn.min_value = (std::numeric_limits::max)(); + st().fn.max_value = (std::numeric_limits::min)(); if constexpr (InitializeContext::template has_option) { ctx.tr << " trying with M = " << M << ", " << hash_size @@ -198,21 +228,21 @@ void fast_perfect_hash::fn::initialize( std::size_t attempts = 0; buckets.resize(hash_size); - while (attempts < 100000) { + while (attempts < 100'000) { std::fill( buckets.begin(), buckets.end(), type_id(detail::uintptr_max)); ++attempts; ++total_attempts; - mult = uniform_dist(rnd) | 1; + st().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 = st().fn(type); + st().fn.min_value = (std::min)(st().fn.min_value, index); + st().fn.max_value = (std::max)(st().fn.max_value, index); if (detail::uintptr(buckets[index]) != detail::uintptr_max) { @@ -224,9 +254,9 @@ 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 " << st().fn.mult << " after " << total_attempts + << " attempts; span = [" << st().fn.min_value << ", " + << st().fn.max_value << "]\n"; } return; @@ -248,8 +278,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 < st().fn.min_value || index > st().fn.max_value || + st().control[index] != type) { if constexpr (Registry::has_error_handler) { missing_class error; @@ -263,8 +293,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..595de98a 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -9,7 +9,9 @@ #include #include -namespace boost::openmethod::policies { +namespace boost::openmethod { + +namespace policies { //! @ref Writes to the C standard error stream. //! @@ -18,14 +20,25 @@ struct stderr_output : output { //! An OutputFn metafunction. template struct fn { - //! A @ref LightweightOuputStream. - static detail::ostderr os; + public: + //! The policy's state: the output stream object. Held in the + //! registry's shared state (see @ref registry_state). + struct state { + detail::ostderr os; + }; + + [[deprecated]] inline static detail::ostderr os; + + //! Returns the stream diagnostics are written to. + //! + //! @return A reference to the policy's output stream. + static auto& stream() { + return Registry::template state().os; + } }; }; -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..1b341337 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -8,6 +8,7 @@ #include +#include #include namespace boost::openmethod { @@ -33,7 +34,21 @@ 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; + + public: + //! The policy's state: the map of v-table pointers. Held in the + //! registry's shared state (see @ref registry_state). + struct state { + //! The v-table pointers (pointers to v-table pointers if + //! `Registry` contains the @ref indirect_vptr policy), keyed by + //! type id. + typename MapFn::template fn vptrs; + }; + + private: + static auto& st() { + return Registry::template state>(); + } public: //! Stores the v-table pointers. @@ -45,7 +60,7 @@ class vptr_map : public vptr { template static void initialize(const Context& ctx, const std::tuple&) { - decltype(vptrs) new_vptrs; + decltype(st().vptrs) new_vptrs; for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); ++iter) { @@ -60,7 +75,7 @@ class vptr_map : public vptr { } } - vptrs.swap(new_vptrs); + st().vptrs.swap(new_vptrs); } //! Returns a reference to a v-table pointer for an object. @@ -80,10 +95,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 = st().vptrs.find(type); if constexpr (Registry::has_runtime_checks) { - if (iter == vptrs.end()) { + if (iter == st().vptrs.end()) { if constexpr (Registry::has_error_handler) { missing_class error; error.type = type; @@ -111,8 +126,9 @@ class vptr_map : public vptr { //! @param options A tuple of option objects. template static auto finalize(const std::tuple&) -> void { - vptrs.clear(); + st().vptrs.clear(); } + }; }; diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index 1b1a2768..1e1627ae 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -8,21 +8,12 @@ #include +#include #include #include namespace boost::openmethod { -namespace detail { - -template -inline std::vector vptr_vector_vptrs; - -template -inline std::vector vptr_vector_indirect_vptrs; - -} // namespace detail - namespace policies { //! Stores v-table pointers in a vector. @@ -52,6 +43,36 @@ struct vptr_vector : vptr { typename Registry::template policy; static constexpr auto has_type_hash = !std::is_same_v; + // vptr_vector::initialize reads the type_hash policy's state, so that + // policy must have been initialized first, i.e. must appear before + // vptr_vector in the registry's policy_list (policies are initialized + // left to right; see detail::initialize_policies). type_hash aliases + // the policy's fn, so locate the policy itself in the list. + static_assert( + !has_type_hash || + boost::mp11::mp_find< + typename Registry::policy_list, + detail::find_first_derived_of< + policies::type_hash, + typename Registry::policy_list>>::value < + boost::mp11::mp_find< + typename Registry::policy_list, vptr_vector>::value, + "the type_hash policy must appear before vptr_vector in the " + "registry's policy_list"); + + public: + //! The policy's state: the vector of v-table pointers. Held in the + //! registry's shared state (see @ref registry_state). + struct state { + //! The v-table pointers (pointers to v-table pointers if + //! `Registry` contains the @ref indirect_vptr policy), indexed by + //! (possibly hashed) type ids. + std::conditional_t< + Registry::has_indirect_vptr, std::vector, + std::vector> + vptrs; + }; + //! Stores the v-table pointers. //! //! If `Registry` contains a @ref type_hash policy, its `initialize` @@ -69,7 +90,7 @@ struct vptr_vector : vptr { (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; @@ -85,11 +106,7 @@ struct vptr_vector : vptr { ++size; } - if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs.resize(size); - } else { - detail::vptr_vector_vptrs.resize(size); - } + st().vptrs.resize(size); for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); ++iter) { @@ -104,11 +121,9 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs[index] = - &iter->vptr(); + st().vptrs[index] = &iter->vptr(); } else { - detail::vptr_vector_vptrs[index] = - iter->vptr(); + st().vptrs[index] = iter->vptr(); } } } @@ -141,14 +156,7 @@ struct vptr_vector : vptr { index = std::size_t(dynamic_type); if constexpr (Registry::has_runtime_checks) { - std::size_t max_index = 0; - - if constexpr (Registry::has_indirect_vptr) { - max_index = - detail::vptr_vector_indirect_vptrs.size(); - } else { - max_index = detail::vptr_vector_vptrs.size(); - } + std::size_t max_index = st().vptrs.size(); if (index >= max_index) { if constexpr (Registry::has_error_handler) { @@ -163,9 +171,9 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - return *detail::vptr_vector_indirect_vptrs[index]; + return *st().vptrs[index]; } else { - return detail::vptr_vector_vptrs[index]; + return st().vptrs[index]; } } @@ -176,13 +184,12 @@ struct vptr_vector : vptr { //! @param options Zero or more option objects. template static auto finalize(const std::tuple&) -> void { - using namespace policies; + st().vptrs.clear(); + } - if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs.clear(); - } else { - detail::vptr_vector_vptrs.clear(); - } + private: + static auto& st() { + return Registry::template state(); } }; }; diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 39b82c6f..2f7ab5a3 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -3,21 +3,30 @@ #include +#include #include #include +#include #include #include #include #include +#include #ifdef _MSC_VER #pragma warning(push) -#pragma warning(disable : 4702) +// 4702: unreachable code. 4251: registry_state::st (dll-exported) has type +// registry_state_type, which intentionally has no dll-interface; benign for +// a static member, which is not part of object layout. +#pragma warning(disable : 4702 4251) #endif namespace boost::openmethod { +// ----------------------------------------------------------------------------- +// word + namespace detail { union word { @@ -37,6 +46,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 +61,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 +85,7 @@ struct virtual_; template struct virtual_traits; -// ----------------------------------------------------------------------------- +// ============================================================================= // Error handling //! Base class for all OpenMethod errors. @@ -96,7 +111,7 @@ struct odr_violation : openmethod_error { namespace detail { template -struct odr_check { +struct BOOST_SYMBOL_VISIBLE odr_check { static std::size_t count; template static std::size_t inc; @@ -284,23 +299,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 +311,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 +369,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 +400,9 @@ struct LightweightOutputStream { #endif +// ----------------------------------------------------------------------------- +// n2216 + //! N2216 ambiguity resolution. //! //! If `n2216` is present in @ref initialize\'s `Options`, additional steps are @@ -412,6 +420,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 +463,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 +576,9 @@ struct RttiFn { #endif +// ----------------------------------------------------------------------------- +// rtti + //! Policy for manipulating type information. //! //! `rtti` policies are responsible for type information acquisition and dynamic @@ -570,8 +587,8 @@ struct RttiFn { //! @par Requirements //! //! Classes implementing this policy must: -//! @li derive from `rtti`. -//! @li provide a `fn` metafunction that conforms to the @ref RttiFn +//! @li derive from @c rtti. +//! @li provide a @c fn metafunction that conforms to the @ref RttiFn //! blueprint. struct rtti { // Policy category. @@ -601,6 +618,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 +630,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 @@ -631,14 +654,17 @@ struct ErrorHandlerFn { //! @par Requirements //! //! Classes implementing this policy must: -//! @li derive from `error_handler`. -//! @li provide a `fn` metafunction that conforms to the @ref +//! @li derive from @c error_handler. +//! @li provide a @c fn metafunction that conforms to the @ref //! ErrorHandlerFn blueprint. struct error_handler { // Policy category. using category = error_handler; }; +// ----------------------------------------------------------------------------- +// vptr + #ifdef __MRDOCS__ //! Blueprint for `vptr` metafunctions (exposition only). @@ -682,8 +708,8 @@ struct VptrFn { //! @par Requirements //! //! Classes implementing this policy must: -//! @li derive from `vptr`. -//! @li provide a `fn` metafunction that conforms to the @ref +//! @li derive from @c vptr. +//! @li provide a @c fn metafunction that conforms to the @ref //! VptrFn blueprint. struct vptr { // Policy category. @@ -704,6 +730,9 @@ struct indirect_vptr final { struct fn {}; }; +// ----------------------------------------------------------------------------- +// type_hash + #ifdef __MRDOCS__ //! Blueprint for @ref type_hash metafunctions (exposition only). //! @@ -716,8 +745,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`. //! @@ -742,8 +771,8 @@ struct TypeHashFn { //! @par Requirements //! //! Classes implementing this policy must: -//! @li derive from `rtti`. -//! @li provide a `fn` metafunction that conforms to the @ref +//! @li derive from @c type_hash. +//! @li provide a @c fn metafunction that conforms to the @ref //! TypeHashFn blueprint. struct type_hash { // Policy category. @@ -763,6 +792,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 @@ -772,14 +804,17 @@ struct OutputFn { //! @par Requirements //! //! Classes implementing this policy must: -//! @li derive from `output`. -//! @li provide a `fn` metafunction that conforms to the @ref +//! @li derive from @c output. +//! @li provide a @c fn metafunction that conforms to the @ref //! OutputFn blueprint. struct output { // Policy category. using category = output; }; +// ----------------------------------------------------------------------------- +// runtime_checks + //! Policy for post-initialize runtime checks. //! //! If this policy is present, performs the following checks: @@ -795,31 +830,114 @@ struct runtime_checks final { } // namespace policies +// ----------------------------------------------------------------------------- +// registry and policy helpers + namespace detail { struct registry_base {}; +// A minimal tuple. Unlike std::tuple (very expensive to instantiate with +// MSVC), it has no converting constructors, no comparisons, and no EBO +// machinery. It is used as a plain holder of heterogeneous objects +// (policy states, registrars), which are always default-constructed; `get` +// retrieves an element by type. Elements are held in base classes, so element +// types must be unique - lists that may contain duplicates are passed through +// mp_unique first. +// +// Measured (MSVC 19.51.36248, /Bt+ front-end time, synthetic +// BOOST_OPENMETHOD_CLASSES lists): ~11% less compile time at 40 classes +// (2.68s -> 2.39s, avg of 3 runs), ~7% at 100 classes (10.99s -> 10.22s). +// This is a constant-factor win that widens with list size, not a fix for +// superlinear blowups; on this library's own test suite (2-5 classes per +// list) the effect is within noise. +template +struct tuple_element { + T element; +}; + +template +struct tuple : tuple_element... {}; + +template +auto get(tuple& t) -> T& { + return static_cast&>(t).element; +} + +// Detects whether T has a nested ::state type. +template +struct has_policy_state : std::false_type {}; +template +struct has_policy_state> : std::true_type {}; + +// Extracts ::state from T. +template +using policy_state_t = typename T::state; + +// Quoted metafunction: maps a policy type P to P::template fn. +template +struct policy_fn_q { + template + using fn = typename P::template fn; +}; + +template +struct registry_state_type { + static_list classes; + static_list methods; + bool initialized; + std::vector dispatch_data; + using policies_type = mp11::mp_apply< + detail::tuple, + mp11::mp_transform< + policy_state_t, + mp11::mp_filter< + has_policy_state, + mp11::mp_transform_q< + policy_fn_q, + typename Registry::policy_list>>>>; + policies_type policies; +}; + 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 +987,67 @@ 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 + +//! The single shared instance of a registry's state. +//! +//! `registry_state` is a thin, function-free class whose only member, `st`, +//! holds all of a registry's mutable state (of type @ref +//! detail::registry_state_type). Reach it through `Registry::state()`. +//! +//! It is deliberately a *separate* one-member class, rather than +//! `registry_state_type` itself, because sharing the state across a DLL +//! boundary on Windows requires exporting and importing a *whole class*: +//! +//! @li MSVC honors @c __declspec(dllexport/dllimport) on a class explicit +//! instantiation, but NOT on a variable template (clients silently get a +//! private copy) nor on a static-data-member instantiation (error C2720). +//! So the exported symbol must be a class member reached via whole-class +//! instantiation. +//! @li dllexporting @c registry_state_type directly would also decorate its +//! member functions and, transitively, the policies' nested @c state types, +//! which MSVC rejects (error C2513). +//! +//! A one-member, function-free class is the only shape MSVC will export as a +//! whole and import via `extern template`. See default_registry.hpp for the +//! per-registry instantiations. +//! +//! For the predefined registries, the export/import pair is emitted by the +//! {{BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY}} / +//! {{BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY}} macros (and the analogous +//! `INDIRECT` pair for @ref indirect_registry). For a custom registry, write +//! the pair yourself, after the registry's definition: +//! @code +//! // in exactly one translation unit of the owning module: +//! template struct BOOST_SYMBOL_EXPORT +//! boost::openmethod::registry_state; +//! // in every client translation unit: +//! extern template struct BOOST_SYMBOL_IMPORT +//! boost::openmethod::registry_state; +//! @endcode +template +struct registry_state { + static detail::registry_state_type st; +}; + +template +detail::registry_state_type registry_state::st; + +namespace detail { + +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(id); + +} // namespace detail + //! Methods, classes and policies. //! //! Methods exist in the context of a registry. Any class used as a method or @@ -912,29 +1091,95 @@ struct initialize_aux; //! //! @par Requirements //! -//! @li `Policy` must contain a `category` alias to its root base class. The +//! @li @c Policy must contain a @c category alias to its root base class. The //! registry may contain at most one policy per category. //! -//! @li `Policy` must contain a `fn` metafunction. +//! @li @c Policy must contain a @c fn metafunction. //! //! @see @ref policies 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; + + private: template friend struct detail::use_class_aux; template friend class method; - static std::vector dispatch_data; - static bool initialized; + using static_ = registry_state; public: //! The type of this registry. + //! + //! `registry_type` is the `registry` specialization itself - for a + //! registry defined as a struct deriving from `registry` (like @ref + //! default_registry), the base class, not the struct. It is the type on + //! which the registry's state is keyed. It appears in the explicit + //! instantiation / `extern template` declaration pair that shares a + //! custom registry's state across shared libraries: + //! `registry_state` (see @ref + //! registry_state). using registry_type = registry; + //! Return the registry's mutable state. + //! + //! Everything mutable in a registry - the class and method registration + //! lists, the dispatch tables, and the state of every stateful policy - + //! is agglomerated in a single variable, + //! `registry_state::st`. `state` returns a reference to + //! it. + //! + //! Modules (executables and shared libraries) contributing to the same + //! registry must share this one variable; see @ref registry_state and + //! {{BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY}}. + static auto& state() { + return static_::st; + } + + //! Return a policy's state. + //! + //! Returns a reference to the `P::fn::state` object held + //! in the registry's state. This is how stateful policies access their + //! data members: policies do not keep their own global variables. + //! + //! @tparam P A stateful policy of this registry, i.e. one whose + //! `fn` contains a nested `state` type. + template + static auto& state() { + return detail::get::state>( + static_::st.policies); + } + + //! Return an address identifying the registry's state. + //! + //! The address is the same in all the modules of a program if, and only + //! if, they share the registry's state. Useful for diagnosing shared + //! library setups. + static const void* id() { + return static_cast(&static_::st.classes); + } + template struct compiler; @@ -959,30 +1204,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 +1262,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 +1297,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/notes.txt b/notes.txt new file mode 100644 index 00000000..5559b04f --- /dev/null +++ b/notes.txt @@ -0,0 +1,59 @@ +..\..\b2 test toolset=gcc cxxstd=17 +..\..\b2 test toolset=clang-win cxxstd=17 + + + + + + + + + + + + +● Bash(/d/boost/b2.exe test toolset=clang-win cxxstd=17 2>&1 | grep -E "error:|warning:" | grep -v "failed-as-expected" | head -20) + ⎿  D:\boost\libs\openmethod\test\dynamic_loading\main.cpp(75,28): error: no member named 'directory_iterator' in namespace 'boost::filesystem'; did you mean 'boost_swap_impl::_Vector_iterator'? + ..\..\boost/dynamic_bitset/detail/dynamic_bitset.hpp(76,31): error: shift count >= width of type [-Werror,-Wshift-count-overflow] + ⎿  (timeout 5m) + +● Bash(/d/boost/b2.exe test toolset=clang-win cxxstd=17 2>&1 | grep -A 5 "dynamic_bitset") + ⎿  In file included from ..\..\boost/dynamic_bitset.hpp:15: + In file included from ..\..\boost/dynamic_bitset/dynamic_bitset.hpp:23: + ..\..\boost/dynamic_bitset/detail/dynamic_bitset.hpp(76,31): error: shift count >= width of type [-Werror,-Wshift-count-overflow] + 76 | : ( v >>= BOOST_DYNAMIC_BITSET_WRAP_CONSTANT( amount ) ); + | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ..\..\boost/dynamic_bitset/impl/dynamic_bitset.ipp(2129,29): note: in instantiation of member function 'boost::detail::dynamic_bitset_impl::shifter::left_shift' requested here + 2129 | for ( ; value; shifter::left_shift( value ), ++it ) { + | ^ + ..\..\boost/dynamic_bitset/impl/dynamic_bitset.ipp(484,5): note: in instantiation of member function 'boost::dynamic_bitset<>::init_from_unsigned_long' requested here + 484 | init_from_unsigned_long( num_bits, value ); + | ^ + ..\..\boost/openmethod/initialize.hpp(1215,20): note: in instantiation of member function 'boost::dynamic_bitset<>::dynamic_bitset' requested here + 1215 | bitvec all(m.overriders.size()); + | ^ + ..\..\boost/openmethod/initialize.hpp(573,5): note: in instantiation of member function 'boost::openmethod::registry>::compiler<>::build_dispatch_tables' requested here + 573 | build_dispatch_tables(); + | ^ + -- + "C:\Program Files\LLVM\bin\clang-cl.exe" --target=x86_64-pc-windows-msvc "D:\boost\libs\openmethod\test\dynamic_loading\main.cpp" -c + -Fo"..\..\bin.v2\libs\openmethod\test\dynamic_loading\dynamic_loading.test\clang-win-22.1.3\debug\x86_64\cxxstd-17-iso\threading-multi\main.obj" -TP /EHs /std:c++17 /GR /Z7 /Od /Ob0 /W4 /WX /MDd -DBOOST_ALL_NO_LIB=1 -DBOOST_ATOMIC_DYN_LINK=1 -DBOOST_ATOMIC_NO_LIB=1 + -DBOOST_EXCEPTION_NO_LIB=1 -DBOOST_FILESYSTEM_DYN_LINK=1 -DBOOST_FILESYSTEM_NO_LIB=1 -DBOOST_TEST_DYN_LINK=1 -DBOOST_TEST_NO_LIB=1 "-I..\.." "-I..\..\libs\config\include" "-ID:\boost\libs\algorithm\include" "-ID:\boost\libs\align\include" "-ID:\boost\libs\array\include" + "-ID:\boost\libs\assert\include" "-ID:\boost\libs\atomic\include" "-ID:\boost\libs\bind\include" "-ID:\boost\libs\concept_check\include" "-ID:\boost\libs\container_hash\include" "-ID:\boost\libs\conversion\include" "-ID:\boost\libs\core\include" + "-ID:\boost\libs\describe\include" "-ID:\boost\libs\detail\include" "-ID:\boost\libs\dll\include" "-ID:\boost\libs\dynamic_bitset\include" "-ID:\boost\libs\exception\include" "-ID:\boost\libs\filesystem\include" "-ID:\boost\libs\function\include" + "-ID:\boost\libs\function_types\include" "-ID:\boost\libs\functional\include" "-ID:\boost\libs\fusion\include" "-ID:\boost\libs\io\include" "-ID:\boost\libs\iterator\include" "-ID:\boost\libs\mp11\include" "-ID:\boost\libs\mpl\include" + "-ID:\boost\libs\numeric\conversion\include" "-ID:\boost\libs\openmethod\include" "-ID:\boost\libs\optional\include" "-ID:\boost\libs\predef\include" "-ID:\boost\libs\preprocessor\include" "-ID:\boost\libs\range\include" "-ID:\boost\libs\regex\include" + "-ID:\boost\libs\scope\include" "-ID:\boost\libs\smart_ptr\include" "-ID:\boost\libs\static_assert\include" "-ID:\boost\libs\system\include" "-ID:\boost\libs\test\include" "-ID:\boost\libs\throw_exception\include" "-ID:\boost\libs\tuple\include" + "-ID:\boost\libs\type_index\include" "-ID:\boost\libs\type_traits\include" "-ID:\boost\libs\typeof\include" "-ID:\boost\libs\unordered\include" "-ID:\boost\libs\utility\include" "-ID:\boost\libs\variant2\include" "-ID:\boost\libs\winapi\include" + + C:\Users\jl>"C:\Program Files\LLVM\bin\clang-cl.exe" --version +clang version 22.1.3 (https://github.com/llvm/llvm-project e9846648fd6183ee6d8cbdb4502213fcf902a211) +Target: x86_64-pc-windows-msvc +Thread model: posix +InstalledDir: C:\Program Files\LLVM\bin + + + + +"clang++" -std=c++17 -fvisibility-inlines-hidden -fPIC -O0 -fno-inline -Wall -Wextra -Werror -g -fvisibility=hidden -m64 --target=arm64-apple-darwin -DBOOST_ALL_NO_LIB=1 -DBOOST_ATOMIC_DYN_LINK=1 -DBOOST_ATOMIC_NO_LIB=1 -DBOOST_FILESYSTEM_DYN_LINK=1 -DBOOST_FILESYSTEM_HAS_DIRENT_D_TYPE -DBOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW -DBOOST_FILESYSTEM_HAS_INIT_PRIORITY -DBOOST_FILESYSTEM_HAS_POSIX_AT_APIS -DBOOST_FILESYSTEM_HAS_STAT_ST_BIRTHTIMESPEC -DBOOST_FILESYSTEM_HAS_STAT_ST_MTIMESPEC -DBOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF -DBOOST_FILESYSTEM_SOURCE -I"." -I"/Users/runner/work/openmethod/boost-root/libs/align/include" -I"/Users/runner/work/openmethod/boost-root/libs/assert/include" -I"/Users/runner/work/openmethod/boost-root/libs/atomic/include" -I"/Users/runner/work/openmethod/boost-root/libs/bind/include" -I"/Users/runner/work/openmethod/boost-root/libs/compat/include" -I"/Users/runner/work/openmethod/boost-root/libs/concept_check/include" -I"/Users/runner/work/openmethod/boost-root/libs/container_hash/include" -I"/Users/runner/work/openmethod/boost-root/libs/core/include" -I"/Users/runner/work/openmethod/boost-root/libs/describe/include" -I"/Users/runner/work/openmethod/boost-root/libs/detail/include" -I"/Users/runner/work/openmethod/boost-root/libs/filesystem/include" -I"/Users/runner/work/openmethod/boost-root/libs/filesystem/src" -I"/Users/runner/work/openmethod/boost-root/libs/function/include" -I"/Users/runner/work/openmethod/boost-root/libs/function_types/include" -I"/Users/runner/work/openmethod/boost-root/libs/functional/include" -I"/Users/runner/work/openmethod/boost-root/libs/fusion/include" -I"/Users/runner/work/openmethod/boost-root/libs/io/include" -I"/Users/runner/work/openmethod/boost-root/libs/iterator/include" -I"/Users/runner/work/openmethod/boost-root/libs/mp11/include" -I"/Users/runner/work/openmethod/boost-root/libs/mpl/include" -I"/Users/runner/work/openmethod/boost-root/libs/optional/include" -I"/Users/runner/work/openmethod/boost-root/libs/predef/include" -I"/Users/runner/work/openmethod/boost-root/libs/preprocessor/include" -I"/Users/runner/work/openmethod/boost-root/libs/scope/include" -I"/Users/runner/work/openmethod/boost-root/libs/smart_ptr/include" -I"/Users/runner/work/openmethod/boost-root/libs/system/include" -I"/Users/runner/work/openmethod/boost-root/libs/throw_exception/include" -I"/Users/runner/work/openmethod/boost-root/libs/tuple/include" -I"/Users/runner/work/openmethod/boost-root/libs/type_traits/include" -I"/Users/runner/work/openmethod/boost-root/libs/typeof/include" -I"/Users/runner/work/openmethod/boost-root/libs/utility/include" -I"/Users/runner/work/openmethod/boost-root/libs/variant2/include" -I"/Users/runner/work/openmethod/boost-root/libs/winapi/include" -I"libs/config/include" -c -o "bin.v2/libs/filesystem/build/clang-darwin-20/debug/arm_64/cxxstd-17-iso/threading-multi/visibility-hidden/directory.o" "/Users/runner/work/openmethod/boost-root/libs/filesystem/src/directory.cpp" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 06c2573e..e08462c2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,7 +37,7 @@ if (BOOST_OPENMETHOD_WARNINGS_AS_ERRORS) set(BOOST_OPENMETHOD_TEST_FLAGS "-Wall -Werror") endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") - set(BOOST_OPENMETHOD_TEST_FLAGS "/W4 /WX /we4265 /wd4251") + set(BOOST_OPENMETHOD_TEST_FLAGS "/W4 /WX /we4265") endif() # Print test configuration if running in CI @@ -53,6 +53,33 @@ 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() + +# Precompiled headers: most test_*.cpp files just #include +# (and friends) before anything else, so a shared PCH avoids re-parsing that +# (large, mp11-heavy) header once per test executable. A handful of files +# instead define their own registry and #define BOOST_OPENMETHOD_DEFAULT_REGISTRY +# *before* the first inclusion of core.hpp (directly or via boost/openmethod.hpp); +# force-including a PCH that already pulled in core.hpp would defeat that +# override, so those files are detected (by scanning for the macro) and left +# without a PCH. +set(BOOST_OPENMETHOD_TEST_PCH_HEADERS + + + + +) + +set(boost_openmethod_pch_owner "") + file(GLOB test_cpp_files "test_*.cpp") foreach(test_cpp ${test_cpp_files}) @@ -60,13 +87,25 @@ 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}) + + file(READ ${test_cpp} test_cpp_contents) + string(FIND "${test_cpp_contents}" "BOOST_OPENMETHOD_DEFAULT_REGISTRY" test_cpp_overrides_registry) + + if (test_cpp_overrides_registry EQUAL -1) + if (NOT boost_openmethod_pch_owner) + target_precompile_headers(${test_target} PRIVATE ${BOOST_OPENMETHOD_TEST_PCH_HEADERS}) + set(boost_openmethod_pch_owner ${test_target}) + else() + target_precompile_headers(${test_target} REUSE_FROM ${boost_openmethod_pch_owner}) + endif() + endif() 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) @@ -77,6 +116,18 @@ function(openmethod_compile_fail_test testname fail_regex) NAME "${test_target}" COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target "${test_target}" --config $) set_property(TEST "${test_target}" PROPERTY PASS_REGULAR_EXPRESSION "${fail_regex}") + # Each of these tests runs `cmake --build` on the shared build tree. With + # the Visual Studio generator, concurrent MSBuild invocations race on shared + # files (every MSBuild invocation rewrites ZERO_CHECK.tlog/ + # ZERO_CHECK.lastbuildstate, and the losers abort with MSB3491 before + # compiling anything, so the expected diagnostic never appears in the + # output). Serialize the compile-fail tests among themselves in that case; + # they still run in parallel with the ordinary tests, which do not touch the + # build tree. Other generators (Ninja, Makefiles) tolerate concurrent + # `cmake --build` invocations, so leave those free to run in parallel. + if (CMAKE_GENERATOR MATCHES "Visual Studio") + set_property(TEST "${test_target}" PROPERTY RESOURCE_LOCK openmethod_compile_fail) + endif() endfunction() openmethod_compile_fail_test( @@ -104,3 +155,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 d47276c7..c5e12fc5 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -46,6 +46,19 @@ for local src in [ glob compile_fail_*.cpp ] compile-fail $(src) ; } +build-project dynamic_loading ; + # quick (for CI) -alias quick : test_dispatch ; +alias quick + : test_dispatch_lvalue_refs + test_dispatch_rvalue_refs + test_dispatch_pointer + test_dispatch_shared_ptr_by_value + test_dispatch_shared_ptr_by_ref + test_dispatch_unique_ptr + test_dispatch_multi + test_dispatch_next_fn + test_dispatch_across_namespaces + test_dispatch_comma_in_return_type + ; explicit quick ; diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt new file mode 100644 index 00000000..404590f3 --- /dev/null +++ b/test/dynamic_loading/CMakeLists.txt @@ -0,0 +1,114 @@ +# 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() + +# Build one set of {registry, method, overrider} shared libraries plus a test +# executable. Each variant compiles the very same sources but with two knobs: +# +# owner - which module owns and exports the single shared registry-state +# symbol: "dll" (lib_registry owns it; method/overrider/exe import it) +# or "exe" (the executable owns it; all three libraries import it). +# In the "exe" case the libraries must link *against* the executable +# (reverse linkage); ENABLE_EXPORTS makes the exe produce the import +# library / -rdynamic exports needed for that. +# ARGN - extra preprocessor definitions (e.g. the default registry) applied +# to every target so all modules agree (ODR). +# +# Targets are suffixed and outputs go to a per-variant directory so that +# main.cpp, which locates its sibling libraries by filename fragment at runtime, +# never picks up another variant's libraries. +function(boost_openmethod_add_dynamic_loading_variant suffix owner) + set(extra_defs ${ARGN}) + set(reg boost_openmethod-dl_test_registry${suffix}) + set(meth boost_openmethod-dl_test_method${suffix}) + set(ovr boost_openmethod-dl_test_overrider${suffix}) + set(exe boost_openmethod-test_dynamic_loading${suffix}) + set(outdir "${CMAKE_CURRENT_BINARY_DIR}/dynamic_loading${suffix}") + + add_library(${reg} SHARED registry.cpp) + add_library(${meth} SHARED method.cpp) + add_library(${ovr} SHARED overrider.cpp) + add_executable(${exe} main.cpp) + + # ENABLE_EXPORTS must be set before any library links against the executable + # (the exe-owned case below), as CMake validates that at the + # target_link_libraries call. The properties are (re)applied to every target + # in the foreach further down. + set_target_properties(${exe} PROPERTIES ENABLE_EXPORTS ON) + + if (owner STREQUAL "exe") + # The executable owns and exports the registry state; the libraries + # import it and therefore link against the executable. + list(APPEND extra_defs REGISTRY_IN_EXE) + target_link_libraries(${reg} PUBLIC Boost::openmethod ${exe} PRIVATE Boost::dll) + target_link_libraries(${meth} PUBLIC Boost::openmethod ${exe} PRIVATE Boost::dll) + target_link_libraries(${ovr} PRIVATE Boost::openmethod ${meth} ${exe} Boost::dll) + target_link_libraries(${exe} + PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) + else() + # lib_registry owns and exports the registry state; method/overrider/exe + # import it. lib_method links lib_registry; the exe links both and loads + # lib_overrider at runtime. + target_link_libraries(${reg} PUBLIC Boost::openmethod PRIVATE Boost::dll) + target_link_libraries(${meth} PUBLIC Boost::openmethod ${reg} PRIVATE Boost::dll) + target_link_libraries(${ovr} PRIVATE Boost::openmethod ${meth} Boost::dll) + target_link_libraries(${exe} + PUBLIC Boost::openmethod ${reg} ${meth} + PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) + # The exe links/loads the libraries, so build them first. + add_dependencies(${exe} ${reg} ${meth} ${ovr}) + endif() + # In the exe-owned case the libraries link against the exe, so the build + # order is the reverse (handled by the link dependency); we must not add + # exe -> libraries here or we'd form a cycle. Either way, building `tests` + # builds every module of the variant. + + # Put all of this variant's outputs in one 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 module are visible to + # the shared libraries loaded at runtime (e.g. the overrider can resolve + # symbols from the method/registry/exe, and an exe-owned registry state can + # be imported by the dlopen'd libraries). + # We do NOT force CXX_VISIBILITY_PRESET here: the shared registry-state + # symbol carries BOOST_SYMBOL_VISIBLE in the library (see registry_state in + # preamble.hpp), so it keeps DEFAULT ELF visibility even under a + # hidden-visibility build (as set by BoostRoot.cmake in the super-project), + # and cross-DSO state sharing works without any per-target override. + foreach(target ${reg} ${meth} ${ovr} ${exe}) + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${outdir}" + LIBRARY_OUTPUT_DIRECTORY "${outdir}" + ARCHIVE_OUTPUT_DIRECTORY "${outdir}" + ENABLE_EXPORTS ON) + if (extra_defs) + target_compile_definitions(${target} PRIVATE ${extra_defs}) + endif() + endforeach() + + if (TARGET tests) + add_dependencies(tests ${reg} ${meth} ${ovr} ${exe}) + endif() + + boost_openmethod_add_test(${exe}) +endfunction() + +# Four variants: {registry owned by dll, by exe} x {default, indirect} registry. +# indirect_registry (default_registry + indirect_vptr) is selected via +# BOOST_OPENMETHOD_DEFAULT_REGISTRY. +boost_openmethod_add_dynamic_loading_variant("_default" dll) +boost_openmethod_add_dynamic_loading_variant( + "_indirect" dll + BOOST_OPENMETHOD_DEFAULT_REGISTRY=::boost::openmethod::indirect_registry) +boost_openmethod_add_dynamic_loading_variant("_exereg_default" exe) +boost_openmethod_add_dynamic_loading_variant( + "_exereg_indirect" exe + BOOST_OPENMETHOD_DEFAULT_REGISTRY=::boost::openmethod::indirect_registry) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile new file mode 100644 index 00000000..736264ac --- /dev/null +++ b/test/dynamic_loading/Jamfile @@ -0,0 +1,137 @@ +# 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 ; + +# Build one set of {registry, method, overrider} shared libraries plus a test +# executable. Each variant compiles the very same sources but with a different +# default registry, selected by extra requirements (e.g. a ) applied to +# every target so all modules agree (ODR). Targets are suffixed and outputs go +# to a per-variant so that main.cpp, which locates its sibling +# libraries by filename fragment at runtime, never picks up another variant's +# libraries. +# +# Here lib_registry always owns and exports the single shared registry-state +# symbol; method/overrider/exe import it. The CMake build additionally has +# "exe-owned" variants (the executable owns the state and the libraries link +# against it). Those are CMake-only: b2 does not expose an executable's import +# library to dependent DLLs, so the reverse linkage they need cannot be +# expressed on Windows. See test/dynamic_loading/CMakeLists.txt. +rule make-variant ( suffix : extra-req * ) +{ + # lib_registry: owns and exports the registry's static policy state. + # registry.cpp defines EXPORT_REGISTRY (since REGISTRY_IN_EXE is unset). + lib boost_openmethod-dl_test_registry$(suffix) + : registry.cpp + : shared + $(VISIBILITY) + $(extra-req) + /boost/dll//boost_dll/off + dynamic_loading$(suffix).test + ; + + # lib_method: exports the speak() open-method; imports registry from + # lib_registry. + lib boost_openmethod-dl_test_method$(suffix) + : method.cpp + : shared + $(VISIBILITY) + $(extra-req) + /boost/dll//boost_dll/off + boost_openmethod-dl_test_registry$(suffix) + dynamic_loading$(suffix).test + ; + + # lib_overrider: adds the Dog overrider; dynamically loaded at runtime. + # It imports the shared registry state (extern template, via registry.hpp), + # so it must link lib_registry, which *defines* that symbol. lib_method only + # imports the symbol too, so linking it does not satisfy the reference. On + # ELF an undefined symbol in a shared object is resolved lazily at load time, + # but Mach-O (macOS) and PE (Windows) require it to be satisfied by a + # directly linked module at link time, so link lib_registry unconditionally. + lib boost_openmethod-dl_test_overrider$(suffix) + : overrider.cpp + : shared + $(VISIBILITY) + $(extra-req) + /boost/dll//boost_dll/off + boost_openmethod-dl_test_method$(suffix) + boost_openmethod-dl_test_registry$(suffix) + dynamic_loading$(suffix).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$(suffix).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$(suffix) + /boost/test//boost_unit_test_framework/off + /boost/dll//boost_dll/off + : + : + : shared + $(RDYNAMIC) + $(VISIBILITY) + $(extra-req) + /boost/filesystem//boost_filesystem/off + linux:"-ldl" + freebsd:"-ldl" + android:"-ldl" + windows:boost_openmethod-dl_test_registry$(suffix) + windows:boost_openmethod-dl_test_method$(suffix) + cygwin:boost_openmethod-dl_test_method$(suffix) + boost_openmethod-dl_test_method$(suffix) + boost_openmethod-dl_test_overrider$(suffix) + : dynamic_loading$(suffix) + ; +} + +# Two variants (dll-owned registry state) x {default, indirect} registry. The +# exe-owned variants are CMake-only (see the note above). +# +# The colons in the registry's qualified name are escaped (\:) because b2's +# scanner treats ':' as a special character and otherwise warns "Unescaped +# special character in argument"; the escaped form still yields the define value +# BOOST_OPENMETHOD_DEFAULT_REGISTRY=::boost::openmethod::indirect_registry. +make-variant _default : ; +make-variant _indirect + : BOOST_OPENMETHOD_DEFAULT_REGISTRY=\:\:boost\:\:openmethod\:\:indirect_registry ; 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..a3bbf89e --- /dev/null +++ b/test/dynamic_loading/main.cpp @@ -0,0 +1,137 @@ +// 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" + +// When the build asks for the executable to own the registry state +// (REGISTRY_IN_EXE), this module exports it and the shared libraries import it. +#if defined(REGISTRY_IN_EXE) +#define EXPORT_REGISTRY +#endif + +#include "registry.hpp" +#include "method.hpp" + +#include +#include +#include +#include + +#include +#include + +using namespace boost::openmethod; + +using state_id_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* id1, const void* id2) { + BOOST_TEST_MESSAGE( + std::setw(60) << "registry state" << ": " << id1 << " " << id2); + + return id1 == id2; +} + +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_state_id = method_lib.get("method_state_id"); + auto method_speak = + method_lib.get)>("method_call_speak"); + auto method_make_dog = + method_lib.get&)>("method_make_dog"); + using meet_fn = + void(greeting&, virtual_ptr, virtual_ptr); + auto method_meet = method_lib.get("method_call_meet"); + + BOOST_TEST(same_ids(registry_state_id(), method_state_id())); + + 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) == "?"); + { + // Only the Animal,Animal overrider is registered, so it has no next. + greeting r; + method_meet(r, main_dog, method_dog); + BOOST_TEST(r.first == "ignore"); + BOOST_TEST(r.second == "n/a"); + } + + dll::shared_library overrider_lib( + find_lib(search_dir, "test_overrider"), load_mode); + auto overrider_state_id = + overrider_lib.get("overrider_state_id"); + auto overrider_speak = overrider_lib.get)>( + "overrider_call_speak"); + auto overrider_make_dog = + overrider_lib.get&)>( + "overrider_make_dog"); + auto overrider_meet = overrider_lib.get("overrider_call_meet"); + + BOOST_TEST(same_ids(registry_state_id(), overrider_state_id())); + + 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"); + { + // Both args are dogs, so the dynamically-loaded Dog,Dog overrider runs; + // its next() resolves to the Animal,Animal overrider in method.cpp, + // across the DSO boundary, through the multi-dispatch table. + greeting r; + overrider_meet(r, main_dog, overrider_dog); + BOOST_TEST(r.first == "wag tails"); + BOOST_TEST(r.second == "ignore"); + } + { + greeting r; + method_meet(r, main_dog, overrider_dog); + BOOST_TEST(r.first == "wag tails"); + BOOST_TEST(r.second == "ignore"); + } +} diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp new file mode 100644 index 00000000..9e4d22fb --- /dev/null +++ b/test/dynamic_loading/method.cpp @@ -0,0 +1,63 @@ +// 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 "registry.hpp" +#include "method.hpp" + +#include + +#include + +#if defined(_WIN32) || defined(__CYGWIN__) +#include +#else +#include +#endif + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "?"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr, virtual_ptr), greeting) { + // Base overrider: no next, so the next word is always "n/a". + return {"ignore", "n/a"}; +} + +extern "C" { + +BOOST_SYMBOL_EXPORT const void* method_state_id() { + return registry_state_id(); +} + +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); +} + +// Returned through an out-parameter rather than by value: an extern "C" +// function returning a non-POD type (greeting) trips -Wreturn-type-c-linkage, +// same as method_make_dog above. +BOOST_SYMBOL_EXPORT void method_call_meet( + greeting& result, boost::openmethod::virtual_ptr a, + boost::openmethod::virtual_ptr b) { + result = meet(a, b); +} + +} diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp new file mode 100644 index 00000000..4d02c6a9 --- /dev/null +++ b/test/dynamic_loading/method.hpp @@ -0,0 +1,30 @@ +// 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 +#include + +// meet returns {own_word, next_word}: the overrider's own word plus the word +// reported by next() (or "n/a" when there is no next overrider). This exercises +// next across the DSO boundary (see overrider.cpp / main.cpp). +using greeting = std::pair; + +// Methods are consolidated across modules at initialize() time, so they need no +// DLL decoration of their own. +BOOST_OPENMETHOD( + speak, (boost::openmethod::virtual_ptr), const char*); + +BOOST_OPENMETHOD( + meet, + (boost::openmethod::virtual_ptr, + boost::openmethod::virtual_ptr), + greeting); + +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..9531c23f --- /dev/null +++ b/test/dynamic_loading/overrider.cpp @@ -0,0 +1,56 @@ +// 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_OVERRIDE( + meet, (virtual_ptr a, virtual_ptr b), greeting) { + std::string next_word = has_next() ? next(a, b).first : "n/a"; + return {"wag tails", next_word}; +} + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +extern "C" { + +BOOST_SYMBOL_EXPORT const void* overrider_state_id() { + return registry_state_id(); +} + +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); +} + +// See method_call_meet: returned via out-parameter to avoid a non-POD return +// type on an extern "C" function (-Wreturn-type-c-linkage). +BOOST_SYMBOL_EXPORT void overrider_call_meet( + greeting& result, boost::openmethod::virtual_ptr a, + boost::openmethod::virtual_ptr b) { + result = meet(a, b); +} +} diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp new file mode 100644 index 00000000..06f47825 --- /dev/null +++ b/test/dynamic_loading/registry.cpp @@ -0,0 +1,39 @@ +// 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 + +// This module owns and exports the registry state, unless the build asks for +// the executable to own it (REGISTRY_IN_EXE), in which case this becomes a +// client that imports the state. +#if !defined(REGISTRY_IN_EXE) +#define EXPORT_REGISTRY +#endif + +#include "registry.hpp" +#include "classes.hpp" + +#include + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +#if defined(_WIN32) || defined(__CYGWIN__) +#include +#else +#include +#endif + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +extern "C" { + 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..99b42491 --- /dev/null +++ b/test/dynamic_loading/registry.hpp @@ -0,0 +1,54 @@ +// 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 + +// The indirect variants select indirect_registry by defining +// BOOST_OPENMETHOD_DEFAULT_REGISTRY on the command line; the default variants +// leave it undefined. Pick the export/import macro pair matching the registry +// under test. This is done on every platform, not just Windows: the modules are +// built with hidden visibility in the super-project, where an implicitly +// instantiated registry state would not be shared across the shared objects, so +// the explicit-instantiation export/import (see default_registry.hpp) is needed +// on ELF too. +#if defined(EXPORT_REGISTRY) +# if defined(BOOST_OPENMETHOD_DEFAULT_REGISTRY) +# define BOOST_OPENMETHOD_EXPORT_INDIRECT_REGISTRY +# else +# define BOOST_OPENMETHOD_EXPORT_DEFAULT_REGISTRY +# endif +#else +# if defined(BOOST_OPENMETHOD_DEFAULT_REGISTRY) +# define BOOST_OPENMETHOD_IMPORT_INDIRECT_REGISTRY +# else +# define BOOST_OPENMETHOD_IMPORT_DEFAULT_REGISTRY +# endif +#endif + +#include + +// The registry under test. The build selects the variant by defining +// BOOST_OPENMETHOD_DEFAULT_REGISTRY on the command line (e.g. to +// ::boost::openmethod::indirect_registry); otherwise fall back to +// default_registry, matching core.hpp's own default. This must be set before +// the .cpp files include core.hpp. +#ifndef BOOST_OPENMETHOD_DEFAULT_REGISTRY +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY ::boost::openmethod::default_registry +#endif + +using test_registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY; + +// The single address that identifies the registry's shared state. It must be +// identical across all modules that share the registry (see registry::id()). +// inline (not static): registry.cpp never calls it, and an unreferenced static +// function trips MSVC's C4505 under /WX. +inline auto registry_state_id() -> const void* { + return test_registry::id(); +} + +#endif diff --git a/test/test_capture_errors.hpp b/test/test_capture_errors.hpp new file mode 100644 index 00000000..d75ec29e --- /dev/null +++ b/test/test_capture_errors.hpp @@ -0,0 +1,53 @@ +// 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_CAPTURE_ERRORS_HPP +#define BOOST_OPENMETHOD_TEST_CAPTURE_ERRORS_HPP + +// Deliberately depends only on preamble.hpp, not core.hpp: it must be +// includable before a test defines its own registry and sets +// BOOST_OPENMETHOD_DEFAULT_REGISTRY, which has to happen before the first +// inclusion of core.hpp (directly or transitively). +#include + +#include +#include + +struct capture_output : boost::openmethod::policies::output { + template + struct fn { + inline static std::ostringstream os; + static auto& stream() { + return os; + } + }; +}; + +template +struct capture_errors { + using error_handler = typename Registry::error_handler; + using output = typename Registry::output; + + capture_errors() { + prev = error_handler::set( + [this](const typename error_handler::error_variant& error) { + prev(error); + std::visit([](auto&& arg) { throw arg; }, error); + }); + } + + ~capture_errors() { + error_handler::set(prev); + output::os.clear(); + } + + auto operator()() const { + return output::os.str(); + } + + typename error_handler::function_type prev; +}; + +#endif diff --git a/test/test_class_registration.cpp b/test/test_class_registration.cpp deleted file mode 100644 index 55b39f8f..00000000 --- a/test/test_class_registration.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// 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 openmethod -#include - -#include -#include -#include - -#include "test_util.hpp" - -using namespace boost::openmethod; - -struct Animal { - virtual ~Animal() { - } -}; - -struct Dog : Animal {}; -struct Bulldog : Dog {}; - -namespace TEST_NS { - -struct registry : test_registry_<__COUNTER__>::with< - policies::runtime_checks, policies::throw_error_handler> { -}; - -BOOST_OPENMETHOD(poke, (boost::openmethod::virtual_), void, registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { -} - -BOOST_OPENMETHOD_CLASSES(Animal, registry); - -BOOST_AUTO_TEST_CASE(unknown_class_overrider) { - BOOST_CHECK_THROW(initialize(), missing_class); -} - -} // namespace TEST_NS - -namespace TEST_NS { - -struct registry : test_registry_<__COUNTER__>::with< - policies::runtime_checks, policies::throw_error_handler> { -}; - -BOOST_OPENMETHOD(poke, (boost::openmethod::virtual_), void, registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { -} - -BOOST_OPENMETHOD_CLASSES(Animal, registry); -BOOST_OPENMETHOD_CLASSES(Dog, registry); // missing base class - -BOOST_AUTO_TEST_CASE(missing_base_class) { - BOOST_CHECK_THROW(initialize(), missing_base); -} - -} // namespace TEST_NS - -namespace TEST_NS { - -struct registry : test_registry_<__COUNTER__>::with< - policies::runtime_checks, policies::throw_error_handler> { -}; - -BOOST_OPENMETHOD(poke, (boost::openmethod::virtual_), void, registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { -} - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, registry); - -BOOST_AUTO_TEST_CASE(unknown_class_call) { - initialize(); - Bulldog hector; - BOOST_CHECK_THROW(poke(hector), missing_class); -} - -} // namespace TEST_NS diff --git a/test/test_class_registration_missing_base_class.cpp b/test/test_class_registration_missing_base_class.cpp new file mode 100644 index 00000000..a1f46cfc --- /dev/null +++ b/test/test_class_registration_missing_base_class.cpp @@ -0,0 +1,41 @@ +// 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 + +struct test_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks, + boost::openmethod::policies::throw_error_handler> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#define BOOST_TEST_MODULE class_registration_missing_base_class +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { +} + +BOOST_OPENMETHOD_CLASSES(Animal); +BOOST_OPENMETHOD_CLASSES(Dog); // missing base class + +BOOST_AUTO_TEST_CASE(missing_base_class) { + BOOST_CHECK_THROW(initialize(), missing_base); +} diff --git a/test/test_class_registration_own_state.cpp b/test/test_class_registration_own_state.cpp new file mode 100644 index 00000000..edeb99d5 --- /dev/null +++ b/test/test_class_registration_own_state.cpp @@ -0,0 +1,21 @@ +// 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 "test_util.hpp" + +#define BOOST_TEST_MODULE class_registration_own_state +#include + +using namespace boost::openmethod; + +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()); +} diff --git a/test/test_class_registration_unknown_class_call.cpp b/test/test_class_registration_unknown_class_call.cpp new file mode 100644 index 00000000..1d2bcf50 --- /dev/null +++ b/test/test_class_registration_unknown_class_call.cpp @@ -0,0 +1,43 @@ +// 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 + +struct test_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks, + boost::openmethod::policies::throw_error_handler> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#define BOOST_TEST_MODULE class_registration_unknown_class_call +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +BOOST_OPENMETHOD(poke, (virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { +} + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_AUTO_TEST_CASE(unknown_class_call) { + initialize(); + Bulldog hector; + BOOST_CHECK_THROW(poke(hector), missing_class); +} diff --git a/test/test_class_registration_unknown_class_overrider.cpp b/test/test_class_registration_unknown_class_overrider.cpp new file mode 100644 index 00000000..4cde66bc --- /dev/null +++ b/test/test_class_registration_unknown_class_overrider.cpp @@ -0,0 +1,40 @@ +// 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 + +struct test_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks, + boost::openmethod::policies::throw_error_handler> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#define BOOST_TEST_MODULE class_registration_unknown_class_overrider +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { +} + +BOOST_OPENMETHOD_CLASSES(Animal); + +BOOST_AUTO_TEST_CASE(unknown_class_overrider) { + BOOST_CHECK_THROW(initialize(), missing_class); +} diff --git a/test/test_compiler.cpp b/test/test_compiler.cpp index a6e77323..36649a8b 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 = "{}"; @@ -431,3 +431,66 @@ BOOST_AUTO_TEST_CASE(test_assign_slots_a1_c1_b1) { BOOST_TEST(get_class(comp)->first_slot == 2u); BOOST_TEST(get_class(comp)->vtbl.size() == 1u); } + +// ============================================================================ +// Test finalize. + +BOOST_AUTO_TEST_CASE(test_finalize_clears_vptr_vector) { + using test_registry = test_registry_<__COUNTER__>; + + struct A { + virtual ~A() = default; + }; + struct B : A {}; + + BOOST_OPENMETHOD_REGISTER(use_classes); + (void)method)->void, test_registry>::fn; + + initialize(); + + auto& vptrs = test_registry::state().vptrs; + BOOST_TEST(!vptrs.empty()); + + // The vptr policy provides a finalize() (portable across MSVC/non-MSVC). + static_assert(detail::has_finalize< + test_registry::policy, const std::tuple<>&>); + + finalize(); + BOOST_TEST(vptrs.empty()); // finalize cleared the vector +} + +BOOST_AUTO_TEST_CASE(test_registries_do_not_share_vptr_state) { + // Two distinct registries, both using vptr_vector, must each have their own + // vptr_vector state; they do not share it. + using registry1 = test_registry_<__COUNTER__>; + using registry2 = test_registry_<__COUNTER__>; + + static_assert(!std::is_same_v); + + struct A { + virtual ~A() = default; + }; + struct B : A {}; + + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + (void)method)->void, registry1>::fn; + (void)method)->void, registry2>::fn; + + initialize(); + initialize(); + + auto& vptrs1 = registry1::state().vptrs; + auto& vptrs2 = registry2::state().vptrs; + + // Distinct state objects. + BOOST_TEST( + static_cast(&vptrs1) != static_cast(&vptrs2)); + + // Independent: clearing one registry's state leaves the other intact. + BOOST_TEST(!vptrs1.empty()); + BOOST_TEST(!vptrs2.empty()); + finalize(); + BOOST_TEST(vptrs1.empty()); + BOOST_TEST(!vptrs2.empty()); +} diff --git a/test/test_core.cpp b/test/test_core.cpp index ab5e90b1..5fd126b6 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 {}; @@ -258,7 +258,7 @@ static_assert( static_assert( std::is_same_v< detail::use_classes_tuple_type, - std::tuple< + detail::tuple< use_class_aux>, use_class_aux>, use_class_aux< diff --git a/test/test_custom_rtti.cpp b/test/test_custom_rtti.cpp deleted file mode 100644 index 034263e4..00000000 --- a/test/test_custom_rtti.cpp +++ /dev/null @@ -1,523 +0,0 @@ -// 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 openmethod -#include -#include - -#include -#include - -#include "test_util.hpp" - -using namespace boost::openmethod; - -namespace type_hash { - -struct Animal { - const char* name; - - Animal(const char* name, const char* type) : name(name), type(type) { - } - - static constexpr const char* static_type = "Animal"; - const char* type; -}; - -struct Dog : Animal { - Dog(const char* name, const char* type = static_type) : Animal(name, type) { - } - - static constexpr const char* static_type = "Dog"; -}; - -struct Cat : Animal { - Cat(const char* name, const char* type = static_type) : Animal(name, type) { - } - - static constexpr const char* static_type = "Cat"; -}; - -struct custom_rtti : policies::rtti { - template - struct fn : defaults { - template - static constexpr bool is_polymorphic = std::is_base_of_v; - - template - static auto static_type() { - if constexpr (is_polymorphic) { - return T::static_type; - } else { - return nullptr; - } - } - - template - static auto dynamic_type(const T& obj) { - if constexpr (is_polymorphic) { - return obj.type; - } else { - return nullptr; - } - } - - template - static void type_name(type_id type, Stream& stream) { - stream - << (type == nullptr ? "?" - : reinterpret_cast(type)); - } - - static auto type_index(type_id type) { - return std::string_view( - (type == nullptr ? "?" : reinterpret_cast(type))); - } - }; -}; - -struct test_registry : test_registry_<__COUNTER__>::with {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { - os << dog.name << " barks."; -} - -BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { - os << cat.name << " hisses."; -} - -BOOST_AUTO_TEST_CASE(custom_rtti_simple_projection) { - initialize(); - - Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); - - { - std::stringstream os; - poke(a, os); - BOOST_TEST(os.str() == "Snoopy barks."); - } - { - std::stringstream os; - poke(b, os); - BOOST_TEST(os.str() == "Sylvester hisses."); - } -} -} // namespace type_hash - -namespace no_projection { - -struct Animal { - const char* name; - - Animal(const char* name, std::size_t type) : name(name), type(type) { - } - - static constexpr std::size_t static_type = 0; - std::size_t type; -}; - -struct Dog : Animal { - Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { - } - - static constexpr std::size_t static_type = 1; -}; - -struct Cat : Animal { - Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { - } - - static constexpr std::size_t static_type = 2; -}; - -struct custom_rtti : policies::rtti { - template - struct fn : defaults { - template - static constexpr bool is_polymorphic = std::is_base_of_v; - - inline static auto invalid_type_id = type_id(0xFFFFFF); - - template - static auto static_type() { - if constexpr (is_polymorphic) { - return type_id(T::static_type); - } else { - return invalid_type_id; - } - } - - template - static auto dynamic_type(const T& obj) { - if constexpr (is_polymorphic) { - return type_id(obj.type); - } else { - return invalid_type_id; - } - } - - template - static void type_name(type_id type, Stream& stream) { - static const char* name[] = {"Animal", "Dog", "Cat"}; - stream << (type == invalid_type_id ? "?" : name[std::size_t(type)]); - } - - static auto type_index(type_id type) { - return type; - } - }; -}; - -struct test_registry : test_registry_<__COUNTER__>::with::without< - policies::type_hash> {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { - os << dog.name << " barks."; -} - -BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { - os << cat.name << " hisses."; -} - -void call_poke(Animal& a, std::ostream& os) { - return poke(a, os); -} -// mov rax, qword ptr [rdi + 8] -// mov rcx, qword ptr [rip + domain::context+24] -// mov rax, qword ptr [rcx + 8*rax] -// mov rcx, qword ptr [rip + method, basic_ostream >&)>::fn+80] mov -// rax, qword ptr [rax + 8*rcx] jmp rax # -// TAILCALL - -BOOST_AUTO_TEST_CASE(custom_rtti_simple) { - BOOST_TEST(Animal::static_type == 0u); - BOOST_TEST(Dog::static_type == 1u); - BOOST_TEST(Cat::static_type == 2u); - initialize(); - - Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); - - { - std::stringstream os; - poke(a, os); - BOOST_TEST(os.str() == "Snoopy barks."); - } - { - std::stringstream os; - poke(b, os); - BOOST_TEST(os.str() == "Sylvester hisses."); - } -} - -namespace using_vptr { - -template -using vptr = virtual_ptr; - -BOOST_OPENMETHOD(poke, (vptr, std::ostream&), void, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (vptr dog, std::ostream& os), void) { - os << dog->name << " barks."; -} - -BOOST_OPENMETHOD_OVERRIDE(poke, (vptr cat, std::ostream& os), void) { - os << cat->name << " hisses."; -} - -void call_poke(vptr a, std::ostream& os) { - // mov rax, qword ptr [rip + method::fn+80] - // mov rax, qword ptr [rsi + 8*rax] - // jmp rax # TAILCALL - return poke(a, os); -} - -} // namespace using_vptr -} // namespace no_projection - -namespace virtual_base { - -struct Animal { - const char* name; - - Animal(const char* name, std::size_t type) : name(name), type(type) { - } - - virtual auto cast_aux(std::size_t to_type) -> void* { - return to_type == static_type ? this : nullptr; - } - - static constexpr std::size_t static_type = 0; - std::size_t type; -}; - -template -auto custom_dynamic_cast(Base& obj) -> Derived { - using derived_type = std::remove_cv_t>; - return *reinterpret_cast( - const_cast&>(obj).cast_aux( - derived_type::static_type)); -} - -struct Dog : virtual Animal { - Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { - } - - auto cast_aux(std::size_t to_type) -> void* override { - return to_type == static_type ? this : Animal::cast_aux(to_type); - } - - static constexpr std::size_t static_type = 1; -}; - -struct Cat : virtual Animal { - Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { - } - - auto cast_aux(std::size_t to_type) -> void* override { - return to_type == static_type ? this : Animal::cast_aux(to_type); - } - - static constexpr std::size_t static_type = 2; -}; - -struct custom_rtti : policies::rtti { - template - struct fn : defaults { - template - static constexpr bool is_polymorphic = std::is_base_of_v; - - template - static auto static_type() { - if constexpr (is_polymorphic) { - return type_id(T::static_type); - } else { - return type_id(0xFFFF); - } - } - - template - static auto dynamic_type(const T& obj) { - if constexpr (is_polymorphic) { - return type_id(obj.type); - } else { - return type_id(0xFFFF); - } - } - - template - static void type_name(type_id type, Stream& stream) { - static const char* name[] = {"Animal", "Dog", "Cat"}; - stream << (type == type_id(0xFFFF) ? "?" : name[std::size_t(type)]); - } - - static auto type_index(type_id type) { - return type; - } - - template - static auto dynamic_cast_ref(Base&& obj) -> Derived { - return custom_dynamic_cast(obj); - } - }; -}; - -struct test_registry : test_registry_<__COUNTER__>::with::without< - policies::type_hash> {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { - os << dog.name << " barks."; -} - -BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { - os << cat.name << " hisses."; -} - -void call_poke(Animal& a, std::ostream& os) { - return poke(a, os); -} -// mov rax, qword ptr [rdi + 8] -// mov rcx, qword ptr [rip + domain::context+24] -// mov rax, qword ptr [rcx + 8*rax] -// mov rcx, qword ptr [rip + method, basic_ostream >&)>::fn+80] mov -// rax, qword ptr [rax + 8*rcx] jmp rax # -// TAILCALL - -BOOST_AUTO_TEST_CASE(virtual_base) { - BOOST_TEST(Animal::static_type == 0u); - BOOST_TEST(Dog::static_type == 1u); - BOOST_TEST(Cat::static_type == 2u); - initialize(); - - Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); - - { - std::stringstream os; - poke(a, os); - BOOST_TEST(os.str() == "Snoopy barks."); - } - { - std::stringstream os; - poke(b, os); - BOOST_TEST(os.str() == "Sylvester hisses."); - } -} - -namespace using_vptr { - -template -using vptr = virtual_ptr; - -BOOST_OPENMETHOD(poke, (vptr, std::ostream&), void, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (vptr dog, std::ostream& os), void) { - os << dog->name << " barks."; -} - -BOOST_OPENMETHOD_OVERRIDE(poke, (vptr cat, std::ostream& os), void) { - os << cat->name << " hisses."; -} - -void call_poke(vptr a, std::ostream& os) { - // mov rax, qword ptr [rip + method::fn+80] - // mov rax, qword ptr [rsi + 8*rax] - // jmp rax # TAILCALL - return poke(a, os); -} - -} // namespace using_vptr -} // namespace virtual_base - -namespace deferred_type_id { - -struct Animal { - const char* name; - - Animal(const char* name, std::size_t type) : name(name), type(type) { - } - - static std::size_t last_type_id; - static std::size_t static_type; - std::size_t type; -}; - -std::size_t Animal::last_type_id; -std::size_t Animal::static_type = ++Animal::last_type_id; - -struct Dog : Animal { - Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { - } - - static std::size_t static_type; -}; - -std::size_t Dog::static_type = ++Animal::last_type_id; - -struct Cat : Animal { - Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { - } - - static std::size_t static_type; -}; - -std::size_t Cat::static_type = ++Animal::last_type_id; - -struct custom_rtti : policies::deferred_static_rtti { - template - struct fn : defaults { - template - static constexpr bool is_polymorphic = std::is_base_of_v; - - template - static auto static_type() { - if constexpr (is_polymorphic) { - return type_id(T::static_type); - } else { - return type_id(0); - } - } - - template - static auto dynamic_type(const T& obj) { - if constexpr (is_polymorphic) { - return type_id(obj.type); - } else { - return type_id(0); - } - } - - template - static void type_name(type_id type, Stream& stream) { - static const char* name[] = {"?", "Animal", "Dog", "Cat"}; - stream << name[std::size_t(type)]; - } - - static auto type_index(type_id type) { - return type; - } - }; -}; - -struct test_registry : test_registry_<__COUNTER__>::with::without< - policies::type_hash> {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { - os << dog.name << " barks."; -} - -BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { - os << cat.name << " hisses."; -} - -BOOST_OPENMETHOD( - meet, (virtual_, virtual_, std::ostream&), void, - test_registry); - -BOOST_OPENMETHOD_OVERRIDE(meet, (Dog&, Dog&, std::ostream& os), void) { - os << "Both wag tails."; -} - -BOOST_AUTO_TEST_CASE(custom_rtti_deferred) { - initialize(); - - Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); - - { - std::stringstream os; - poke(a, os); - BOOST_TEST(os.str() == "Snoopy barks."); - } - - { - std::stringstream os; - poke(b, os); - BOOST_TEST(os.str() == "Sylvester hisses."); - } - - { - std::stringstream os; - meet(a, a, os); - BOOST_TEST(os.str() == "Both wag tails."); - } -} - -} // namespace deferred_type_id diff --git a/test/test_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp new file mode 100644 index 00000000..99fe4459 --- /dev/null +++ b/test/test_custom_rtti_deferred.cpp @@ -0,0 +1,152 @@ +// 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 + +namespace { +constexpr std::size_t non_polymorphic_high_bit = + std::size_t(1) << (sizeof(std::size_t) * 8 - 1); + +inline std::size_t next_non_polymorphic_id() { + static std::size_t counter = 0; + return non_polymorphic_high_bit | ++counter; +} + +template +inline std::size_t non_polymorphic_static_type() { + static std::size_t value = next_non_polymorphic_id(); + return value; +} +} // anonymous namespace + +struct Animal { + const char* name; + + Animal(const char* name, std::size_t type) : name(name), type(type) { + } + + static std::size_t last_type_id; + static std::size_t static_type; + std::size_t type; +}; + +std::size_t Animal::last_type_id; +std::size_t Animal::static_type = ++Animal::last_type_id; + +struct Dog : Animal { + Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static std::size_t static_type; +}; + +std::size_t Dog::static_type = ++Animal::last_type_id; + +struct Cat : Animal { + Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static std::size_t static_type; +}; + +std::size_t Cat::static_type = ++Animal::last_type_id; + +struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = std::is_base_of_v; + + template + static auto static_type() { + if constexpr (is_polymorphic) { + return boost::openmethod::type_id(T::static_type); + } else { + return boost::openmethod::type_id( + non_polymorphic_static_type()); + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (is_polymorphic) { + return boost::openmethod::type_id(obj.type); + } else { + return boost::openmethod::type_id( + non_polymorphic_static_type()); + } + } + + template + static void type_name( + boost::openmethod::type_id type, Stream& stream) { + static const char* name[] = {"?", "Animal", "Dog", "Cat"}; + auto idx = reinterpret_cast(type); + stream << (idx >= 1 && idx <= 3 ? name[idx] : "?"); + } + + static auto type_index(boost::openmethod::type_id type) { + return type; + } + }; +}; + +struct test_registry + : boost::openmethod::default_registry::with::without< + boost::openmethod::policies::type_hash> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#define BOOST_TEST_MODULE custom_rtti_deferred +#include +#include + +using namespace boost::openmethod; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +BOOST_OPENMETHOD( + meet, (virtual_, virtual_, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(meet, (Dog&, Dog&, std::ostream& os), void) { + os << "Both wag tails."; +} + +BOOST_AUTO_TEST_CASE(custom_rtti_deferred) { + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } + + { + std::stringstream os; + meet(a, a, os); + BOOST_TEST(os.str() == "Both wag tails."); + } +} diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp new file mode 100644 index 00000000..28a3ea11 --- /dev/null +++ b/test/test_custom_rtti_simple.cpp @@ -0,0 +1,168 @@ +// 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 + +namespace { +constexpr std::size_t non_polymorphic_high_bit = + std::size_t(1) << (sizeof(std::size_t) * 8 - 1); + +inline std::size_t next_non_polymorphic_id() { + static std::size_t counter = 0; + return non_polymorphic_high_bit | ++counter; +} + +template +inline std::size_t non_polymorphic_static_type() { + static std::size_t value = next_non_polymorphic_id(); + return value; +} +} // anonymous namespace + +struct Animal { + const char* name; + + Animal(const char* name, std::size_t type) : name(name), type(type) { + } + + static constexpr std::size_t static_type = 1; + std::size_t type; +}; + +struct Dog : Animal { + Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static constexpr std::size_t static_type = 2; +}; + +struct Cat : Animal { + Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static constexpr std::size_t static_type = 3; +}; + +struct custom_rtti : boost::openmethod::policies::rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = std::is_base_of_v; + + template + static auto static_type() { + if constexpr (is_polymorphic) { + return boost::openmethod::type_id(T::static_type); + } else { + return boost::openmethod::type_id( + non_polymorphic_static_type()); + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (is_polymorphic) { + return boost::openmethod::type_id(obj.type); + } else { + return boost::openmethod::type_id( + non_polymorphic_static_type()); + } + } + + template + static void type_name( + boost::openmethod::type_id type, Stream& stream) { + static const char* name[] = {"Animal", "Dog", "Cat"}; + auto idx = reinterpret_cast(type); + stream << (idx >= 1 && idx <= 3 ? name[idx - 1] : "?"); + } + + static auto type_index(boost::openmethod::type_id type) { + return type; + } + }; +}; + +struct test_registry + : boost::openmethod::default_registry::with::without< + boost::openmethod::policies::type_hash> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#define BOOST_TEST_MODULE custom_rtti_simple +#include +#include + +using namespace boost::openmethod; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +void call_poke(Animal& a, std::ostream& os) { + return poke(a, os); +} +// mov rax, qword ptr [rdi + 8] +// mov rcx, qword ptr [rip + domain::context+24] +// mov rax, qword ptr [rcx + 8*rax] +// mov rcx, qword ptr [rip + method, basic_ostream >&)>::fn+80] mov +// rax, qword ptr [rax + 8*rcx] jmp rax # +// TAILCALL + +BOOST_AUTO_TEST_CASE(custom_rtti_simple) { + BOOST_TEST(Animal::static_type == 1u); + BOOST_TEST(Dog::static_type == 2u); + BOOST_TEST(Cat::static_type == 3u); + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } +} + +namespace using_vptr { + +template +using vptr = virtual_ptr; + +BOOST_OPENMETHOD(poke, (vptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr dog, std::ostream& os), void) { + os << dog->name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr cat, std::ostream& os), void) { + os << cat->name << " hisses."; +} + +void call_poke(vptr a, std::ostream& os) { + // mov rax, qword ptr [rip + method::fn+80] + // mov rax, qword ptr [rsi + 8*rax] + // jmp rax # TAILCALL + return poke(a, os); +} + +} // namespace using_vptr diff --git a/test/test_custom_rtti_simple_projection.cpp b/test/test_custom_rtti_simple_projection.cpp new file mode 100644 index 00000000..4c36a58b --- /dev/null +++ b/test/test_custom_rtti_simple_projection.cpp @@ -0,0 +1,118 @@ +// 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 + +namespace { +template +inline char non_polymorphic_static_type_storage = '\0'; +} // anonymous namespace + +struct Animal { + const char* name; + + Animal(const char* name, const char* type) : name(name), type(type) { + } + + static constexpr const char* static_type = "Animal"; + const char* type; +}; + +struct Dog : Animal { + Dog(const char* name, const char* type = static_type) : Animal(name, type) { + } + + static constexpr const char* static_type = "Dog"; +}; + +struct Cat : Animal { + Cat(const char* name, const char* type = static_type) : Animal(name, type) { + } + + static constexpr const char* static_type = "Cat"; +}; + +struct custom_rtti : boost::openmethod::policies::rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = std::is_base_of_v; + + template + static auto static_type() { + if constexpr (is_polymorphic) { + return T::static_type; + } else { + return static_cast( + &non_polymorphic_static_type_storage); + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (is_polymorphic) { + return obj.type; + } else { + return static_cast( + &non_polymorphic_static_type_storage); + } + } + + template + static void type_name( + boost::openmethod::type_id type, Stream& stream) { + stream + << (type == nullptr ? "?" + : reinterpret_cast(type)); + } + + static auto type_index(boost::openmethod::type_id type) { + return type; + } + }; +}; + +struct test_registry + : boost::openmethod::default_registry::with {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#define BOOST_TEST_MODULE custom_rtti_simple_projection +#include +#include + +using namespace boost::openmethod; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +BOOST_AUTO_TEST_CASE(custom_rtti_simple_projection) { + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } +} diff --git a/test/test_custom_rtti_virtual_base.cpp b/test/test_custom_rtti_virtual_base.cpp new file mode 100644 index 00000000..b277f5f7 --- /dev/null +++ b/test/test_custom_rtti_virtual_base.cpp @@ -0,0 +1,193 @@ +// 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 + +namespace { +constexpr std::size_t non_polymorphic_high_bit = + std::size_t(1) << (sizeof(std::size_t) * 8 - 1); + +inline std::size_t next_non_polymorphic_id() { + static std::size_t counter = 0; + return non_polymorphic_high_bit | ++counter; +} + +template +inline std::size_t non_polymorphic_static_type() { + static std::size_t value = next_non_polymorphic_id(); + return value; +} +} // anonymous namespace + +struct Animal { + const char* name; + + Animal(const char* name, std::size_t type) : name(name), type(type) { + } + + virtual auto cast_aux(std::size_t to_type) -> void* { + return to_type == static_type ? this : nullptr; + } + + static constexpr std::size_t static_type = 1; + std::size_t type; +}; + +template +auto custom_dynamic_cast(Base& obj) -> Derived { + using derived_type = std::remove_cv_t>; + return *reinterpret_cast( + const_cast&>(obj).cast_aux( + derived_type::static_type)); +} + +struct Dog : virtual Animal { + Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + auto cast_aux(std::size_t to_type) -> void* override { + return to_type == static_type ? this : Animal::cast_aux(to_type); + } + + static constexpr std::size_t static_type = 2; +}; + +struct Cat : virtual Animal { + Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + auto cast_aux(std::size_t to_type) -> void* override { + return to_type == static_type ? this : Animal::cast_aux(to_type); + } + + static constexpr std::size_t static_type = 3; +}; + +struct custom_rtti : boost::openmethod::policies::rtti { + template + struct fn : defaults { + template + static constexpr bool is_polymorphic = std::is_base_of_v; + + template + static auto static_type() { + if constexpr (is_polymorphic) { + return boost::openmethod::type_id(T::static_type); + } else { + return boost::openmethod::type_id( + non_polymorphic_static_type()); + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (is_polymorphic) { + return boost::openmethod::type_id(obj.type); + } else { + return boost::openmethod::type_id( + non_polymorphic_static_type()); + } + } + + template + static void type_name( + boost::openmethod::type_id type, Stream& stream) { + static const char* name[] = {"Animal", "Dog", "Cat"}; + auto idx = reinterpret_cast(type); + stream << (idx >= 1 && idx <= 3 ? name[idx - 1] : "?"); + } + + static auto type_index(boost::openmethod::type_id type) { + return type; + } + + template + static auto dynamic_cast_ref(Base&& obj) -> Derived { + return custom_dynamic_cast(obj); + } + }; +}; + +struct test_registry + : boost::openmethod::default_registry::with::without< + boost::openmethod::policies::type_hash> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#define BOOST_TEST_MODULE custom_rtti_virtual_base +#include +#include + +using namespace boost::openmethod; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +void call_poke(Animal& a, std::ostream& os) { + return poke(a, os); +} +// mov rax, qword ptr [rdi + 8] +// mov rcx, qword ptr [rip + domain::context+24] +// mov rax, qword ptr [rcx + 8*rax] +// mov rcx, qword ptr [rip + method, basic_ostream >&)>::fn+80] mov +// rax, qword ptr [rax + 8*rcx] jmp rax # +// TAILCALL + +BOOST_AUTO_TEST_CASE(virtual_base) { + BOOST_TEST(Animal::static_type == 1u); + BOOST_TEST(Dog::static_type == 2u); + BOOST_TEST(Cat::static_type == 3u); + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } +} + +namespace using_vptr { + +template +using vptr = virtual_ptr; + +BOOST_OPENMETHOD(poke, (vptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr dog, std::ostream& os), void) { + os << dog->name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr cat, std::ostream& os), void) { + os << cat->name << " hisses."; +} + +void call_poke(vptr a, std::ostream& os) { + // mov rax, qword ptr [rip + method::fn+80] + // mov rax, qword ptr [rsi + 8*rax] + // jmp rax # TAILCALL + return poke(a, os); +} + +} // namespace using_vptr diff --git a/test/test_dispatch.cpp b/test/test_dispatch.cpp deleted file mode 100644 index 5cd36396..00000000 --- a/test/test_dispatch.cpp +++ /dev/null @@ -1,450 +0,0 @@ -// 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 -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "test_util.hpp" - -#define BOOST_TEST_MODULE openmethod -#include - -using namespace boost::openmethod; - -namespace animals { - -struct Animal { - explicit Animal(std::string str) { - name = std::move(str); - } - - Animal(const Animal&) = delete; - - Animal(Animal&&) = default; - - virtual ~Animal() { - } - - std::string name; -}; - -struct Property { - std::string owner = "Bill"; -}; - -struct Dog : Property, Animal { - using Animal::Animal; -}; - -struct Cat : Property, virtual Animal { - using Animal::Animal; -}; - -} // namespace animals - -// ----------------------------------------------------------------------------- -// pass virtual args by lvalue references - -namespace TEST_NS { - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD(name, (virtual_), std::string, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(name, (const Cat& cat), std::string) { - return cat.owner + "'s cat " + cat.name; -} - -BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { - return dog.owner + "'s dog " + dog.name; -} - -BOOST_AUTO_TEST_CASE(cast_args_lvalue_refs) { - initialize(); - - Dog spot("Spot"); - BOOST_TEST(name(spot) == "Bill's dog Spot"); - - Cat felix("Felix"); - BOOST_TEST(name(felix) == "Bill's cat Felix"); -} -} // namespace TEST_NS - -namespace TEST_NS { - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; - -} // namespace TEST_NS - -// ----------------------------------------------------------------------------- -// pass virtual args by rvalue references - -namespace TEST_NS { - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD( - teleport, (virtual_), std::unique_ptr, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(teleport, (Cat && cat), std::unique_ptr) { - return std::make_unique(std::move(cat)); -} - -BOOST_OPENMETHOD_OVERRIDE(teleport, (Dog && dog), std::unique_ptr) { - return std::make_unique(std::move(dog)); -} - -BOOST_AUTO_TEST_CASE(cast_args_rvalue_refs) { - initialize(); - - { - Dog spot("Spot"); - auto animal = teleport(std::move(spot)); - BOOST_TEST(animal->name == "Spot"); - BOOST_TEST(spot.name == ""); - } - - { - Cat felix("Felix"); - auto animal = teleport(std::move(felix)); - BOOST_TEST(animal->name == "Felix"); - BOOST_TEST(felix.name == ""); - } -} -} // namespace TEST_NS - -// ----------------------------------------------------------------------------- -// pass virtual args by pointer - -namespace TEST_NS { - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD(name, (virtual_), std::string, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(name, (const Cat* cat), std::string) { - return cat->owner + "'s cat " + cat->name; -} - -BOOST_OPENMETHOD_OVERRIDE(name, (const Dog* dog), std::string) { - return dog->owner + "'s dog " + dog->name; -} - -BOOST_AUTO_TEST_CASE(cast_args_pointer) { - initialize(); - - Dog spot("Spot"); - BOOST_TEST(name(&spot) == "Bill's dog Spot"); - - Cat felix("Felix"); - BOOST_TEST(name(&felix) == "Bill's cat Felix"); -} -} // namespace TEST_NS - -namespace TEST_NS { - -// ----------------------------------------------------------------------------- -// pass virtual args by shared_ptr by value - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD( - name, (virtual_>), std::string, - test_registry); - -BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr cat), std::string) { - return cat->owner + "'s cat " + cat->name; -} - -BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr dog), std::string) { - return dog->owner + "'s dog " + dog->name; -} - -BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_value) { - initialize(); - - auto spot = std::make_shared("Spot"); - BOOST_TEST(name(spot) == "Bill's dog Spot"); - - auto felix = std::make_shared("Felix"); - BOOST_TEST(name(felix) == "Bill's cat Felix"); -} -} // namespace TEST_NS - -namespace TEST_NS { - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; -} // namespace TEST_NS - -namespace TEST_NS { - -// ----------------------------------------------------------------------------- -// pass virtual args by shared_ptr by ref - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD( - name, (virtual_&>), std::string, - test_registry); - -BOOST_OPENMETHOD_OVERRIDE( - name, (const std::shared_ptr& cat), std::string) { - return cat->owner + "'s cat " + cat->name; -} - -BOOST_OPENMETHOD_OVERRIDE( - name, (const std::shared_ptr& dog), std::string) { - return dog->owner + "'s dog " + dog->name; -} - -BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_ref) { - initialize(); - - auto spot = std::make_shared("Spot"); - BOOST_TEST(name(spot) == "Bill's dog Spot"); - - auto felix = std::make_shared("Felix"); - BOOST_TEST(name(felix) == "Bill's cat Felix"); -} - -} // namespace TEST_NS - -namespace TEST_NS { - -// ----------------------------------------------------------------------------- -// pass virtual args by unique_ptr - -using test_registry = test_registry_<__COUNTER__>; -using namespace animals; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_registry); - -BOOST_OPENMETHOD( - name, (virtual_>), std::string, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr cat), std::string) { - return cat->owner + "'s cat " + cat->name; -} - -BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr dog), std::string) { - return dog->owner + "'s dog " + dog->name; -} - -BOOST_AUTO_TEST_CASE(cast_args_unique_ptr) { - initialize(); - - auto spot = std::make_unique("Spot"); - BOOST_TEST(name(std::move(spot)) == "Bill's dog Spot"); - BOOST_TEST(spot.get() == nullptr); - - auto felix = std::make_unique("Felix"); - BOOST_TEST(name(std::move(felix)) == "Bill's cat Felix"); - BOOST_TEST(felix.get() == nullptr); -} -} // namespace TEST_NS - -namespace TEST_NS { - -using namespace test_matrices; - -using test_registry = test_registry_<__COUNTER__>; - -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, test_registry); - -BOOST_OPENMETHOD( - times, (virtual_, virtual_), string_pair, - test_registry); - -BOOST_OPENMETHOD( - times, (double, virtual_), string_pair, test_registry); - -BOOST_OPENMETHOD( - times, (virtual_, double), string_pair, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), string_pair) { - return string_pair(MATRIX_MATRIX, NONE); -} - -BOOST_OPENMETHOD_OVERRIDE( - times, (const diagonal_matrix&, const diagonal_matrix&), string_pair) { - return string_pair(DIAGONAL_DIAGONAL, MATRIX_MATRIX); -} - -BOOST_OPENMETHOD_OVERRIDE(times, (double, const matrix&), string_pair) { - return string_pair(SCALAR_MATRIX, NONE); -} - -BOOST_OPENMETHOD_OVERRIDE( - times, (double, const diagonal_matrix&), string_pair) { - return string_pair(SCALAR_DIAGONAL, SCALAR_MATRIX); -} - -BOOST_OPENMETHOD_OVERRIDE( - times, (const diagonal_matrix&, double), string_pair) { - return string_pair(DIAGONAL_SCALAR, MATRIX_SCALAR); -} - -BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, double), string_pair) { - return string_pair(MATRIX_SCALAR, NONE); -} - -BOOST_AUTO_TEST_CASE(simple) { - auto report = initialize().report; - BOOST_TEST(report.not_implemented == 0u); - BOOST_TEST(report.ambiguous == 0u); - - { - // pass by const ref - const matrix& dense = dense_matrix(); - const matrix& diag = diagonal_matrix(); - BOOST_TEST(times(dense, dense) == string_pair(MATRIX_MATRIX, NONE)); - BOOST_TEST( - times(diag, diag) == string_pair(DIAGONAL_DIAGONAL, MATRIX_MATRIX)); - BOOST_TEST(times(diag, dense) == string_pair(MATRIX_MATRIX, NONE)); - BOOST_TEST(times(2, dense) == string_pair(SCALAR_MATRIX, NONE)); - BOOST_TEST(times(dense, 2) == string_pair(MATRIX_SCALAR, NONE)); - 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 - -namespace test_next_fn { - -struct Animal { - virtual ~Animal() { - } -}; - -struct Dog : Animal {}; -struct Bulldog : Dog {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Bulldog); - -struct BOOST_OPENMETHOD_ID(poke); -using poke = - method)->std::string>; - -auto poke_dog(Dog&) -> std::string { - return "bark"; -} - -BOOST_OPENMETHOD_REGISTER(poke::override); - -auto poke_bulldog(Bulldog& dog) -> std::string { - return poke::next(dog) + " and bite back"; -} - -BOOST_OPENMETHOD_REGISTER(poke::override); - -BOOST_AUTO_TEST_CASE(test_next_fn) { - initialize(); - - std::unique_ptr snoopy = std::make_unique(); - BOOST_TEST(poke::fn(*snoopy) == "bark"); - - std::unique_ptr hector = std::make_unique(); - BOOST_TEST(poke::fn(*hector) == "bark and bite back"); -} - -} // namespace test_next_fn - -namespace across_namespaces { - -namespace animals { - -class Animal { - public: - virtual ~Animal() { - } -}; - -BOOST_OPENMETHOD(poke, (virtual_), std::string); - -} // namespace animals - -namespace more_animals { - -class Dog : public animals::Animal {}; - -BOOST_OPENMETHOD_CLASSES(Dog, animals::Animal); - -BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog&), std::string) { - return "bark"; -} - -} // namespace more_animals - -BOOST_AUTO_TEST_CASE(across_namespaces) { - const animals::Animal& animal = more_animals::Dog(); - BOOST_TEST("bark" == poke(animal)); -} - -} // namespace across_namespaces - -namespace test_comma_in_return_type { - -using test_registry = test_registry_<__COUNTER__>; - -struct Test { - virtual ~Test(){}; -}; - -BOOST_OPENMETHOD_CLASSES(Test, test_registry); - -BOOST_OPENMETHOD(foo, (virtual_), std::pair, test_registry); - -BOOST_OPENMETHOD_OVERRIDE(foo, (Test&), std::pair) { - return {1, 2}; -} - -BOOST_AUTO_TEST_CASE(comma_in_return_type) { - initialize(); - - Test test; - - BOOST_CHECK(foo(test) == std::pair(1, 2)); -} - -} // namespace test_comma_in_return_type diff --git a/test/test_dispatch_across_namespaces.cpp b/test/test_dispatch_across_namespaces.cpp new file mode 100644 index 00000000..c3505c5d --- /dev/null +++ b/test/test_dispatch_across_namespaces.cpp @@ -0,0 +1,45 @@ +// 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 + +#define BOOST_TEST_MODULE dispatch_across_namespaces +#include + +using namespace boost::openmethod; + +namespace animals { + +class Animal { + public: + virtual ~Animal() { + } +}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string); + +} // namespace animals + +namespace more_animals { + +class Dog : public animals::Animal {}; + +BOOST_OPENMETHOD_CLASSES(Dog, animals::Animal); + +BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog&), std::string) { + return "bark"; +} + +} // namespace more_animals + +BOOST_AUTO_TEST_CASE(across_namespaces) { + initialize(); + + const animals::Animal& animal = more_animals::Dog(); + BOOST_TEST("bark" == poke(animal)); +} diff --git a/test/test_dispatch_comma_in_return_type.cpp b/test/test_dispatch_comma_in_return_type.cpp new file mode 100644 index 00000000..75d1b5dd --- /dev/null +++ b/test/test_dispatch_comma_in_return_type.cpp @@ -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 + +#include +#include + +#define BOOST_TEST_MODULE dispatch_comma_in_return_type +#include + +using namespace boost::openmethod; + +struct Test { + virtual ~Test(){}; +}; + +BOOST_OPENMETHOD_CLASSES(Test); + +BOOST_OPENMETHOD(foo, (virtual_), std::pair); + +BOOST_OPENMETHOD_OVERRIDE(foo, (Test&), std::pair) { + return {1, 2}; +} + +BOOST_AUTO_TEST_CASE(comma_in_return_type) { + initialize(); + + Test test; + + BOOST_CHECK(foo(test) == std::pair(1, 2)); +} diff --git a/test/test_dispatch_lvalue_refs.cpp b/test/test_dispatch_lvalue_refs.cpp new file mode 100644 index 00000000..6822b2b7 --- /dev/null +++ b/test/test_dispatch_lvalue_refs.cpp @@ -0,0 +1,39 @@ +// 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 + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE dispatch_lvalue_refs +#include + +using namespace boost::openmethod; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Cat& cat), std::string) { + return cat.owner + "'s cat " + cat.name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.owner + "'s dog " + dog.name; +} + +BOOST_AUTO_TEST_CASE(cast_args_lvalue_refs) { + initialize(); + + Dog spot("Spot"); + BOOST_TEST(name(spot) == "Bill's dog Spot"); + + Cat felix("Felix"); + BOOST_TEST(name(felix) == "Bill's cat Felix"); +} diff --git a/test/test_dispatch_multi.cpp b/test/test_dispatch_multi.cpp new file mode 100644 index 00000000..380f6cd0 --- /dev/null +++ b/test/test_dispatch_multi.cpp @@ -0,0 +1,71 @@ +// 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 "test_util.hpp" + +#define BOOST_TEST_MODULE dispatch_multi +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), string_pair); + +BOOST_OPENMETHOD(times, (double, virtual_), string_pair); + +BOOST_OPENMETHOD(times, (virtual_, double), string_pair); + +BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), string_pair) { + return string_pair(MATRIX_MATRIX, NONE); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const diagonal_matrix&, const diagonal_matrix&), string_pair) { + return string_pair(DIAGONAL_DIAGONAL, MATRIX_MATRIX); +} + +BOOST_OPENMETHOD_OVERRIDE(times, (double, const matrix&), string_pair) { + return string_pair(SCALAR_MATRIX, NONE); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (double, const diagonal_matrix&), string_pair) { + return string_pair(SCALAR_DIAGONAL, SCALAR_MATRIX); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const diagonal_matrix&, double), string_pair) { + return string_pair(DIAGONAL_SCALAR, MATRIX_SCALAR); +} + +BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, double), string_pair) { + return string_pair(MATRIX_SCALAR, NONE); +} + +BOOST_AUTO_TEST_CASE(simple) { + auto report = initialize().report; + BOOST_TEST(report.not_implemented == 0u); + BOOST_TEST(report.ambiguous == 0u); + + { + // pass by const ref + const matrix& dense = dense_matrix(); + const matrix& diag = diagonal_matrix(); + BOOST_TEST(times(dense, dense) == string_pair(MATRIX_MATRIX, NONE)); + BOOST_TEST( + times(diag, diag) == string_pair(DIAGONAL_DIAGONAL, MATRIX_MATRIX)); + BOOST_TEST(times(diag, dense) == string_pair(MATRIX_MATRIX, NONE)); + BOOST_TEST(times(2, dense) == string_pair(SCALAR_MATRIX, NONE)); + BOOST_TEST(times(dense, 2) == string_pair(MATRIX_SCALAR, NONE)); + BOOST_TEST( + times(diag, 2) == string_pair(DIAGONAL_SCALAR, MATRIX_SCALAR)); + } +} diff --git a/test/test_dispatch_next_fn.cpp b/test/test_dispatch_next_fn.cpp new file mode 100644 index 00000000..560202c4 --- /dev/null +++ b/test/test_dispatch_next_fn.cpp @@ -0,0 +1,51 @@ +// 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 +#include + +#define BOOST_TEST_MODULE dispatch_next_fn +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Bulldog); + +struct BOOST_OPENMETHOD_ID(poke); +using poke = + method)->std::string>; + +auto poke_dog(Dog&) -> std::string { + return "bark"; +} + +BOOST_OPENMETHOD_REGISTER(poke::override); + +auto poke_bulldog(Bulldog& dog) -> std::string { + return poke::next(dog) + " and bite back"; +} + +BOOST_OPENMETHOD_REGISTER(poke::override); + +BOOST_AUTO_TEST_CASE(test_next_fn) { + initialize(); + + std::unique_ptr snoopy = std::make_unique(); + BOOST_TEST(poke::fn(*snoopy) == "bark"); + + std::unique_ptr hector = std::make_unique(); + BOOST_TEST(poke::fn(*hector) == "bark and bite back"); +} diff --git a/test/test_dispatch_pointer.cpp b/test/test_dispatch_pointer.cpp new file mode 100644 index 00000000..f8c8915c --- /dev/null +++ b/test/test_dispatch_pointer.cpp @@ -0,0 +1,39 @@ +// 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 + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE dispatch_pointer +#include + +using namespace boost::openmethod; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Cat* cat), std::string) { + return cat->owner + "'s cat " + cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog* dog), std::string) { + return dog->owner + "'s dog " + dog->name; +} + +BOOST_AUTO_TEST_CASE(cast_args_pointer) { + initialize(); + + Dog spot("Spot"); + BOOST_TEST(name(&spot) == "Bill's dog Spot"); + + Cat felix("Felix"); + BOOST_TEST(name(&felix) == "Bill's cat Felix"); +} diff --git a/test/test_dispatch_rvalue_refs.cpp b/test/test_dispatch_rvalue_refs.cpp new file mode 100644 index 00000000..6178ef10 --- /dev/null +++ b/test/test_dispatch_rvalue_refs.cpp @@ -0,0 +1,47 @@ +// 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 + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE dispatch_rvalue_refs +#include + +using namespace boost::openmethod; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(teleport, (virtual_), std::unique_ptr); + +BOOST_OPENMETHOD_OVERRIDE(teleport, (Cat && cat), std::unique_ptr) { + return std::make_unique(std::move(cat)); +} + +BOOST_OPENMETHOD_OVERRIDE(teleport, (Dog && dog), std::unique_ptr) { + return std::make_unique(std::move(dog)); +} + +BOOST_AUTO_TEST_CASE(cast_args_rvalue_refs) { + initialize(); + + { + Dog spot("Spot"); + auto animal = teleport(std::move(spot)); + BOOST_TEST(animal->name == "Spot"); + BOOST_TEST(spot.name == ""); + } + + { + Cat felix("Felix"); + auto animal = teleport(std::move(felix)); + BOOST_TEST(animal->name == "Felix"); + BOOST_TEST(felix.name == ""); + } +} diff --git a/test/test_dispatch_shared_ptr_by_ref.cpp b/test/test_dispatch_shared_ptr_by_ref.cpp new file mode 100644 index 00000000..fee66033 --- /dev/null +++ b/test/test_dispatch_shared_ptr_by_ref.cpp @@ -0,0 +1,44 @@ +// 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 +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE dispatch_shared_ptr_by_ref +#include + +using namespace boost::openmethod; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD( + name, (virtual_&>), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + name, (const std::shared_ptr& cat), std::string) { + return cat->owner + "'s cat " + cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE( + name, (const std::shared_ptr& dog), std::string) { + return dog->owner + "'s dog " + dog->name; +} + +BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_ref) { + initialize(); + + auto spot = std::make_shared("Spot"); + BOOST_TEST(name(spot) == "Bill's dog Spot"); + + auto felix = std::make_shared("Felix"); + BOOST_TEST(name(felix) == "Bill's cat Felix"); +} diff --git a/test/test_dispatch_shared_ptr_by_value.cpp b/test/test_dispatch_shared_ptr_by_value.cpp new file mode 100644 index 00000000..6c408027 --- /dev/null +++ b/test/test_dispatch_shared_ptr_by_value.cpp @@ -0,0 +1,41 @@ +// 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 +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE dispatch_shared_ptr_by_value +#include + +using namespace boost::openmethod; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(name, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr cat), std::string) { + return cat->owner + "'s cat " + cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr dog), std::string) { + return dog->owner + "'s dog " + dog->name; +} + +BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_value) { + initialize(); + + auto spot = std::make_shared("Spot"); + BOOST_TEST(name(spot) == "Bill's dog Spot"); + + auto felix = std::make_shared("Felix"); + BOOST_TEST(name(felix) == "Bill's cat Felix"); +} diff --git a/test/test_dispatch_unique_ptr.cpp b/test/test_dispatch_unique_ptr.cpp new file mode 100644 index 00000000..be6cd88b --- /dev/null +++ b/test/test_dispatch_unique_ptr.cpp @@ -0,0 +1,43 @@ +// 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 +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE dispatch_unique_ptr +#include + +using namespace boost::openmethod; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(name, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr cat), std::string) { + return cat->owner + "'s cat " + cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr dog), std::string) { + return dog->owner + "'s dog " + dog->name; +} + +BOOST_AUTO_TEST_CASE(cast_args_unique_ptr) { + initialize(); + + auto spot = std::make_unique("Spot"); + BOOST_TEST(name(std::move(spot)) == "Bill's dog Spot"); + BOOST_TEST(spot.get() == nullptr); + + auto felix = std::make_unique("Felix"); + BOOST_TEST(name(std::move(felix)) == "Bill's cat Felix"); + BOOST_TEST(felix.get() == nullptr); +} diff --git a/test/test_n2216.cpp b/test/test_n2216.cpp deleted file mode 100644 index 6b27fbc6..00000000 --- a/test/test_n2216.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// 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 -#include -#include - -#include -#include -#include -#include -#include - -#include "test_util.hpp" - -#define BOOST_TEST_MODULE openmethod -#include - -using namespace boost::openmethod; - -namespace TEST_NS { - -using namespace test_matrices; - -struct test_registry : test_registry_<__COUNTER__> {}; - -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, test_registry); - -BOOST_OPENMETHOD( - times, (virtual_, virtual_), - std::unique_ptr, test_registry); - -BOOST_OPENMETHOD_OVERRIDE( - times, (const dense_matrix&, const matrix&), - std::unique_ptr) { - return std::make_unique(); -} - -BOOST_OPENMETHOD_OVERRIDE( - times, (const matrix&, const dense_matrix&), std::unique_ptr) { - return std::make_unique(); -} - -static_assert( - std::is_same_v< - detail::virtual_type, test_registry>, matrix>); - -BOOST_AUTO_TEST_CASE(covariant_return_type) { - auto compiler = boost::openmethod::initialize(n2216()); - BOOST_TEST(compiler.report.ambiguous == 0u); - - // test_registry: use covariant return types to resolve ambiguity. - dense_matrix left, right; - auto result = times(left, right); - BOOST_TEST(result->type == DENSE_MATRIX); -} - -} // namespace TEST_NS - -namespace TEST_NS { - -using namespace test_matrices; - -struct test_registry : test_registry_<__COUNTER__> {}; - -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, test_registry); - -BOOST_OPENMETHOD( - times, (virtual_, virtual_), string_pair, - test_registry); - -BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), string_pair) { - return string_pair(MATRIX_MATRIX, NONE); -} - -BOOST_OPENMETHOD_OVERRIDE( - times, (const dense_matrix&, const matrix&), string_pair) { - return string_pair(DENSE_MATRIX, MATRIX_MATRIX); -} - -BOOST_OPENMETHOD_OVERRIDE( - times, (const matrix&, const dense_matrix&), string_pair) { - return string_pair(MATRIX_DENSE, MATRIX_MATRIX); -} - -BOOST_AUTO_TEST_CASE(pick_any_ambiguous) { - auto compiler = boost::openmethod::initialize(n2216()); - BOOST_TEST(compiler.report.ambiguous == 1u); - - // test_registry: use covariant return types to resolve ambiguity. - dense_matrix left, right; - auto result = times(left, right); - BOOST_TEST(result.first == MATRIX_DENSE); - BOOST_TEST(result.second == MATRIX_MATRIX); -} - -} // namespace TEST_NS diff --git a/test/test_n2216_covariant_return_type.cpp b/test/test_n2216_covariant_return_type.cpp new file mode 100644 index 00000000..64e576ba --- /dev/null +++ b/test/test_n2216_covariant_return_type.cpp @@ -0,0 +1,50 @@ +// 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 +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE n2216_covariant_return_type +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), + std::unique_ptr); + +BOOST_OPENMETHOD_OVERRIDE( + times, (const dense_matrix&, const matrix&), + std::unique_ptr) { + return std::make_unique(); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const matrix&, const dense_matrix&), std::unique_ptr) { + return std::make_unique(); +} + +static_assert( + std::is_same_v< + detail::virtual_type, default_registry>, + matrix>); + +BOOST_AUTO_TEST_CASE(covariant_return_type) { + auto compiler = boost::openmethod::initialize(n2216()); + BOOST_TEST(compiler.report.ambiguous == 0u); + + // use covariant return types to resolve ambiguity. + dense_matrix left, right; + auto result = times(left, right); + BOOST_TEST(result->type == DENSE_MATRIX); +} diff --git a/test/test_n2216_pick_any_ambiguous.cpp b/test/test_n2216_pick_any_ambiguous.cpp new file mode 100644 index 00000000..5391218f --- /dev/null +++ b/test/test_n2216_pick_any_ambiguous.cpp @@ -0,0 +1,45 @@ +// 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 "test_util.hpp" + +#define BOOST_TEST_MODULE n2216_pick_any_ambiguous +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), string_pair); + +BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), string_pair) { + return string_pair(MATRIX_MATRIX, NONE); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const dense_matrix&, const matrix&), string_pair) { + return string_pair(DENSE_MATRIX, MATRIX_MATRIX); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const matrix&, const dense_matrix&), string_pair) { + return string_pair(MATRIX_DENSE, MATRIX_MATRIX); +} + +BOOST_AUTO_TEST_CASE(pick_any_ambiguous) { + auto compiler = boost::openmethod::initialize(n2216()); + BOOST_TEST(compiler.report.ambiguous == 1u); + + // use covariant return types to resolve ambiguity. + dense_matrix left, right; + auto result = times(left, right); + BOOST_TEST(result.first == MATRIX_DENSE); + BOOST_TEST(result.second == MATRIX_MATRIX); +} 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 deleted file mode 100644 index fcd57c9c..00000000 --- a/test/test_runtime_errors.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// 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 -#include -#include - -#include "test_util.hpp" - -#include - -#define BOOST_TEST_MODULE openmethod -#include - -using namespace boost::openmethod; -using namespace test_matrices; - -struct capture_output : policies::output { - template - struct fn { - inline static std::ostringstream os; - }; -}; - -template -struct errors_ : test_registry_ { - struct capture { - using error_handler = - typename test_registry_::error_handler; - using output = typename test_registry_::output; - - capture() { - prev = error_handler::set( - [this](const typename errors_::error_handler::error_variant& - error) { - prev(error); - std::visit([](auto&& arg) { throw arg; }, error); - }); - } - - ~capture() { - error_handler::set(prev); - output::os.clear(); - } - - auto operator()() const { - return output::os.str(); - } - - typename errors_::error_handler::function_type prev; - }; -}; - -namespace TEST_NS { - -struct registry : errors_<__COUNTER__> {}; - -BOOST_OPENMETHOD( - transpose, (virtual_ptr), void, registry); - -BOOST_OPENMETHOD_OVERRIDE( - transpose, (virtual_ptr), void) { -} - -BOOST_AUTO_TEST_CASE(no_initialization) { - if constexpr (registry::has_runtime_checks) { - // throw during virtual_ptr construction, because of hash table lookup - { - registry::capture capture; - BOOST_CHECK_THROW( - (shared_virtual_ptr{ - std::make_shared()}), - not_initialized); - BOOST_TEST(capture() == "not initialized\n"); - } - - // throw during method call - { - registry::capture capture; - BOOST_CHECK_THROW( - transpose(make_shared_virtual()), - not_initialized); - } - } else { - try { - registry::require_initialized(); - } catch (not_initialized&) { - BOOST_TEST_FAIL("should not have thrown in release variant"); - } - } -} - -} // namespace TEST_NS - -namespace TEST_NS { - -using registry = errors_<__COUNTER__>; - -BOOST_OPENMETHOD( - transpose, (virtual_ptr), void, registry); - -// without any overrider initialize() would do nothing -BOOST_OPENMETHOD_OVERRIDE( - transpose, (virtual_ptr), void) { -} - -BOOST_AUTO_TEST_CASE(initialize_unknown_class) { - if constexpr (registry::has_runtime_checks) { - { - registry::capture capture; - BOOST_CHECK_THROW(initialize(), missing_class); - BOOST_TEST(capture().find("unknown class") != std::string::npos); - } - } -} - -} // namespace TEST_NS - -namespace TEST_NS { - -using registry = errors_<__COUNTER__>; - -// don't register dense_matrix -BOOST_OPENMETHOD_CLASSES(matrix, diagonal_matrix, registry); - -BOOST_OPENMETHOD(transpose, (virtual_), void, registry); - -// without any overrider initialize() would do nothing -BOOST_OPENMETHOD_OVERRIDE(transpose, (const matrix&), void) { -} - -BOOST_AUTO_TEST_CASE(call_unknown_class) { - if constexpr (registry::has_runtime_checks) { - { - initialize(); - - registry::capture capture; - BOOST_CHECK_THROW(transpose(dense_matrix()), missing_class); - BOOST_TEST(capture().find("unknown class") != std::string::npos); - } - } -} - -} // namespace TEST_NS - -namespace TEST_NS { - -using namespace test_matrices; -using registry = errors_<__COUNTER__>; - -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, registry); - -BOOST_OPENMETHOD( - times, - (virtual_ptr, virtual_ptr), - void, registry); - -BOOST_OPENMETHOD_OVERRIDE( - times, - (virtual_ptr, - virtual_ptr), - void) { -} - -BOOST_OPENMETHOD_OVERRIDE( - times, - (virtual_ptr, - virtual_ptr), - void) { -} - -BOOST_AUTO_TEST_CASE(bad_call) { - auto report = initialize().report; - BOOST_TEST(report.not_implemented == 1u); - BOOST_TEST(report.ambiguous == 1u); - - { - registry::capture capture; - matrix a, b; - BOOST_CHECK_THROW(times(a, b), no_overrider); - BOOST_TEST(capture().find("not implemented") != std::string::npos); - } - - { - registry::capture capture; - diagonal_matrix a, b; - BOOST_CHECK_THROW(times(a, b), ambiguous_call); - BOOST_TEST(capture().find("ambiguous") != std::string::npos); - } -} - -BOOST_AUTO_TEST_CASE(bad_call_type_ids) { - initialize(); - registry::capture capture; - - try { - diagonal_matrix a, b; - times(a, b); - BOOST_FAIL("should have thrown"); - } catch (const ambiguous_call& error) { - BOOST_TEST(error.arity == 2u); - BOOST_TEST(error.types[0] == &typeid(diagonal_matrix)); - BOOST_TEST(error.types[1] == &typeid(diagonal_matrix)); - } catch (...) { - BOOST_FAIL("wrong exception"); - } -} - -} // namespace TEST_NS - -namespace TEST_NS { - -using namespace test_matrices; -using registry = errors_<__COUNTER__>; - -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, registry); - -BOOST_OPENMETHOD( - times, - (shared_virtual_ptr, - shared_virtual_ptr), - void, registry); - -BOOST_AUTO_TEST_CASE(bad_call_type_ids_smart_ptr) { - initialize(); - registry::capture capture; - - try { - auto a = make_shared_virtual(); - auto b = make_shared_virtual(); - times(a, b); - BOOST_FAIL("should have thrown"); - } catch (const no_overrider& error) { - BOOST_TEST(error.arity == 2u); - BOOST_TEST(error.types[0] == &typeid(diagonal_matrix)); - BOOST_TEST(error.types[1] == &typeid(diagonal_matrix)); - } catch (...) { - BOOST_FAIL("wrong exception"); - } -} - -} // namespace TEST_NS - -namespace TEST_NS { - -using namespace test_matrices; -struct registry - : test_registry_<__COUNTER__>::with {}; - -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, registry); - -BOOST_OPENMETHOD( - times, (virtual_, virtual_), void, registry); - -BOOST_AUTO_TEST_CASE(throw_error) { - initialize(); - - try { - times(matrix(), matrix()); - BOOST_FAIL("should have thrown"); - } catch (const std::runtime_error& error) { - BOOST_TEST( - std::string(error.what()).find("not implemented") != - std::string::npos); - } catch (...) { - BOOST_FAIL("wrong exception"); - } -} - -} // namespace TEST_NS diff --git a/test/test_runtime_errors_bad_call.cpp b/test/test_runtime_errors_bad_call.cpp new file mode 100644 index 00000000..f37f007d --- /dev/null +++ b/test/test_runtime_errors_bad_call.cpp @@ -0,0 +1,63 @@ +// 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 "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE runtime_errors_bad_call +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +using capture = capture_errors; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); + +BOOST_OPENMETHOD( + times, (virtual_ptr, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + times, + (virtual_ptr, virtual_ptr), void) { +} + +BOOST_OPENMETHOD_OVERRIDE( + times, + (virtual_ptr, virtual_ptr), void) { +} + +// note: named bad_calls, not bad_call, to avoid clashing with +// boost::openmethod::bad_call under the file-wide using-directive +BOOST_AUTO_TEST_CASE(bad_calls) { + auto report = initialize().report; + BOOST_TEST(report.not_implemented == 1u); + BOOST_TEST(report.ambiguous == 1u); + + { + capture capture; + matrix a, b; + BOOST_CHECK_THROW(times(a, b), no_overrider); + BOOST_TEST(capture().find("not implemented") != std::string::npos); + } + + { + capture capture; + diagonal_matrix a, b; + BOOST_CHECK_THROW(times(a, b), ambiguous_call); + BOOST_TEST(capture().find("ambiguous") != std::string::npos); + } +} diff --git a/test/test_runtime_errors_bad_call_type_ids.cpp b/test/test_runtime_errors_bad_call_type_ids.cpp new file mode 100644 index 00000000..b31db119 --- /dev/null +++ b/test/test_runtime_errors_bad_call_type_ids.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) + +#include + +#include "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE runtime_errors_bad_call_type_ids +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +using capture = capture_errors; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); + +BOOST_OPENMETHOD( + times, (virtual_ptr, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + times, + (virtual_ptr, virtual_ptr), void) { +} + +BOOST_OPENMETHOD_OVERRIDE( + times, + (virtual_ptr, virtual_ptr), void) { +} + +BOOST_AUTO_TEST_CASE(bad_call_type_ids) { + initialize(); + capture capture; + + try { + diagonal_matrix a, b; + times(a, b); + BOOST_FAIL("should have thrown"); + } catch (const ambiguous_call& error) { + BOOST_TEST(error.arity == 2u); + BOOST_TEST(error.types[0] == &typeid(diagonal_matrix)); + BOOST_TEST(error.types[1] == &typeid(diagonal_matrix)); + } catch (...) { + BOOST_FAIL("wrong exception"); + } +} diff --git a/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp b/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp new file mode 100644 index 00000000..75177403 --- /dev/null +++ b/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp @@ -0,0 +1,51 @@ +// 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 "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE runtime_errors_bad_call_type_ids_smart_ptr +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +using capture = capture_errors; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); + +BOOST_OPENMETHOD( + times, (shared_virtual_ptr, shared_virtual_ptr), + void); + +BOOST_AUTO_TEST_CASE(bad_call_type_ids_smart_ptr) { + initialize(); + capture capture; + + try { + auto a = make_shared_virtual(); + auto b = make_shared_virtual(); + times(a, b); + BOOST_FAIL("should have thrown"); + } catch (const no_overrider& error) { + BOOST_TEST(error.arity == 2u); + BOOST_TEST(error.types[0] == &typeid(diagonal_matrix)); + BOOST_TEST(error.types[1] == &typeid(diagonal_matrix)); + } catch (...) { + BOOST_FAIL("wrong exception"); + } +} diff --git a/test/test_runtime_errors_call_unknown_class.cpp b/test/test_runtime_errors_call_unknown_class.cpp new file mode 100644 index 00000000..8cba0dca --- /dev/null +++ b/test/test_runtime_errors_call_unknown_class.cpp @@ -0,0 +1,47 @@ +// 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 "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE runtime_errors_call_unknown_class +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +using capture = capture_errors; + +// don't register dense_matrix +BOOST_OPENMETHOD_CLASSES(matrix, diagonal_matrix); + +BOOST_OPENMETHOD(transpose, (virtual_), void); + +// without any overrider initialize() would do nothing +BOOST_OPENMETHOD_OVERRIDE(transpose, (const matrix&), void) { +} + +BOOST_AUTO_TEST_CASE(call_unknown_class) { + if constexpr (test_registry::has_runtime_checks) { + { + initialize(); + + capture capture; + BOOST_CHECK_THROW(transpose(dense_matrix()), missing_class); + BOOST_TEST(capture().find("unknown class") != std::string::npos); + } + } +} diff --git a/test/test_runtime_errors_initialize_unknown_class.cpp b/test/test_runtime_errors_initialize_unknown_class.cpp new file mode 100644 index 00000000..e61d2be8 --- /dev/null +++ b/test/test_runtime_errors_initialize_unknown_class.cpp @@ -0,0 +1,43 @@ +// 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 "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE runtime_errors_initialize_unknown_class +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +using capture = capture_errors; + +BOOST_OPENMETHOD(transpose, (virtual_ptr), void); + +// without any overrider initialize() would do nothing +BOOST_OPENMETHOD_OVERRIDE(transpose, (virtual_ptr), void) { +} + +BOOST_AUTO_TEST_CASE(initialize_unknown_class) { + if constexpr (test_registry::has_runtime_checks) { + { + capture capture; + BOOST_CHECK_THROW(initialize(), missing_class); + auto s = capture(); + BOOST_TEST(s.find("unknown class") != std::string::npos); + } + } +} diff --git a/test/test_runtime_errors_no_initialization.cpp b/test/test_runtime_errors_no_initialization.cpp new file mode 100644 index 00000000..cdcc8543 --- /dev/null +++ b/test/test_runtime_errors_no_initialization.cpp @@ -0,0 +1,61 @@ +// 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 "test_capture_errors.hpp" + +struct test_registry + : boost::openmethod::default_registry::with {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE runtime_errors_no_initialization +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +using capture = capture_errors; + +BOOST_OPENMETHOD(transpose, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + transpose, (virtual_ptr), void) { +} + +BOOST_AUTO_TEST_CASE(no_initialization) { + if constexpr (test_registry::has_runtime_checks) { + // throw during virtual_ptr construction, because of hash table lookup + { + capture capture; + BOOST_CHECK_THROW( + (shared_virtual_ptr{ + std::make_shared()}), + not_initialized); + BOOST_TEST(capture() == "not initialized\n"); + } + + // throw during method call + { + capture capture; + BOOST_CHECK_THROW( + transpose(make_shared_virtual()), + not_initialized); + } + } else { + try { + test_registry::require_initialized(); + } catch (not_initialized&) { + BOOST_TEST_FAIL("should not have thrown in release variant"); + } + } +} diff --git a/test/test_runtime_errors_throw_error.cpp b/test/test_runtime_errors_throw_error.cpp new file mode 100644 index 00000000..1572569e --- /dev/null +++ b/test/test_runtime_errors_throw_error.cpp @@ -0,0 +1,44 @@ +// 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 + +struct test_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::throw_error_handler> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry + +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE runtime_errors_throw_error +#include + +using namespace boost::openmethod; +using namespace test_matrices; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), void); + +BOOST_AUTO_TEST_CASE(throw_error) { + initialize(); + + try { + times(matrix(), matrix()); + BOOST_FAIL("should have thrown"); + } catch (const std::runtime_error& error) { + BOOST_TEST( + std::string(error.what()).find("not implemented") != + std::string::npos); + } catch (...) { + BOOST_FAIL("wrong exception"); + } +} diff --git a/test/test_shared_virtual_ptr_dispatch.cpp b/test/test_shared_virtual_ptr_dispatch.cpp new file mode 100644 index 00000000..33b2fc73 --- /dev/null +++ b/test/test_shared_virtual_ptr_dispatch.cpp @@ -0,0 +1,59 @@ +// 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 + +#define BOOST_TEST_MODULE shared_virtual_ptr_dispatch +#include + +#include "test_util.hpp" + +using boost::mp11::mp_list; +using namespace boost::openmethod; +using namespace boost::openmethod::detail; +using namespace game; + +struct BOOST_OPENMETHOD_ID(poke); +struct BOOST_OPENMETHOD_ID(fight); + +BOOST_AUTO_TEST_CASE_TEMPLATE( + test_virtual_ptr_dispatch, Registry, policy_types<__COUNTER__>) { + + BOOST_OPENMETHOD_REGISTER( + use_classes); + + using poke = method< + BOOST_OPENMETHOD_ID(poke), + auto(shared_virtual_ptr)->std::string, Registry>; + + BOOST_OPENMETHOD_REGISTER(typename poke::template override< + poke_bear>>); + + using fight = method< + BOOST_OPENMETHOD_ID(fight), + auto( + shared_virtual_ptr, + shared_virtual_ptr, + shared_virtual_ptr) + ->std::string, + Registry>; + + BOOST_OPENMETHOD_REGISTER(typename fight::template override, + shared_virtual_ptr, + shared_virtual_ptr>>); + + initialize(); + + auto bear = make_shared_virtual(); + auto warrior = make_shared_virtual(); + auto axe = make_shared_virtual(); + + BOOST_TEST(poke::fn(bear) == "growl"); + + BOOST_TEST(fight::fn(warrior, axe, bear) == "kill bear"); +} diff --git a/test/test_util.hpp b/test/test_util.hpp index 5562a620..b9428349 100644 --- a/test/test_util.hpp +++ b/test/test_util.hpp @@ -7,23 +7,25 @@ #define BOOST_OPENMETHOD_TEST_HELPERS_HPP #include +#include +#include #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__) @@ -50,6 +52,77 @@ inline std::ostream& operator<<(std::ostream& os, const string_pair& pair) { return os << "(" << pair.first << ", " << pair.second << ")"; } +namespace animals { + +struct Animal { + explicit Animal(std::string str) { + name = std::move(str); + } + + Animal(const Animal&) = delete; + + Animal(Animal&&) = default; + + virtual ~Animal() { + } + + std::string name; +}; + +struct Property { + std::string owner = "Bill"; +}; + +struct Dog : Property, Animal { + using Animal::Animal; +}; + +struct Cat : Property, virtual Animal { + using Animal::Animal; +}; + +} // namespace animals + +namespace game { + +struct Player { + virtual ~Player() { + } +}; + +struct Warrior : Player {}; +struct Wizard : Player {}; + +struct Bear : Player {}; + +struct Object { + virtual ~Object() { + } +}; + +struct Axe : Object {}; + +template +auto poke_bear(VirtualBearPtr) { + return std::string("growl"); +} + +template +auto fight_bear(VirtualWarriorPtr, VirtualAxePtr, VirtualBearPtr) { + return "kill bear"; +} + +template +struct indirect_test_registry + : test_registry_::template with< + boost::openmethod::policies::indirect_vptr> {}; + +template +using policy_types = + boost::mp11::mp_list, indirect_test_registry>; + +} // namespace game + namespace test_matrices { MAKE_STRING_CONSTANT(NONE) diff --git a/test/test_virtual_ptr_by_ref.cpp b/test/test_virtual_ptr_by_ref.cpp new file mode 100644 index 00000000..b1905ef1 --- /dev/null +++ b/test/test_virtual_ptr_by_ref.cpp @@ -0,0 +1,61 @@ +// 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 + +#define BOOST_TEST_MODULE virtual_ptr_by_ref +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (const virtual_ptr&, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const virtual_ptr&, std::ostream& os), void) { + os << "bark"; +} + +static_assert(sizeof(virtual_ptr) == 2 * sizeof(void*)); + +BOOST_AUTO_TEST_CASE(test_virtual_ptr_by_ref) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + Dog dog; + virtual_ptr vptr(dog); + poke(vptr, os); + } + + { + // Using deduction guide. + boost::test_tools::output_test_stream os; + Animal&& animal = Dog(); + auto vptr = virtual_ptr(animal); + poke(vptr, os); + BOOST_CHECK(os.is_equal("bark")); + } + + { + // Using conversion ctor. + boost::test_tools::output_test_stream os; + Animal&& animal = Dog(); + poke(animal, os); + BOOST_CHECK(os.is_equal("bark")); + } +} diff --git a/test/test_virtual_ptr_dispatch.cpp b/test/test_virtual_ptr_dispatch.cpp index b6e35bd3..9ce8bda1 100644 --- a/test/test_virtual_ptr_dispatch.cpp +++ b/test/test_virtual_ptr_dispatch.cpp @@ -4,255 +4,21 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include -#include #include -#include -#include -#include - -#define BOOST_TEST_MODULE openmethod +#define BOOST_TEST_MODULE virtual_ptr_dispatch #include -#include #include "test_util.hpp" using boost::mp11::mp_list; -using std::cout; using namespace boost::openmethod; using namespace boost::openmethod::detail; - -namespace using_polymorphic_classes { - -struct Animal { - virtual ~Animal() { - } -}; - -struct Dog : Animal {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog); - -namespace BOOST_OPENMETHOD_GENSYM { - -BOOST_OPENMETHOD(poke, (const virtual_ptr&, std::ostream&), void); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (const virtual_ptr&, std::ostream& os), void) { - os << "bark"; -} - -static_assert(sizeof(virtual_ptr) == 2 * sizeof(void*)); - -BOOST_AUTO_TEST_CASE(test_virtual_ptr_by_ref) { - boost::openmethod::initialize(); - - { - boost::test_tools::output_test_stream os; - Dog dog; - virtual_ptr vptr(dog); - poke(vptr, os); - } - - { - // Using deduction guide. - boost::test_tools::output_test_stream os; - Animal&& animal = Dog(); - auto vptr = virtual_ptr(animal); - poke(vptr, os); - BOOST_CHECK(os.is_equal("bark")); - } - - { - // Using conversion ctor. - boost::test_tools::output_test_stream os; - Animal&& animal = Dog(); - poke(animal, os); - BOOST_CHECK(os.is_equal("bark")); - } -} - -} // namespace BOOST_OPENMETHOD_GENSYM - -namespace BOOST_OPENMETHOD_GENSYM { - -BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); - -BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { - os << "bark"; -} - -BOOST_AUTO_TEST_CASE(test_virtual_shared_by_value) { - boost::openmethod::initialize(); - - { - boost::test_tools::output_test_stream os; - shared_virtual_ptr animal = make_shared_virtual(); - poke(animal, os); - BOOST_CHECK(os.is_equal("bark")); - } -} - -} // namespace BOOST_OPENMETHOD_GENSYM - -namespace BOOST_OPENMETHOD_GENSYM { - -BOOST_OPENMETHOD( - poke, (const shared_virtual_ptr&, std::ostream&), void); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (const shared_virtual_ptr&, std::ostream& os), void) { - os << "bark"; -} - -BOOST_AUTO_TEST_CASE(test_virtual_shared_by_const_reference) { - boost::openmethod::initialize(); - - { - boost::test_tools::output_test_stream os; - shared_virtual_ptr animal = make_shared_virtual(); - poke(animal, os); - BOOST_CHECK(os.is_equal("bark")); - } -} - -} // namespace BOOST_OPENMETHOD_GENSYM - -namespace BOOST_OPENMETHOD_GENSYM { - -BOOST_OPENMETHOD(poke, (unique_virtual_ptr, std::ostream&), void); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (unique_virtual_ptr, std::ostream& os), void) { - os << "bark"; -} - -BOOST_AUTO_TEST_CASE(test_virtual_unique) { - boost::openmethod::initialize(); - - { - boost::test_tools::output_test_stream os; - poke(make_unique_virtual(), os); - BOOST_CHECK(os.is_equal("bark")); - } -} - -} // namespace BOOST_OPENMETHOD_GENSYM -} // namespace using_polymorphic_classes - -namespace using_non_polymorphic_classes { - -struct Animal {}; - -struct Dog : Animal {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog); - -BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); - -BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { - os << "bark"; -} - -BOOST_AUTO_TEST_CASE(test_virtual_ptr_non_polymorphic) { - boost::openmethod::initialize(); - - { - boost::test_tools::output_test_stream os; - Dog dog; - auto vptr = virtual_ptr::final(dog); - poke(vptr, os); - BOOST_CHECK(os.is_equal("bark")); - } -} - -} // namespace using_non_polymorphic_classes - -struct Player { - virtual ~Player() { - } -}; - -struct Warrior : Player {}; -struct Wizard : Player {}; - -struct Bear : Player {}; - -struct Object { - virtual ~Object() { - } -}; - -struct Axe : Object {}; - -template -auto poke_bear(VirtualBearPtr) { - return std::string("growl"); -} - -template -auto fight_bear(VirtualWarriorPtr, VirtualAxePtr, VirtualBearPtr) { - return "kill bear"; -} - -template -struct indirect_test_registry - : test_registry_::template with {}; - -template -using policy_types = - boost::mp11::mp_list, indirect_test_registry>; +using namespace game; struct BOOST_OPENMETHOD_ID(poke); struct BOOST_OPENMETHOD_ID(fight); -namespace BOOST_OPENMETHOD_GENSYM { - -BOOST_AUTO_TEST_CASE_TEMPLATE( - test_virtual_ptr, Registry, policy_types<__COUNTER__>) { - - BOOST_OPENMETHOD_REGISTER( - use_classes); - using poke = method< - BOOST_OPENMETHOD_ID(poke), - auto(virtual_ptr)->std::string, Registry>; - BOOST_OPENMETHOD_REGISTER(typename poke::template override< - poke_bear>>); - - initialize(); - - using vptr_player = virtual_ptr; - static_assert(detail::is_virtual_ptr); - using vptr_bear = virtual_ptr; - - Player player; - auto virtual_player = vptr_player::final(player); - BOOST_TEST(&*virtual_player == &player); - BOOST_TEST( - (virtual_player.vptr() == Registry::template static_vptr)); - - Bear bear; - BOOST_TEST((&*vptr_bear::final(bear)) == &bear); - BOOST_TEST(( - vptr_bear::final(bear).vptr() == Registry::template static_vptr)); - - BOOST_TEST( - (vptr_player(bear).vptr() == Registry::template static_vptr)); - - vptr_bear virtual_bear_ptr(bear); - - struct upcast { - static void fn(vptr_player) { - } - }; - - upcast::fn(virtual_bear_ptr); -} -} // namespace BOOST_OPENMETHOD_GENSYM - -namespace test_virtual_ptr_dispatch { - BOOST_AUTO_TEST_CASE_TEMPLATE( test_virtual_ptr_dispatch, Registry, policy_types<__COUNTER__>) { @@ -290,47 +56,3 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( virtual_ptr(axe), virtual_ptr(bear)) == "kill bear"); } - -} // namespace test_virtual_ptr_dispatch - -namespace test_shared_virtual_ptr_dispatch { - -BOOST_AUTO_TEST_CASE_TEMPLATE( - test_virtual_ptr_dispatch, Registry, policy_types<__COUNTER__>) { - - BOOST_OPENMETHOD_REGISTER( - use_classes); - - using poke = method< - BOOST_OPENMETHOD_ID(poke), - auto(shared_virtual_ptr)->std::string, Registry>; - - BOOST_OPENMETHOD_REGISTER(typename poke::template override< - poke_bear>>); - - using fight = method< - BOOST_OPENMETHOD_ID(fight), - auto( - shared_virtual_ptr, - shared_virtual_ptr, - shared_virtual_ptr) - ->std::string, - Registry>; - - BOOST_OPENMETHOD_REGISTER(typename fight::template override, - shared_virtual_ptr, - shared_virtual_ptr>>); - - initialize(); - - auto bear = make_shared_virtual(); - auto warrior = make_shared_virtual(); - auto axe = make_shared_virtual(); - - BOOST_TEST(poke::fn(bear) == "growl"); - - BOOST_TEST(fight::fn(warrior, axe, bear) == "kill bear"); -} - -} // namespace test_shared_virtual_ptr_dispatch diff --git a/test/test_virtual_ptr_final.cpp b/test/test_virtual_ptr_final.cpp new file mode 100644 index 00000000..ee09f075 --- /dev/null +++ b/test/test_virtual_ptr_final.cpp @@ -0,0 +1,60 @@ +// 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 + +#define BOOST_TEST_MODULE virtual_ptr_final +#include + +#include "test_util.hpp" + +using boost::mp11::mp_list; +using namespace boost::openmethod; +using namespace boost::openmethod::detail; +using namespace game; + +struct BOOST_OPENMETHOD_ID(poke); + +BOOST_AUTO_TEST_CASE_TEMPLATE( + test_virtual_ptr, Registry, policy_types<__COUNTER__>) { + + BOOST_OPENMETHOD_REGISTER( + use_classes); + using poke = method< + BOOST_OPENMETHOD_ID(poke), + auto(virtual_ptr)->std::string, Registry>; + BOOST_OPENMETHOD_REGISTER(typename poke::template override< + poke_bear>>); + + initialize(); + + using vptr_player = virtual_ptr; + static_assert(detail::is_virtual_ptr); + using vptr_bear = virtual_ptr; + + Player player; + auto virtual_player = vptr_player::final(player); + BOOST_TEST(&*virtual_player == &player); + BOOST_TEST( + (virtual_player.vptr() == Registry::template static_vptr)); + + Bear bear; + BOOST_TEST((&*vptr_bear::final(bear)) == &bear); + BOOST_TEST(( + vptr_bear::final(bear).vptr() == Registry::template static_vptr)); + + BOOST_TEST( + (vptr_player(bear).vptr() == Registry::template static_vptr)); + + vptr_bear virtual_bear_ptr(bear); + + struct upcast { + static void fn(vptr_player) { + } + }; + + upcast::fn(virtual_bear_ptr); +} diff --git a/test/test_virtual_ptr_non_polymorphic.cpp b/test/test_virtual_ptr_non_polymorphic.cpp new file mode 100644 index 00000000..7ffd642d --- /dev/null +++ b/test/test_virtual_ptr_non_polymorphic.cpp @@ -0,0 +1,39 @@ +// 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 + +#define BOOST_TEST_MODULE virtual_ptr_non_polymorphic +#include +#include + +using namespace boost::openmethod; + +struct Animal {}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_ptr_non_polymorphic) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + Dog dog; + auto vptr = virtual_ptr::final(dog); + poke(vptr, os); + BOOST_CHECK(os.is_equal("bark")); + } +} diff --git a/test/test_virtual_ptr_shared_by_const_ref.cpp b/test/test_virtual_ptr_shared_by_const_ref.cpp new file mode 100644 index 00000000..d7b1be43 --- /dev/null +++ b/test/test_virtual_ptr_shared_by_const_ref.cpp @@ -0,0 +1,44 @@ +// 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 + +#include + +#define BOOST_TEST_MODULE virtual_ptr_shared_by_const_ref +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD( + poke, (const shared_virtual_ptr&, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const shared_virtual_ptr&, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_shared_by_const_reference) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + shared_virtual_ptr animal = make_shared_virtual(); + poke(animal, os); + BOOST_CHECK(os.is_equal("bark")); + } +} diff --git a/test/test_virtual_ptr_shared_by_value.cpp b/test/test_virtual_ptr_shared_by_value.cpp new file mode 100644 index 00000000..871961b9 --- /dev/null +++ b/test/test_virtual_ptr_shared_by_value.cpp @@ -0,0 +1,42 @@ +// 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 + +#include + +#define BOOST_TEST_MODULE virtual_ptr_shared_by_value +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_shared_by_value) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + shared_virtual_ptr animal = make_shared_virtual(); + poke(animal, os); + BOOST_CHECK(os.is_equal("bark")); + } +} diff --git a/test/test_virtual_ptr_unique.cpp b/test/test_virtual_ptr_unique.cpp new file mode 100644 index 00000000..49d84418 --- /dev/null +++ b/test/test_virtual_ptr_unique.cpp @@ -0,0 +1,42 @@ +// 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 + +#include + +#define BOOST_TEST_MODULE virtual_ptr_unique +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (unique_virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (unique_virtual_ptr, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_unique) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + poke(make_unique_virtual(), os); + BOOST_CHECK(os.is_equal("bark")); + } +}