diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e246ba..dd18d861 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ set( if (BOOST_OPENMETHOD_BUILD_TESTS OR BOOST_OPENMETHOD_MRDOCS_BUILD) list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::smart_ptr) + list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::any) endif() foreach (BOOST_OPENMETHOD_DEPENDENCY ${BOOST_OPENMETHOD_DEPENDENCIES}) @@ -219,5 +220,7 @@ if (BOOST_OPENMETHOD_BUILD_TESTS) # Examples if (BOOST_OPENMETHOD_BUILD_EXAMPLES) add_subdirectory(doc/modules/ROOT/examples) + # Sources behind the `include:` markers in the reference doc comments. + add_subdirectory(doc/modules/ROOT/snippets) endif () endif () diff --git a/doc/antora.yml b/doc/antora.yml index dfcfac86..71112caf 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -15,6 +15,12 @@ asciidoc: attributes: source-language: asciidoc@ table-caption: false + # Base of the links to the header sources in ref_headers.adoc. It must be + # an absolute url: neither the PR previews nor boost.org serve the library + # sources next to the docs. The previews publish libs/openmethod/doc only, + # and boost.org serves the headers from doc/libs//boost, not from + # libs/openmethod/include. + headers-url: https://github.com/boostorg/openmethod/blob/develop/include nav: - modules/ROOT/nav.adoc ext: diff --git a/doc/build_antora.sh b/doc/build_antora.sh index 58958b9c..7d287c05 100755 --- a/doc/build_antora.sh +++ b/doc/build_antora.sh @@ -22,82 +22,14 @@ fi SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "$SCRIPT_DIR" -if [ -z "${BOOST_SRC_DIR:-}" ]; then - CANDIDATE=$( cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd ) - if [ -n "$CANDIDATE" ]; then - BOOST_SRC_DIR_IS_VALID=ON - for F in "CMakeLists.txt" "Jamroot" "boost-build.jam" "bootstrap.sh" "libs"; do - if [ ! -e "$CANDIDATE/$F" ]; then - BOOST_SRC_DIR_IS_VALID=OFF - break - fi - done - if [ "$BOOST_SRC_DIR_IS_VALID" = "ON" ]; then - export BOOST_SRC_DIR="$CANDIDATE" - echo "Using BOOST_SRC_DIR=$BOOST_SRC_DIR" - fi - fi -fi - -BRANCH=master - -if [ -n "${BOOST_SRC_DIR:-}" ]; then - if [ -n "${CIRCLE_REPOSITORY_URL:-}" ]; then - if [[ "$CIRCLE_REPOSITORY_URL" =~ boostorg/boost(\.git)?$ ]]; then - LIB="$(basename "$(dirname "$SCRIPT_DIR")")" - REPOSITORY="boostorg/${LIB}" - BRANCH=$(git -C "$BOOST_SRC_DIR" rev-parse --abbrev-ref HEAD) - else - ACCOUNT="${CIRCLE_REPOSITORY_URL#*:}" - ACCOUNT="${ACCOUNT%%/*}" - LIB=$(basename "$(git rev-parse --show-toplevel)") - REPOSITORY="${ACCOUNT}/${LIB}" - fi - SHA=$(git -C "$BOOST_SRC_DIR/libs" ls-tree HEAD | grep -w openmethod | awk '{print $3}') - elif [ -n "${GITHUB_REPOSITORY:-}" ]; then - REPOSITORY="${GITHUB_REPOSITORY}" - SHA="${GITHUB_SHA}" - fi -fi - -cd "$SCRIPT_DIR" - -if [ -n "${REPOSITORY}" ] && [ -n "${SHA}" ]; then - BASE_URL="https://github.com/${REPOSITORY}/blob/${SHA}" - echo "Setting base-url to $BASE_URL" - cp mrdocs.yml mrdocs.yml.bak - perl -i -pe 's{^\s*base-url:.*$}{base-url: '"$BASE_URL/"'}' mrdocs.yml -else - echo "REPOSITORY or SHA not set; skipping base-url modification" -fi - echo "Building documentation with Antora..." + echo "Installing npm dependencies..." npm ci echo "Building docs in custom dir..." PATH="$(pwd)/node_modules/.bin:${PATH}" export PATH -npx antora --clean --fetch "$PLAYBOOK" --stacktrace # --log-level all - -echo "Fixing links to non-mrdocs URIs..." -echo "BRANCH='${BRANCH:-}'" -echo "BASE_URL='${BASE_URL:-}'" - -for f in $(find html -name '*.html'); do - perl -i -pe "s{{{(.*?)}}}{\$1}g" "$f" - perl -i -pe "s{Boost.OpenMethod}{Boost.OpenMethod}g" "$f" -done - -if [ -n "${BASE_URL:-}" ]; then - if [ -f mrdocs.yml.bak ]; then - mv -f mrdocs.yml.bak mrdocs.yml - echo "Restored original mrdocs.yml" - else - echo "mrdocs.yml.bak not found; skipping restore" - fi - perl -i -pe "s[{{BASE_URL}}][$BASE_URL]g" \ - html/openmethod/ref_headers.html html/openmethod/BOOST_OPENMETHOD*.html -fi +npx antora --clean --fetch "$PLAYBOOK" --stacktrace --log-level all echo "Done" diff --git a/doc/modules/ROOT/examples/registry_identity.cpp b/doc/modules/ROOT/examples/registry_identity.cpp new file mode 100644 index 00000000..5d91db6a --- /dev/null +++ b/doc/modules/ROOT/examples/registry_identity.cpp @@ -0,0 +1,55 @@ +// 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 + +using namespace boost::openmethod; + +namespace same_policies { + +// tag::shared[] +struct animals : registry> {}; +struct vehicles : registry> {}; + +// the policy lists are identical, so this is one registry, not two +static_assert(std::is_same_v); +// end::shared[] + +} // namespace same_policies + +namespace distinct_policies { + +// tag::distinct[] +// a policy in a category of its own, carrying nothing but a number +struct marker_category { + using category = marker_category; +}; + +template +struct marker final : marker_category { + template + struct fn {}; +}; + +struct animals : default_registry::with> {}; +struct vehicles : default_registry::with> {}; + +static_assert(!std::is_same_v); +// end::distinct[] + +} // namespace distinct_policies + +auto main() -> int { + // the shared pair reach one state, the distinct pair two + assert(same_policies::animals::id() == same_policies::vehicles::id()); + assert( + distinct_policies::animals::id() != distinct_policies::vehicles::id()); + + return 0; +} diff --git a/doc/modules/ROOT/examples/virtual_any.cpp b/doc/modules/ROOT/examples/virtual_any.cpp new file mode 100644 index 00000000..f1a6adf8 --- /dev/null +++ b/doc/modules/ROOT/examples/virtual_any.cpp @@ -0,0 +1,63 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// clang-format off + +// tag::content[] +#include +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +// `std::any` becomes the common base of the types it may contain. +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +// An overrider takes the contained value... +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) { + return std::to_string(value) + " the integer"; +} + +// ...or the `any` itself, which makes it a catch-all. +BOOST_OPENMETHOD_OVERRIDE(name, (const std::any&), std::string) { + return "something else"; +} + +#include + +int main() { + initialize(); + + std::any spot = Dog{"Spot"}; + std::any felix = std::string("Felix the cat"); + std::any answer = 42; + std::any pi = 3.14f; + + std::cout << name(spot) << "\n"; // Spot the dog + std::cout << name(felix) << "\n"; // Felix the cat + std::cout << name(answer) << "\n"; // 42 the integer + + // `float` is registered, but has no overrider of its own, so the + // catch-all applies. + std::cout << name(pi) << "\n"; // something else +} +// end::content[] diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index edaebaa3..6edd8231 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -13,8 +13,9 @@ ** xref:custom_rtti.adoc[Custom RTTI] ** xref:error_handling.adoc[Error Handling] ** xref:virtual_ptr_alt.adoc[Virtual Pointer Alternatives] +** xref:interop_any.adoc[Interoperation with `any`] ** xref:shared_libraries.adoc[Shared Libraries] -* Reference +* xref:reference:index.adoc[Reference] ** xref:ref_headers.adoc[Headers] ** xref:ref_macros.adoc[Macros] ** xref:reference:boost/openmethod.adoc[Namespace boost::openmethod] diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc deleted file mode 100644 index 99528713..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD.adoc +++ /dev/null @@ -1,87 +0,0 @@ - -# BOOST_OPENMETHOD - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD(ID, (PARAMETERS...), RETURN_TYPE [, REGISTRY]); -``` - -## Description - -Declares a method, called `ID`, with the given `PARAMETERS` and `RETURN_TYPE`, -and adds it to `REGISTRY`. - -`PARAMETERS` is a comma-separated list of types, possibly followed by parameter -names, just like in a function declaration. Parameters with a type in the form -`virtual_ptr` or `virtual_` are called virtual parameters. The dynamic -type of the arguments passed in virtual parameters determines which overrider to -call, following the same rules as overloaded function resolution: - -1. Form the set of all applicable overriders. An overrider is applicable - if it can be called with the arguments passed to the method. -2. If the set is empty, call the error handler (if present in the - registry), then terminate the program with `abort`. -3. Remove the overriders that are dominated by other overriders in the - set. Overrider A dominates overrider B if any of its virtual formal - parameters is more specialized than B's, and if none of B's virtual - parameters is more specialized than A's. -4. If the resulting set contains exactly one overrider, call it. - -If a single most specialized overrider does not exist, the program is -terminated via `abort`. If the registry contains an `error_handler` -policy, its `error` function is called with an object that describes the -error, prior calling `abort`. `error` may prevent termination by throwing an -exception. - -[] - -For each virtual argument `arg`, the dispatch mechanism calls -`virtual_traits::peek(arg)` and deduces the v-table pointer from the -`result`, using the first of the following methods that applies: - -1. If `result` is a `virtual_ptr`, get the pointer to the v-table from it. -2. If `boost_openmethod_vptr` can be called with `result` and a `Registry*`, - and it returns a `vptr_type`, call it. -3. Call `Registry::vptr::dynamic_vptr(result)`. - - -The macro creates an ordinary inline function in the current scope, with the -`virtual_` decorators removed from the parameter types. `virtual_ptr`{empty}s -are preserved. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. - -NOTE: The default value for `REGISTRY` is the value of -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` at the point `` is -included. Changing the value of this symbol has no effect after that point. - -## Implementation Notes - -The macro creates several additional constructs: - -* A `struct` forward declaration that acts as the method's identifier: - -```c++ -struct BOOST_OPENMETHOD_ID(ID); -``` - -* A class template declaration that acts as a container for the method's -overriders in the current scope: - -```c++ -template struct BOOST_OPENMETHOD_OVERRIDERS(NAME); -``` - -* A _guide_ function used to match overriders with the method: - -```c++ -auto BOOST_OPENMETHOD_ID(ID)_guide(...) - -> ::boost::openmethod::method< - BOOST_OPENMETHOD_ID(ID)(PARAMETERS...), RETURN_TYPE [, REGISTRY]>; -``` - -* A xref:BOOST_OPENMETHOD_REGISTER.adoc[registrar] that adds the method to the -registry. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc deleted file mode 100644 index 979a165c..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_CLASSES.adoc +++ /dev/null @@ -1,20 +0,0 @@ -# BOOST_OPENMETHOD_CLASSES - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_CLASSES(CLASSES...[, REGISTRY]); -``` - -## Description - -Registers `CLASSES` in REGISTRY. - -NOTE: The default value for `REGISTRY` is the value of -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` when `` is -included. Subsequently changing it has no retroactive effect. - -This macro is a wrapper around cpp:use_classes[]; see its documentation for more -details. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc deleted file mode 100644 index 3465682c..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc +++ /dev/null @@ -1,56 +0,0 @@ -# BOOST_OPENMETHOD_DECLARE_OVERRIDER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, (PARAMETERS...), RETURN_TYPE) -``` - -## Description - -Declares an overrider for a method, but does not start its definition. This -macro can be used in header files. - -`ID` is the identifier of the method to which the overrider is added. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. - -`PARAMETERS` is a comma-separated list of types, possibly followed by parameter -names, just like in a function declaration. - -The macro tries to locate a method that can be called with the same argument -list as the overrider, possibly via argument dependent lookup. - -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. - -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 accessible unambiguous base. - -## Implementation Notes - -The macro creates additional entities in the current scope. - -* A class template declaration that acts as a container for the method's -overriders in the current scope: - -```c++ -template struct BOOST_OPENMETHOD_OVERRIDERS(NAME); -``` - -* A specialization of the container for the overrider: -+ --- -```c++ -struct BOOST_OPENMETHOD_OVERRIDERS(ID) { - static auto fn(PARAMETERS...) -> RETURN_TYPE; - static auto has_next() -> bool; - template - static auto next(typename... Args) -> RETURN_TYPE; -}; -``` diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc deleted file mode 100644 index 5febb823..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc +++ /dev/null @@ -1,36 +0,0 @@ -# BOOST_OPENMETHOD_DEFAULT_REGISTRY - -Default value for Registry - -== Synopsis - -Defined in `<https://www.github.com/boostorg/openmethod/blob/develop/include/boost/openmethod/core.hpp#L27[boost/openmethod/core.hpp]>` - -```cpp -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY ::boost::openmethod::default_registry -``` - -== Description - -The name of the default registry. - -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` is the default value for the `Registry` -template parameter of cpp:method[], cpp:use_classes[], cpp:virtual_ptr[], and -all the constructs that take a registry as a template argument. - -`BOOST_OPENMETHOD_DEFAULT_REGISTRY` can be defined by a program to change the -default registry globally, *before* including ``. After that, changing its value has no effect, even on other macros. - -To override the default registry, proceed as follows: - -1. Define a cpp:registry[] class, either from scratch, or by tuning an existing -registry. Include ``, -``, and headers under -`boost/openmethod/policies` as needed. - -2. Set `BOOST_OPENMETHOD_DEFAULT_REGISTRY` to the new registry class. - -3. Include ``. - -NOTE;; Use this feature with caution, as it will cause ODR violations if -different translation units define different default registries. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc deleted file mode 100644 index e118ace6..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc +++ /dev/null @@ -1,18 +0,0 @@ - -# BOOST_OPENMETHOD_DEFINE_OVERRIDER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, (PARAMETERS...), RETURN_TYPE) -``` - -## Description - -Defines the body of an overrider declared with -xref:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER]. -It should be called in an implementation file, and followed by a function body. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc deleted file mode 100644 index afe3134d..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc +++ /dev/null @@ -1,13 +0,0 @@ - -# BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS - -Enables runtime checks in cpp:default_registry[]. - -## Synopsis - -May be defined by a program before including -`` to enable runtime checks. - -## Description - -See cpp:default_registry[] for details. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc deleted file mode 100644 index 5578c295..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc +++ /dev/null @@ -1,46 +0,0 @@ - -# BOOST_OPENMETHOD_EXPORT_REGISTRY - -Declares a registry's state exported, in the module that owns it. - -## Synopsis - -[source,c++] ----- -BOOST_OPENMETHOD_EXPORT_REGISTRY(registry); ----- - -Used at namespace scope, after the registry's definition, in _every_ translation -unit of the module that owns the registry. Being a declaration it may be -repeated, so it belongs in the header those translation units share. - -## Description - -All of a registry's mutable state lives in a single variable (see -cpp:registry_state[]). Sharing a registry across modules means sharing that one -symbol, which takes three macros: the _owning_ module uses -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header its translation units share and -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -in exactly one of them; every _client_ module uses -xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY]. - -They exist to hide a platform incompatibility: on Windows, Cygwin and MinGW, -`__declspec(dllexport)` and `extern` are incompatible on an explicit -instantiation, while on ELF and Mach-O the visibility attribute must be on the -declaration and must not be repeated on the definition. See -xref:shared_libraries.adoc[Shared Libraries] for the full discussion, including -the required link setup. - -On ELF it emits an exported explicit instantiation _declaration_, which both -suppresses implicit instantiation and pins the symbol to default visibility. On -declspec platforms it expands to nothing, because there the export belongs on -the instantiation instead. - -WARNING: on ELF this macro is not decoration. A translation unit of the owning -module that uses neither it nor -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -instantiates the state implicitly, and under `-fvisibility=hidden` that copy is -module-local. Since ELF merges COMDATs at the _most restrictive_ visibility, the -merged symbol becomes local: the module builds, exports nothing, and clients -fail to link with an undefined reference to `registry_state<...>::st`. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ID.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ID.adoc deleted file mode 100644 index 57eb26a3..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_ID.adoc +++ /dev/null @@ -1,17 +0,0 @@ - -# BOOST_OPENMETHOD_ID - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_ID(ID) /* unspecified */ -``` - -## Description - -Generates a long, obfuscated name from a short name. All the other names -generated by macros are based on this name. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc deleted file mode 100644 index 77c00034..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc +++ /dev/null @@ -1,42 +0,0 @@ - -# BOOST_OPENMETHOD_IMPORT_REGISTRY - -Imports a registry's state from the module that owns it. - -## Synopsis - -[source,c++] ----- -BOOST_OPENMETHOD_IMPORT_REGISTRY(registry); ----- - -Used at namespace scope, after the registry's definition, in every translation -unit of every module that uses the registry without owning it. Being a -declaration it may be repeated, so it belongs in the header those modules share. - -## Description - -All of a registry's mutable state lives in a single variable (see -cpp:registry_state[]). Sharing a registry across modules means sharing that one -symbol, which takes three macros: the _owning_ module uses -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header its translation units share and -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -in exactly one of them; every _client_ module uses -xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY]. - -They exist to hide a platform incompatibility: on Windows, Cygwin and MinGW, -`__declspec(dllexport)` and `extern` are incompatible on an explicit -instantiation, while on ELF and Mach-O the visibility attribute must be on the -declaration and must not be repeated on the definition. See -xref:shared_libraries.adoc[Shared Libraries] for the full discussion, including -the required link setup. - -It emits an `extern template` declaration decorated with `BOOST_SYMBOL_IMPORT` -(`__declspec(dllimport)` on Windows, nothing on ELF). The declaration suppresses -the client's own instantiation, so it references the owner's symbol instead of -creating a private copy. - -The client module must be linked so the reference resolves: on Windows and macOS -by linking against the owning module; on ELF a dynamically loaded library may -also leave it for the dynamic linker to resolve at load time. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc deleted file mode 100644 index 97f5144b..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc +++ /dev/null @@ -1,17 +0,0 @@ -# BOOST_OPENMETHOD_INLINE_OVERRIDE - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_INLINE_OVERRIDE(ID, (PARAMETERS...), RETURN_TYPE) { - // body -} -``` - -## Description - -`BOOST_OPENMETHOD_INLINE_OVERRIDE` performs the same function as -xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE], except that the -overrider is marked `inline`. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc deleted file mode 100644 index 24fe1793..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc +++ /dev/null @@ -1,40 +0,0 @@ - -# BOOST_OPENMETHOD_INSTANTIATE_REGISTRY - -Instantiates a registry's state in the module that owns it. - -## Synopsis - -[source,c++] ----- -BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(registry); ----- - -Used at namespace scope, after the registry's definition, in _exactly one_ -translation unit of the module that owns the registry. It belongs in a `.cpp` -file, never in a header. - -## Description - -All of a registry's mutable state lives in a single variable (see -cpp:registry_state[]). Sharing a registry across modules means sharing that one -symbol, which takes three macros: the _owning_ module uses -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header its translation units share and -xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] -in exactly one of them; every _client_ module uses -xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY]. - -They exist to hide a platform incompatibility: on Windows, Cygwin and MinGW, -`__declspec(dllexport)` and `extern` are incompatible on an explicit -instantiation, while on ELF and Mach-O the visibility attribute must be on the -declaration and must not be repeated on the definition. See -xref:shared_libraries.adoc[Shared Libraries] for the full discussion, including -the required link setup. - -It emits the explicit instantiation _definition_ of the registry state, of which -a program may contain only one. On declspec platforms the definition carries the -`dllexport`; on ELF and Mach-O it carries no attribute, that having been -supplied by -xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] in -the header. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc deleted file mode 100644 index eb64930b..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDE.adoc +++ /dev/null @@ -1,87 +0,0 @@ - -# BOOST_OPENMETHOD_OVERRIDE - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_OVERRIDE(ID, (PARAMETERS...), RETURN_TYPE) { - // body -} -``` - -## Description - -`BOOST_OPENMETHOD_OVERRIDE` adds an overrider to a method. - -`ID` is the identifier of the method to which the overrider is added. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. - -`PARAMETERS` is a comma-separated list of types, possibly followed by parameter -names, just like in a function declaration. - -The macro tries to locate a method that can be called with the same argument -list as the overrider, possibly via argument dependent lookup. - -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. - -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 accessible unambiguous base. - -The following names are available inside the overrider's body: - -* `fn`: a pointer to a function, the overrider itself. Can be used for recursion. - -* `next`: a function with the same signature as the method (minus the -`virtual_<>` decorators). It forwards to the next most specialized overrider, if -it exists and it is unique. If the next overrider does not exist, or is -ambiguous, calling `next` reports a cpp:no_overrider[] or a cpp:ambiguous_call[] -and terminates the program. - -* `has_next()`: returns `true` if the next most specialized overrider exists. - -## Implementation Notes - -The macro creates additional entities in the current scope. - -* A class template declaration that acts as a container for the method's -overriders in the current scope: - -```c++ -template struct BOOST_OPENMETHOD_OVERRIDERS(NAME); -``` - -* A specialization of the container for the overrider: -+ --- -```c++ -struct BOOST_OPENMETHOD_OVERRIDERS(ID) { - static auto fn(PARAMETERS...) -> RETURN_TYPE; - static auto has_next() -> bool; - template - static auto next(typename... Args) -> RETURN_TYPE; -}; -``` - -[] - -* A xref:BOOST_OPENMETHOD_REGISTER.adoc[registrar] adding the overrider to the -method. - -* Finally, the macro starts the definition of the overrider function: --- -```c++ -auto BOOST_OPENMETHOD_OVERRIDERS(ID)::fn( - PARAMETERS...) -> RETURN_TYPE -``` --- - -{empty} - -The `{}` block following the call to the macro is the body of the function. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDER.adoc deleted file mode 100644 index fcedb6bd..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDER.adoc +++ /dev/null @@ -1,17 +0,0 @@ - -# BOOST_OPENMETHOD_OVERRIDER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_OVERRIDER(ID, (PARAMETERS...), RETURN_TYPE) -``` - -## Description - -Expands to the specialization of the class template that contains the overrider -for with the given name, parameter list and return type. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDERS.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDERS.adoc deleted file mode 100644 index 11d42c76..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_OVERRIDERS.adoc +++ /dev/null @@ -1,18 +0,0 @@ - -# BOOST_OPENMETHOD_OVERRIDERS - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -#define BOOST_OPENMETHOD_OVERRIDERS(ID) \ - BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _overriders) -``` - -## Description - -`BOOST_OPENMETHOD_OVERRIDERS` expands to the name of the class template that -contains the overriders for all the methods with a given name. - -NOTE: `ID` must be an *identifier*. Qualified names are not allowed. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc deleted file mode 100644 index 0f2d5adf..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_REGISTER.adoc +++ /dev/null @@ -1,17 +0,0 @@ - -# BOOST_OPENMETHOD_REGISTER - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_REGISTER(TYPE); -``` - -## Description - -Creates a registrar for `TYPE`, i.e. a static `TYPE` object with a unique -generated name. At static initialization time, the object adds itself to a list: -methods and class registrations add themselves to a cpp:registry[], and -overriders add themselves to a method's overrider list. diff --git a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc b/doc/modules/ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc deleted file mode 100644 index cc2789d8..00000000 --- a/doc/modules/ROOT/pages/BOOST_OPENMETHOD_TYPE.adoc +++ /dev/null @@ -1,14 +0,0 @@ -# BOOST_OPENMETHOD_TYPE - -## Synopsis - -Defined in link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[]. - -```c++ -BOOST_OPENMETHOD_TYPE(ID, (PARAMETERS...), RETURN_TYPE [, REGISTRY]); -``` - -## Description - -Expands to the core cpp:method[`method`] specialization created by -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] called with the same arguments. diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index b2781aee..0d50f66d 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -15,7 +15,7 @@ class that points to an instance of `Class`. `virtual_ptr` is defined in the lib `boost::openmethod`. To create an open-method that implements the `postfix` operation, we use the -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] macro: +xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] macro: ```c++ BOOST_OPENMETHOD( @@ -35,7 +35,7 @@ inline auto postfix(virtual_ptr node, std::ostream& os) -> void { ``` Before we can call the method, we need to define overriders. For that we use the -xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] macro: +xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] macro: [source,cpp] ---- @@ -70,7 +70,7 @@ There are two more things we need to do. OpenMethod is a library, not a compiler. It needs to be informed of all the classes that may be used as virtual parameters, and in method calls, and their inheritance relationships. We provide that information with the -xref:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] macro: +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] macro: [source,cpp] diff --git a/doc/modules/ROOT/pages/core_api.adoc b/doc/modules/ROOT/pages/core_api.adoc index 5da6129c..9d67461a 100644 --- a/doc/modules/ROOT/pages/core_api.adoc +++ b/doc/modules/ROOT/pages/core_api.adoc @@ -29,7 +29,7 @@ The exact name of the identifier class does not matter. The class needs not be defined, only declared. Inventing identifier class names can get tedious, so OpenMethod provides a macro -for that: xref:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID]. Let's use it: +for that: xref:reference:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID]. Let's use it: [source,c++] ---- @@ -38,7 +38,7 @@ include::{example}/core_api.cpp[tag=method] We said macro-free interface, but here is a macro again! Well, we are not forced to use the macro. There is a benefit though: it is used in the implementation of -high-level macros like xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD]. This makes +high-level macros like xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD]. This makes it possible to mix the two styles, for example to define a method using the macro, and add overriders using the core API. @@ -61,7 +61,7 @@ include::{example}/core_api.cpp[tag=variable_overrider] Once again we find ourselves inventing a name for a single use. Maybe some day C++ will get a Python-like `_` special variable. In the meantime, we can use another convenience macro: -xref:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER]. It takes a +xref:reference:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER]. It takes a class, and instantiates a static object with an obfuscated name: [source,c++] @@ -119,7 +119,7 @@ notation: include::{example}/core_api.cpp[tag=postfix_binary] ---- -Macro xref:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] takes the same +Macro xref:reference:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] takes the same parameters as `BOOST_OPENMETHOD`, and expands to the core cpp:method[method] instance. That is how we access its nested `overrider` class template: diff --git a/doc/modules/ROOT/pages/custom_rtti.adoc b/doc/modules/ROOT/pages/custom_rtti.adoc index 40a2e41c..610cf56e 100644 --- a/doc/modules/ROOT/pages/custom_rtti.adoc +++ b/doc/modules/ROOT/pages/custom_rtti.adoc @@ -124,7 +124,7 @@ include::{example}/1/custom_rtti.cpp[tag=registry] ---- Defining macro -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] sets the default registry used by all library components that need one. Next, we include the main header. diff --git a/doc/modules/ROOT/pages/headers.adoc b/doc/modules/ROOT/pages/headers.adoc index 86f50dd5..92802fff 100644 --- a/doc/modules/ROOT/pages/headers.adoc +++ b/doc/modules/ROOT/pages/headers.adoc @@ -57,14 +57,14 @@ include::{example}/2/roles.hpp[tag=content] ---- Unlike function declarations, -xref:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] +xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] cannot appear multiple times in a translation unit with the same arguments. Also, it requires the _method_ itself to be defined prior using this macro. Overriders are placed in _overrider_ _containers_. An overrider container is a class template named after the method, declared in the current namespace. It is specialized for each overrider signature. Macro -xref:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] takes the same +xref:reference:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] takes the same arguments `BOOST_OPENMETHOD_OVERRIDE`, and expands to the corresponding specialization of the overrider container. Containers have a static member function `fn` that contains the body of the overrider, provided by the user. We can @@ -81,7 +81,7 @@ OpenMethod does, it's `next`. It is almost always the right choice. The exception is: when performance is critical, we may want to inline the call to the base overrider. -xref:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] +xref:reference:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] defines the overrider as an inline function, and it can go in a header file: [source,c++] diff --git a/doc/modules/ROOT/pages/interop_any.adoc b/doc/modules/ROOT/pages/interop_any.adoc new file mode 100644 index 00000000..d11797f8 --- /dev/null +++ b/doc/modules/ROOT/pages/interop_any.adoc @@ -0,0 +1,170 @@ + +[#interop_any] +## Interoperation with `any` + +A value held in an `any` has a type that is not visible in the static type of +the variable holding the `any`. This section covers the constructs that let a +method look through the wrapper and dispatch on what is really inside. + +### `any` + +An `any` holds a value of almost any type, and remembers which type that is. +That is precisely what a method needs in order to pick an overrider. OpenMethod +can thus dispatch on the type _contained_ in an `any`, in effect treating a set +of otherwise unrelated types as a hierarchy rooted at `std::any`. The types need +not be polymorphic, and need not be related to one another - which makes this a +way of adding behavior to types we do not own, including built-in types. + +Support is provided by ``. It is not +included by ``, so it must be included explicitly. + +Dispatch works on classes known to a registry, so the types the `any` may +contain have to be registered. cpp:use_std_any_types[] does that, registering +`std::any` as a class, and each of the types as a class derived from it. A type +that is not registered cannot be dispatched on; a call with such a value in the +`any` is a cpp:missing_class[] error - see +xref:error_handling.adoc[Error Handling]. + +The `any` is then passed like any other virtual argument that is not a +`virtual_ptr`: wrapped in `virtual_`, as described in +xref:virtual_ptr_alt.adoc[Alternatives to virtual_ptr]. Overriders receive the +_contained_ value, by a reference of a compatible category. An overrider may +also take the `any` itself; since every registered type derives from it, such an +overrider is a catch-all, applying to any contained type that has no more +specific overrider: + +[source,c++] +---- +include::example$virtual_any.cpp[tag=content] +---- + +#### Mixing with ordinary virtual parameters + +An `any` virtual parameter is an ordinary virtual parameter that happens to +resolve through the contained type, so it composes with the others without +restriction. A multi-method can dispatch on an `any` and on a `virtual_ptr`, or +a plain reference, in the same call: + +```c++ +BOOST_OPENMETHOD( + meet, (virtual_, virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const Dog& dog, virtual_ptr), std::string) { + return dog.name + " meets a cat"; +} +``` + +#### Reference categories + +All three reference categories are supported, and they determine what the +overriders may take: + +[cols="1,2"] +|=== +| Method parameter | Overrider parameter + +| `virtual_` +| `const Dog&`, `Dog` + +| `virtual_` +| `Dog&`, `const Dog&`, `Dog` + +| `virtual_` +| `Dog&&`, `const Dog&`, `Dog` +|=== + +The mutable lvalue reference is the awkward one. +xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] locates +the method by checking that the overrider's parameters can be passed to the +method's forwarder, and `Dog&` does not convert to `std::any&`. A temporary +`std::any` binds to `const std::any&` and to `std::any&&`, which is why the +other two categories can use the macro; nothing binds to a mutable lvalue +reference. Those overriders are registered with the core API instead - the +primitive the macro itself expands to: + +```c++ +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +``` + +#### `virtual_std_any` + +Every call above looks the v-table up in a hash table, keyed on the type the +`any` contains. cpp:virtual_std_any[] - an alias for `virtual_any` - +removes that cost: it bundles an `any` with the v-table pointer for the value +inside it, acquiring it once, on construction, and maintaining it across +assignment and `emplace`. It is to an `any` what cpp:virtual_ptr[] is to a +pointer, except that it _owns_ the object: the `any` is held by value. + +The pointer comes from a lookup when the `virtual_std_any` is built from an +existing `any`, and from a static variable - no lookup at all - when it is built +from a value, or by `emplace`, since the type is then known at compile time. + +That makes it worthwhile when the same value is dispatched on repeatedly. Its +usefulness is limited, though, by the fact that the wrapper is not what an +overrider receives: an overrider takes the contained value, as before, so it +cannot pass the `virtual_std_any` on to another method and save the lookup +there. Only a catch-all overrider, which takes `const virtual_std_any&`, gets +it. + +A `virtual_std_any` method parameter must be a reference - passing it by value +would copy the `any`, and the value inside it, on every call. The three +categories, and the limitation on the mutable one, are as above. + +For the same reason that a `virtual_std_any` caches what a plain `any` does not, +cpp:final_virtual_ptr[] is _deleted_ for `std::any`: it would silently produce +the v-table of the `any` root class rather than the one for the contained value. + +#### `virtual_any_ref` + +`virtual_std_any` owns its `any`. cpp:virtual_any_ref[] is its non-owning +counterpart: it _borrows_ an `any` that lives elsewhere, bundling its address +with the v-table pointer for the value inside it - acquired once, when the +handle is created, or taken at no cost from a `virtual_any`. It is a cheap, +two-word handle with pointer semantics. Unlike the owning wrapper, it is passed +to methods _by value_, like the reference-wrapper flavors of Boost.TypeErasure's +`any`: + +```c++ +BOOST_OPENMETHOD(poke, (virtual_any_ref), std::string); + +std::any spot_any = Dog{"Spot"}; +virtual_any_ref spot = spot_any; // one lookup + +poke(spot); // no lookup +poke(spot); // no lookup; mutations reach spot_any +``` + +`Any` may be const-qualified: through `virtual_any_ref`, +overriders receive the contained value by value or by const reference only. A +mutable handle converts to a const one. + +A plain value does not convert to a `virtual_any_ref` - there is no `any` for +the handle to borrow - so `BOOST_OPENMETHOD_OVERRIDE`, which locates the method +by convertibility, cannot register overriders that take the contained value. +Register them with the core API instead, as in the `virtual_` case +above; the catch-all overrider, which takes the handle itself, can use the +macro. + +The handle does not track its referent: if the value inside the `any` is +replaced, the handle is stale - like an iterator into a modified container - +and must be re-created. + +#### `boost::any` + +`boost::any` is supported as well, by +``, with cpp:use_boost_any_types[] and +cpp:virtual_boost_any[] - the exact counterparts of the constructs above. The two root classes are distinct, so +`std::any` and `boost::any` may be used in the same program, and with the same +registry. + +cpp:virtual_any[] itself is generic: it can serve any type with an `any`-like +interface, given cpp:virtual_traits[] specializations for its reference types. diff --git a/doc/modules/ROOT/pages/namespaces.adoc b/doc/modules/ROOT/pages/namespaces.adoc index 28a15cb1..39e6ab20 100644 --- a/doc/modules/ROOT/pages/namespaces.adoc +++ b/doc/modules/ROOT/pages/namespaces.adoc @@ -5,8 +5,8 @@ Note;; This section uses overrider containers, described in the xref:headers.adoc[Headers and Implementation Files] section. -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] defines a method in the current -namespace. xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] works +xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] defines a method in the current +namespace. xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] works _across_ namespaces. Overriders are not required to be in the same namespace as the method they override. The macro adds the overrider to a method that can be called with the same arguments as the overrider, possibly located via argument diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 0c89651a..07309b7a 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -1,6 +1,13 @@ [#ref_headers] = xref:ref_headers.adoc[Headers] +// The links to the headers go to GitHub, via `headers-url` in antora.yml. A +// relative path would be nicer -- it would follow the deployment -- but it +// cannot work: the PR previews publish libs/openmethod/doc alone, and +// boost.org serves the headers from doc/libs//boost, not from +// libs/openmethod/include. They must also be `link:`, not `xref:` -- Antora +// resolves an `xref:` target as a resource id, and rejects an absolute url. + {empty} ## Headers for General Use @@ -25,14 +32,14 @@ parameters: ## High-level Headers [#core] -### link:{{BASE_URL}}/include/boost/openmethod/core.hpp[] +### link:{headers-url}/boost/openmethod/core.hpp[] Defines the main constructs of the library: methods, overriders and virtual pointers, and mechanisms to implement them. Does not define any public macros apart from `BOOST_OPENMETHOD_DEFAULT_REGISTRY`, if it is not defined already. [#macros] -### link:{{BASE_URL}}/include/boost/openmethod/macros.hpp[] +### link:{headers-url}/boost/openmethod/macros.hpp[] Defines the public macros of the library, such as `BOOST_OPENMETHOD`, `BOOST_OPENMETHOD_CLASSES`, etc. @@ -41,12 +48,12 @@ There is little point in including this header directly, as this has the same effect as including `boost/openmethod.hpp`, which is shorter. [#openmethod] -### link:{{BASE_URL}}/include/boost/openmethod.hpp[] +### link:{headers-url}/boost/openmethod.hpp[] Includes `core.hpp` and `macros.hpp`. [#initialize] -### link:{{BASE_URL}}/include/boost/openmethod/initialize.hpp[] +### link:{headers-url}/boost/openmethod/initialize.hpp[] Provides the cpp:initialize[] and cpp:finalize[] functions. This header is typically included in the translation unit containing `main`. Translation units @@ -54,77 +61,97 @@ that dynamically load or unload shared libraries may also need to call those functions. [#std_shared_ptr] -### link:{{BASE_URL}}/include/boost/openmethod/interop/std_shared_ptr.hpp[] +### link:{headers-url}/boost/openmethod/interop/std_shared_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::shared_ptr` in place of a raw pointer or reference in virtual parameters. [#std_unique_ptr] -### link:{{BASE_URL}}/include/boost/openmethod/interop/std_unique_ptr.hpp[] +### link:{headers-url}/boost/openmethod/interop/std_unique_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `std::unique_ptr` in place of a raw pointer or reference in virtual parameters. [#boost_intrusive_ptr] -### link:{{BASE_URL}}/include/boost/openmethod/interop/boost_intrusive_ptr.hpp[] +### link:{headers-url}/boost/openmethod/interop/boost_intrusive_ptr.hpp[] Provides a `virtual_traits` specialization that makes it possible to use a `boost::intrusive_ptr` in place of a raw pointer or reference in virtual parameters. +[#virtual_any] +### link:{headers-url}/boost/openmethod/interop/virtual_any.hpp[] + +Provides `virtual_any`, a wide `any` that combines an `any`, held by value, with +a pointer to the v-table for the contained value - similar to `virtual_ptr`. +Also provides `virtual_any_ref`, a non-owning counterpart that borrows an +existing `any`. + +[#std_any] +### link:{headers-url}/boost/openmethod/interop/std_any.hpp[] + +Provides `virtual_traits` specializations that make it possible to use a +`std::any` in virtual parameters. + +[#boost_any] +### link:{headers-url}/boost/openmethod/interop/boost_any.hpp[] + +Provides `virtual_traits` specializations that make it possible to use a +`boost::any` in virtual parameters. + *The headers below are for advanced use*. ## Pre-Core Headers The following headers can be included before `core.hpp` to define custom registries and policies, and override the default registry by defining -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. -### link:{{BASE_URL}}/include/boost/openmethod/preamble.hpp[] +### link:{headers-url}/boost/openmethod/preamble.hpp[] Defines `registry` and stock policy categories. Also defines all types and functions necessary for the definition of `registry`. -### link:{{BASE_URL}}/include/boost/openmethod/policies/std_rtti.hpp[] +### link:{headers-url}/boost/openmethod/policies/std_rtti.hpp[] Provides an implementation of the `rtti` policy using standard RTTI. -### link:{{BASE_URL}}/include/boost/openmethod/policies/fast_perfect_hash.hpp[] +### link:{headers-url}/boost/openmethod/policies/fast_perfect_hash.hpp[] Provides an implementation of the `hash` policy using a fast perfect hash function. -### link:{{BASE_URL}}/include/boost/openmethod/policies/vptr_vector.hpp[] +### link:{headers-url}/boost/openmethod/policies/vptr_vector.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a `std::vector` indexed by type ids, possibly hashed. -### link:{{BASE_URL}}/include/boost/openmethod/policies/default_error_handler.hpp[] +### link:{headers-url}/boost/openmethod/policies/default_error_handler.hpp[] Provides an implementation of the `error_handler` policy that calls a `std::function` when an error is encountered, and before the library aborts the program. -### link:{{BASE_URL}}/include/boost/openmethod/policies/stderr_output.hpp[] +### link:{headers-url}/boost/openmethod/policies/stderr_output.hpp[] Provides an implementation of the `output` policy that writes diagnostics to the C standard error stream (not using iostreams). -### link:{{BASE_URL}}/include/boost/openmethod/default_registry.hpp[] +### link:{headers-url}/boost/openmethod/default_registry.hpp[] Defines the default registry, which contains all the stock policies listed above. Includes all the headers listed in this section so far. -### link:{{BASE_URL}}/include/boost/openmethod/policies/static_rtti.hpp[] +### link:{headers-url}/boost/openmethod/policies/static_rtti.hpp[] Provides a minimal implementation of the `rtti` policy that does not depend on standard RTTI. -### link:{{BASE_URL}}/include/boost/openmethod/policies/throw_error_handler.hpp[] +### link:{headers-url}/boost/openmethod/policies/throw_error_handler.hpp[] Provides an implementation of the `error_handler` policy that throws errors as exceptions. -### link:{{BASE_URL}}/include/boost/openmethod/policies/vptr_map.hpp[] +### link:{headers-url}/boost/openmethod/policies/vptr_map.hpp[] Provides an implementation of the `vptr` policy that stores the v-table pointers in a map (by default a `std::map`) indexed by type ids. diff --git a/doc/modules/ROOT/pages/ref_macros.adoc b/doc/modules/ROOT/pages/ref_macros.adoc index 71852cba..c3250ee9 100644 --- a/doc/modules/ROOT/pages/ref_macros.adoc +++ b/doc/modules/ROOT/pages/ref_macros.adoc @@ -8,19 +8,19 @@ uses of the library. |=== | Name | Description. -| xref:BOOST_OPENMETHOD_CLASSES.adoc[*BOOST_OPENMETHOD_CLASSES*] -| Registers classes. -| xref:BOOST_OPENMETHOD.adoc[*BOOST_OPENMETHOD*] +| xref:reference:BOOST_OPENMETHOD.adoc[*BOOST_OPENMETHOD*] | Declares a method. -| xref:BOOST_OPENMETHOD_OVERRIDE.adoc[*BOOST_OPENMETHOD_OVERRIDE*] +| xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc[*BOOST_OPENMETHOD_OVERRIDE*] | Adds an overrider to a method. -| xref:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] +| xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[*BOOST_OPENMETHOD_CLASSES*] +| Registers classes. +| xref:reference:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] | Adds an overrider to a method as an inline function. -| xref:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] +| xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] | Declares a method overrider. -| xref:BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc[BOOST_OPENMETHOD_DEFINE_OVERRIDER] +| xref:reference:BOOST_OPENMETHOD_DEFINE_OVERRIDER.adoc[BOOST_OPENMETHOD_DEFINE_OVERRIDER] | Defines the body of a method overrider. -| xref:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] +| xref:reference:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] | Enables runtime checks in method calls. |=== @@ -31,22 +31,22 @@ The following macros are for advanced uses of the library. |=== | Name | Description. -| xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] | Default registry. -| xref:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] +| xref:reference:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] | Returns the class template specialization containing an overrider. -| xref:BOOST_OPENMETHOD_OVERRIDERS.adoc[BOOST_OPENMETHOD_OVERRIDERS] +| xref:reference:BOOST_OPENMETHOD_OVERRIDERS.adoc[BOOST_OPENMETHOD_OVERRIDERS] | Returns the class template containing the overriders for all the methods with a given name. -| xref:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID] +| xref:reference:BOOST_OPENMETHOD_ID.adoc[BOOST_OPENMETHOD_ID] | Generates a method id. -| xref:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] +| xref:reference:BOOST_OPENMETHOD_TYPE.adoc[BOOST_OPENMETHOD_TYPE] | Expands to core `method` specialization. -| xref:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER] +| xref:reference:BOOST_OPENMETHOD_REGISTER.adoc[BOOST_OPENMETHOD_REGISTER] | Creates a registrar object. -| xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] | Imports a registry's state from the module that owns it. -| xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] | Declares a registry's state exported, in every translation unit of the owning module. -| xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] | Instantiates a registry's state, in exactly one translation unit of the owning module. |=== diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index 89101a45..928f5088 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -6,11 +6,11 @@ same registry. If a class is used as a virtual parameter in methods using different registries, it must be registered with each of them. Class templates cpp:use_classes[], cpp:method[], cpp:virtual_ptr[], and macros -xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] and -xref:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], take an additional +xref:reference:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] and +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES], take an additional argument, a cpp:registry[] class, which defaults to cpp:default_registry[]. The default registry can be overridden by defining the macroprocessor symbol -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] _before_ including ``. The value of the symbol is used as a default template parameter for `use_classes`, `method`, `virtual_ptr`, and others. Once the `core` header has been included, changing @@ -34,14 +34,14 @@ Policies are placed in the cpp:boost::openmethod::policies[] namespace. | std_rtti | provides type information for classes and objects -| vptr -| vptr_vector -| stores vptrs in an indexed collection - | type_hash | fast_perfect_hash | hashes type id to an index in a vector +| vptr +| vptr_vector +| stores vptrs in an indexed collection + | error_handler | default_error_handler | calls an overridable handler function @@ -53,7 +53,7 @@ Policies are placed in the cpp:boost::openmethod::policies[] namespace. |=== if -xref:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] +xref:reference:BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS.adoc[BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS] is defined, `default_registry` also contains the `runtime_checks` policy. This enables extra validations during method dispatch, which can detect missing class registrations that could not be caught by `initialize`. @@ -87,6 +87,28 @@ 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. +That class is a convenience, not the registry's identity. Everything a registry +owns - the class and method lists, the dispatch tables, the state of every +stateful policy - is keyed on the `registry<...>` specialization the class +derives from, which is what its `registry_type` member aliases. Two classes +built from the same policies, in the same order, are therefore the _same_ +registry, and share everything: + +[source,c++] +---- +include::example$registry_identity.cpp[tag=shared] +---- + +This is worth watching for when the purpose of a second registry is to isolate a +set of methods from another, since such a registry would naturally be given the +same policies as the first. Registering a class or a method in either would then +register it in both. To keep them apart, give each one a policy of its own: + +[source,c++] +---- +include::example$registry_identity.cpp[tag=distinct] +---- + 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 diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index 507581d1..97da9bbf 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -2,8 +2,8 @@ [#shared_libraries] -This section discusses how OpenMethod interoperates with shared libraries on -Linux, other POSIX-like platforms, and Windows. +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 @@ -21,13 +21,13 @@ Each takes the registry as an argument, so they can be used to manage |=== | Macro | Where -| xref:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_IMPORT_REGISTRY.adoc[BOOST_OPENMETHOD_IMPORT_REGISTRY] | header; every translation unit of a _client_ module -| xref:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_EXPORT_REGISTRY.adoc[BOOST_OPENMETHOD_EXPORT_REGISTRY] | header; every translation unit of the _owning_ module -| xref:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] +| xref:reference:BOOST_OPENMETHOD_INSTANTIATE_REGISTRY.adoc[BOOST_OPENMETHOD_INSTANTIATE_REGISTRY] | exactly one `.cpp` of the owning module |=== @@ -240,7 +240,7 @@ registry that contains the cpp:indirect_vptr[] policy. `` provides an cpp:indirect_registry[] that has the same policies as `default_registry`, plus `indirect_vptr`. Make it the registry the `BOOST_OPENMETHOD` macros use by defining -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] +xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY] _before_ including ``. The `indirect_vptr` example does that in the header both modules share, rather diff --git a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc index 2c936031..eb5886f8 100644 --- a/doc/modules/ROOT/pages/virtual_ptr_alt.adoc +++ b/doc/modules/ROOT/pages/virtual_ptr_alt.adoc @@ -102,3 +102,7 @@ v-table for the bases, just like what C++ does for its native vptrs. `inplace_vptr_base` and `inplace_vptr_derived` are aliased in `namespace boost::openmethod::aliases`. + +An object that embeds its v-table pointer does not need to be wrapped in a +`virtual_ptr` - the two fill the same goal, fast access to the v-table +pointer - and wrapping one is rejected at compile time. diff --git a/doc/modules/ROOT/snippets/CMakeLists.txt b/doc/modules/ROOT/snippets/CMakeLists.txt new file mode 100644 index 00000000..f3558474 --- /dev/null +++ b/doc/modules/ROOT/snippets/CMakeLists.txt @@ -0,0 +1,29 @@ +# 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) + +# Sources for the `include:` markers in the reference doc comments; see +# doc/mrdocs-addons/extensions/include.lua. Built and run alongside the +# examples, which is the whole point: a reference example cannot drift from the +# library without this failing. +# +# Targets carry a `snippet_` prefix because a snippet and an example may share +# a stem -- both directories have a virtual_ptr.cpp. + +message(STATUS "Boost.OpenMethod: building documentation snippets") + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions(BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS) +endif() + +file(GLOB cpp_files "*.cpp") + +foreach (cpp ${cpp_files}) + get_filename_component(stem ${cpp} NAME_WE) + set(test_target "boost_openmethod-snippet_${stem}") + add_executable(${test_target} ${cpp}) + target_link_libraries(${test_target} PRIVATE Boost::openmethod Boost::unit_test_framework) + add_test(NAME ${test_target} COMMAND ${test_target}) + add_dependencies(tests ${test_target}) +endforeach() diff --git a/doc/modules/ROOT/snippets/capture.hpp b/doc/modules/ROOT/snippets/capture.hpp new file mode 100644 index 00000000..f48f1269 --- /dev/null +++ b/doc/modules/ROOT/snippets/capture.hpp @@ -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) + +// Harness for the snippets in this directory, never part of a tagged region: +// the reference pages show what a program would write, and the capture lets the +// test check that it wrote it. +// +// Note that the library's own `output` policy writes to the C `stderr` stream, +// which a streambuf redirect cannot intercept; only what an example prints +// itself is captured. + +#ifndef BOOST_OPENMETHOD_SNIPPETS_CAPTURE_HPP +#define BOOST_OPENMETHOD_SNIPPETS_CAPTURE_HPP + +#include +#include +#include + +// Redirects a standard stream for the duration of a scope. +template +struct capture_stream { + std::ostringstream captured; + std::streambuf* previous = Stream->rdbuf(captured.rdbuf()); + + ~capture_stream() { + Stream->rdbuf(previous); + } + + auto str() const -> std::string { + return captured.str(); + } +}; + +using capture_cout = capture_stream<&std::cout>; +using capture_cerr = capture_stream<&std::cerr>; + +#endif diff --git a/doc/modules/ROOT/snippets/error_harness.hpp b/doc/modules/ROOT/snippets/error_harness.hpp new file mode 100644 index 00000000..1cd27074 --- /dev/null +++ b/doc/modules/ROOT/snippets/error_harness.hpp @@ -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) + +// Harness for the error snippets, never part of a tagged region: the reference +// pages show the mistake and the operation that reports it, and nothing else. +// +// Each of those snippets lives in a translation unit of its own, so that one +// deliberate mistake cannot affect another and the examples can use the default +// registry -- which is what keeps a registry argument out of every line. + +#ifndef BOOST_OPENMETHOD_SNIPPETS_ERROR_HARNESS_HPP +#define BOOST_OPENMETHOD_SNIPPETS_ERROR_HARNESS_HPP + +#include +#include + +#include "capture.hpp" + +// Thrown only to unwind out of an example: the library calls `abort` as soon as +// the error handler returns, and a handler may prevent that only by throwing. +struct reported {}; + +// Reports the error the way the default handler does, but on std::cerr. The +// `output` policy writes to the C `stderr` stream, which a streambuf redirect +// cannot intercept, so `capture_cerr` would see nothing otherwise. +template +auto report_on_cerr() -> void { + Registry::error_handler::set([](const auto& error) { + std::visit( + [](auto&& e) { e.template write(std::cerr); }, error); + std::cerr << "\n"; + throw reported{}; + }); +} + +// Runs `f`, swallowing the unwind that `report_on_cerr`'s handler throws. +template +auto reporting(F&& f) -> void { + try { + f(); + } catch (const reported&) { + } +} + +#endif diff --git a/doc/modules/ROOT/snippets/errors_missing_base.cpp b/doc/modules/ROOT/snippets/errors_missing_base.cpp new file mode 100644 index 00000000..c9484d71 --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_base.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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; + +// tag::classes[] +// registered separately, so the inheritance is never seen +BOOST_OPENMETHOD_CLASSES(Animal); +BOOST_OPENMETHOD_CLASSES(Dog); + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_base_error) { + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::init[] + // aborts with error message: missing base Animal -<| Dog + initialize(); + // end::init[] + }); + + BOOST_TEST(cerr.str().find("missing base") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/errors_missing_class_call.cpp b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp new file mode 100644 index 00000000..13195809 --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp @@ -0,0 +1,57 @@ +// 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) + +// The class missing from a *call* is caught by the `runtime_checks` policy, +// which `default_registry` carries only when this symbol is defined. +#define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS + +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +// The registration below is also the `fix` example on the missing_base page, +// hence the nested tag. +// tag::classes[] +// Bulldog is missing +// tag::fix[] +BOOST_OPENMETHOD_CLASSES(Animal, Dog); +// end::fix[] + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_class_in_call) { + initialize(); + + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::use[] + Bulldog hector; + + // aborts with error message: unknown class Bulldog + poke(hector); + // end::use[] + }); + + BOOST_TEST(cerr.str().find("Bulldog") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/errors_missing_class_method.cpp b/doc/modules/ROOT/snippets/errors_missing_class_method.cpp new file mode 100644 index 00000000..4dcf6886 --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_class_method.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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; + +// tag::classes[] +BOOST_OPENMETHOD_CLASSES(Dog); // Animal is missing + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_class_in_method) { + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::init[] + // aborts with error message: unknown class Animal + initialize(); + // end::init[] + }); + + BOOST_TEST(cerr.str().find("Animal") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp b/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp new file mode 100644 index 00000000..11027c18 --- /dev/null +++ b/doc/modules/ROOT/snippets/errors_missing_class_overrider.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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "error_harness.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; + +// tag::classes[] +BOOST_OPENMETHOD_CLASSES(Animal); // Dog is missing + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { + // ... +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(missing_class_in_overrider) { + capture_cerr cerr; + report_on_cerr(); + + reporting([] { + // tag::init[] + // aborts with error message: unknown class Dog + initialize(); + // end::init[] + }); + + BOOST_TEST(cerr.str().find("Dog") != std::string::npos); +} diff --git a/doc/modules/ROOT/snippets/initialize.cpp b/doc/modules/ROOT/snippets/initialize.cpp new file mode 100644 index 00000000..c51ad1d1 --- /dev/null +++ b/doc/modules/ROOT/snippets/initialize.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 openmethod +#include + +#include "capture.hpp" + +namespace bom = boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +BOOST_OPENMETHOD(trick, (bom::virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(trick, (bom::virtual_ptr), std::string) { + return "stare"; +} + +BOOST_OPENMETHOD_OVERRIDE(trick, (bom::virtual_ptr), std::string) { + return "spin"; +} + +BOOST_AUTO_TEST_CASE(initialize_report) { + capture_cerr cerr; + +// The example ends with `exit(1)`, as a program would; make it expand to +// nothing so that the test can carry on. +#define exit(code) + + // tag::report[] + auto report = bom::initialize(bom::trace::from_env()).report; + + if (report.not_implemented != 0 || report.ambiguous) { + std::cerr << "some methods are ambiguous or not implemented for " + "some combinations of virtual arguments\n" + "set BOOST_OPENMETHOD_TRACE=1 to troubleshoot\n"; + exit(1); + } + // end::report[] + +#undef exit + + BOOST_TEST(report.not_implemented == 0); + BOOST_TEST(report.ambiguous == 0); + BOOST_TEST(cerr.str().empty()); + + Dog snoopy; + BOOST_TEST(trick(bom::virtual_ptr(snoopy)) == "spin"); +} diff --git a/doc/modules/ROOT/snippets/inplace_vptr.cpp b/doc/modules/ROOT/snippets/inplace_vptr.cpp new file mode 100644 index 00000000..df139fdc --- /dev/null +++ b/doc/modules/ROOT/snippets/inplace_vptr.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 openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +// tag::classes[] +struct Animal : inplace_vptr_base {}; + +struct Cat : Animal, inplace_vptr_derived {}; + +struct Dog : Animal, inplace_vptr_derived {}; + +BOOST_OPENMETHOD(trick, (virtual_ animal), std::string); + +BOOST_OPENMETHOD_OVERRIDE(trick, (Cat&), std::string) { + return "sulk"; +} + +BOOST_OPENMETHOD_OVERRIDE(trick, (Dog&), std::string) { + return "spin"; +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(inplace_vptr_examples) { + capture_cout cout; + + // tag::dispatch[] + initialize(); + + std::unique_ptr a = std::make_unique(); + std::unique_ptr b = std::make_unique(); + + std::cout << trick(*a) << "\n"; // sulk + std::cout << trick(*b) << "\n"; // spin + // end::dispatch[] + + BOOST_TEST(cout.str() == "sulk\nspin\n"); +} diff --git a/doc/modules/ROOT/snippets/intrusive_ptr.cpp b/doc/modules/ROOT/snippets/intrusive_ptr.cpp new file mode 100644 index 00000000..409a7ae1 --- /dev/null +++ b/doc/modules/ROOT/snippets/intrusive_ptr.cpp @@ -0,0 +1,138 @@ +// 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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +// tag::classes[] +struct Animal : boost::intrusive_ref_counter { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +// end::classes[] + +namespace by_value { + +// tag::by_value[] +BOOST_OPENMETHOD(poke, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost::intrusive_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost::intrusive_ptr animal), std::string) { + return "hiss"; +} +// end::by_value[] + +} // namespace by_value + +namespace by_reference { + +// tag::by_reference[] +BOOST_OPENMETHOD( + poke, (virtual_&>), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const boost::intrusive_ptr& animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const boost::intrusive_ptr& animal), std::string) { + return "hiss"; +} +// end::by_reference[] + +} // namespace by_reference + +namespace vptr { + +BOOST_OPENMETHOD(poke, (boost_intrusive_virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost_intrusive_virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (boost_intrusive_virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace vptr + +BOOST_AUTO_TEST_CASE(intrusive_ptr_examples) { + initialize(); + + { + using namespace vptr; + capture_cout cout; + + // tag::make_boost_intrusive_virtual[] + boost_intrusive_virtual_ptr animal = + make_boost_intrusive_virtual(); + + std::cout << poke(animal) << "\n"; // bark + // end::make_boost_intrusive_virtual[] + + BOOST_TEST(cout.str() == "bark\n"); + } + + { + // tag::boost_intrusive_virtual_ptr_alias[] + boost_intrusive_virtual_ptr animal = + make_boost_intrusive_virtual(); + boost::intrusive_ptr owner = animal.pointer(); + + BOOST_TEST(owner->use_count() == 2); + // end::boost_intrusive_virtual_ptr_alias[] + } + + { + using namespace by_value; + capture_cout cout; + + // tag::by_value_call[] + std::cout << poke(boost::intrusive_ptr(new Dog)) + << "\n"; // bark + std::cout << poke(boost::intrusive_ptr(new Cat)) + << "\n"; // hiss + // end::by_value_call[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); + } + + { + using namespace by_reference; + capture_cout cout; + + // tag::by_reference_call[] + const boost::intrusive_ptr snoopy(new Dog); + + std::cout << poke(snoopy) << "\n"; // bark + + BOOST_TEST(snoopy->use_count() == 1); + // end::by_reference_call[] + + BOOST_TEST(cout.str() == "bark\n"); + } +} diff --git a/doc/modules/ROOT/snippets/macros.cpp b/doc/modules/ROOT/snippets/macros.cpp new file mode 100644 index 00000000..3af63dc0 --- /dev/null +++ b/doc/modules/ROOT/snippets/macros.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 +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +// tag::declare[] +BOOST_OPENMETHOD(poke, (virtual_ptr animal, std::ostream& os), void); +// end::declare[] + +// tag::override[] +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << "hiss"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << "bark"; +} +// end::override[] + +BOOST_AUTO_TEST_CASE(macro_examples) { + initialize(); + + capture_cout cout; + + // tag::call[] + Cat felix; + Animal& a = felix; + Dog snoopy; + Animal& b = snoopy; + + poke(a, std::cout); // hiss + poke(b, std::cout); // bark + // end::call[] + + BOOST_TEST(cout.str() == "hissbark"); +} diff --git a/doc/modules/ROOT/snippets/policies.cpp b/doc/modules/ROOT/snippets/policies.cpp new file mode 100644 index 00000000..e67f3371 --- /dev/null +++ b/doc/modules/ROOT/snippets/policies.cpp @@ -0,0 +1,282 @@ +// 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 + +#define BOOST_TEST_MODULE openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +// The registries below each get their own copy of the classes and of `trick`. +// Only `Dog` has an overrider, so calling `trick` on a `Cat` reaches the +// registry's error handler. + +namespace std_rtti_demo { + +// tag::std_rtti[] +struct dynamic_registry : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector> {}; +// end::std_rtti[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, dynamic_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + dynamic_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace std_rtti_demo + +namespace vptr_vector_demo { + +// tag::vptr_vector[] +// `fast_perfect_hash` turns the type ids into small indices; without it the +// vector is indexed by the type id itself, which `std_rtti` makes a pointer +struct vector_registry : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector> {}; +// end::vptr_vector[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, vector_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + vector_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace vptr_vector_demo + +namespace vptr_map_demo { + +// tag::vptr_map[] +struct map_registry : registry> {}; +// end::vptr_map[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, map_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, map_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace vptr_map_demo + +namespace fast_perfect_hash_demo { + +// tag::fast_perfect_hash[] +// `vptr_vector` indexes by the type id unless a `type_hash` policy maps it to +// a small integer first. With `std_rtti`, where a type id is a pointer, that +// makes the difference between a vector of a few entries and one that cannot +// be allocated at all. +struct hashed_registry : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector> {}; +// end::fast_perfect_hash[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, hashed_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + hashed_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace fast_perfect_hash_demo + +namespace stderr_output_demo { + +// tag::stderr_output[] +struct noisy_registry + : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::default_error_handler, + policies::stderr_output> {}; +// end::stderr_output[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, noisy_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, noisy_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace stderr_output_demo + +namespace default_error_handler_demo { + +// tag::default_error_handler_registry[] +struct handled_registry + : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::default_error_handler, + policies::stderr_output> {}; +// end::default_error_handler_registry[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, handled_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + handled_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace default_error_handler_demo + +namespace throw_error_handler_demo { + +// tag::throw_error_handler_registry[] +struct throwing_registry + : registry< + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::throw_error_handler> {}; +// end::throw_error_handler_registry[] + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, throwing_registry); + +BOOST_OPENMETHOD( + trick, (virtual_ptr), std::string, + throwing_registry); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (virtual_ptr), std::string) { + return "spin"; +} + +} // namespace throw_error_handler_demo + +BOOST_AUTO_TEST_CASE(rtti_and_storage) { + { + using namespace std_rtti_demo; + initialize(); + capture_cout cout; + + // tag::std_rtti_dispatch[] + Dog snoopy; + Animal& animal = snoopy; + + std::cout << trick(virtual_ptr(animal)) + << "\n"; // spin + // end::std_rtti_dispatch[] + + BOOST_TEST(cout.str() == "spin\n"); + } + + { + using namespace vptr_vector_demo; + initialize(); + + Dog snoopy; + BOOST_TEST( + trick(virtual_ptr(snoopy)) == "spin"); + } + + { + using namespace vptr_map_demo; + initialize(); + + Dog snoopy; + BOOST_TEST(trick(virtual_ptr(snoopy)) == "spin"); + } + + { + using namespace fast_perfect_hash_demo; + initialize(); + + Dog snoopy; + BOOST_TEST( + trick(virtual_ptr(snoopy)) == "spin"); + } + + { + using namespace stderr_output_demo; + initialize(); + + Dog snoopy; + BOOST_TEST( + trick(virtual_ptr(snoopy)) == "spin"); + } +} + +BOOST_AUTO_TEST_CASE(error_handlers) { + { + using namespace default_error_handler_demo; + initialize(); + + capture_cerr cerr; + + // tag::default_error_handler_set[] + handled_registry::error_handler::set([](const auto& error) { + if (std::holds_alternative(error)) { + throw std::runtime_error("not implemented"); + } + }); + + Cat felix; + + try { + trick(virtual_ptr(felix)); + } catch (const std::runtime_error& error) { + std::cerr << error.what() << "\n"; // not implemented + } + // end::default_error_handler_set[] + + BOOST_TEST(cerr.str() == "not implemented\n"); + } + + { + using namespace throw_error_handler_demo; + initialize(); + + capture_cerr cerr; + + // tag::throw_error_handler_catch[] + Cat felix; + + try { + trick(virtual_ptr(felix)); + } catch (const no_overrider&) { + std::cerr << "no overrider for Cat\n"; + } + // end::throw_error_handler_catch[] + + BOOST_TEST(cerr.str() == "no overrider for Cat\n"); + } +} diff --git a/doc/modules/ROOT/snippets/smart_pointers.cpp b/doc/modules/ROOT/snippets/smart_pointers.cpp new file mode 100644 index 00000000..b4fbd908 --- /dev/null +++ b/doc/modules/ROOT/snippets/smart_pointers.cpp @@ -0,0 +1,196 @@ +// 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 openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +// tag::classes[] +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +// end::classes[] + +namespace by_value { + +// tag::shared_by_value[] +BOOST_OPENMETHOD(poke, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::shared_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::shared_ptr animal), std::string) { + return "hiss"; +} +// end::shared_by_value[] + +} // namespace by_value + +namespace by_reference { + +// tag::shared_by_reference[] +BOOST_OPENMETHOD(poke, (virtual_&>), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const std::shared_ptr& animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const std::shared_ptr& animal), std::string) { + return "hiss"; +} +// end::shared_by_reference[] + +} // namespace by_reference + +namespace unique { + +// tag::unique_by_value[] +BOOST_OPENMETHOD(poke, (virtual_>), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::unique_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::unique_ptr animal), std::string) { + return "hiss"; +} +// end::unique_by_value[] + +} // namespace unique + +namespace shared_vptr { + +BOOST_OPENMETHOD(poke, (shared_virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (shared_virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (shared_virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace shared_vptr + +namespace unique_vptr { + +BOOST_OPENMETHOD(poke, (unique_virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (unique_virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (unique_virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace unique_vptr + +BOOST_AUTO_TEST_CASE(shared_ptr_examples) { + initialize(); + + { + using namespace shared_vptr; + capture_cout cout; + + // tag::make_shared_virtual[] + shared_virtual_ptr animal = make_shared_virtual(); + + std::cout << poke(animal) << "\n"; // bark + // end::make_shared_virtual[] + + BOOST_TEST(cout.str() == "bark\n"); + } + + { + // tag::shared_virtual_ptr_alias[] + shared_virtual_ptr animal = make_shared_virtual(); + std::shared_ptr owner = animal.pointer(); + + BOOST_TEST(owner.use_count() == 2); + // end::shared_virtual_ptr_alias[] + } + + { + using namespace by_value; + capture_cout cout; + + // tag::shared_by_value_call[] + std::cout << poke(std::make_shared()) << "\n"; // bark + std::cout << poke(std::make_shared()) << "\n"; // hiss + // end::shared_by_value_call[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); + } + + { + using namespace by_reference; + capture_cout cout; + + // tag::shared_by_reference_call[] + const std::shared_ptr snoopy = std::make_shared(); + + std::cout << poke(snoopy) << "\n"; // bark + + BOOST_TEST(snoopy.use_count() == 1); + // end::shared_by_reference_call[] + + BOOST_TEST(cout.str() == "bark\n"); + } +} + +BOOST_AUTO_TEST_CASE(unique_ptr_examples) { + initialize(); + + { + using namespace unique_vptr; + capture_cout cout; + + // tag::make_unique_virtual[] + unique_virtual_ptr animal = make_unique_virtual(); + + std::cout << poke(std::move(animal)) << "\n"; // bark + // end::make_unique_virtual[] + + BOOST_TEST(cout.str() == "bark\n"); + } + + { + // tag::unique_virtual_ptr_alias[] + unique_virtual_ptr animal = make_unique_virtual(); + unique_virtual_ptr owner = std::move(animal); + + BOOST_TEST(owner.get() != nullptr); + BOOST_TEST(animal.get() == nullptr); + // end::unique_virtual_ptr_alias[] + } + + { + using namespace unique; + capture_cout cout; + + // tag::unique_by_value_call[] + std::cout << poke(std::make_unique()) << "\n"; // bark + std::cout << poke(std::make_unique()) << "\n"; // hiss + // end::unique_by_value_call[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); + } +} diff --git a/doc/modules/ROOT/snippets/static_rtti.cpp b/doc/modules/ROOT/snippets/static_rtti.cpp new file mode 100644 index 00000000..cd561f55 --- /dev/null +++ b/doc/modules/ROOT/snippets/static_rtti.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) + +// `static_rtti` has to be selected before is included, +// so this example needs a translation unit of its own. + +// tag::registry[] +#include +#include + +struct static_registry + : boost::openmethod::registry {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry +// end::registry[] + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod::aliases; + +// tag::classes[] +// polymorphism not required: there is no RTTI to consult +struct Animal {}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +BOOST_OPENMETHOD(trick, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(trick, (virtual_ptr), std::string) { + return "spin"; +} + +BOOST_OPENMETHOD_OVERRIDE(trick, (virtual_ptr), std::string) { + return "sulk"; +} +// end::classes[] + +BOOST_AUTO_TEST_CASE(static_rtti_examples) { + boost::openmethod::initialize(); + capture_cout cout; + + // tag::dispatch[] + // the exact class must be known where the pointer is created + unique_virtual_ptr a = make_unique_virtual(); + unique_virtual_ptr b = make_unique_virtual(); + + std::cout << trick(a) << "\n"; // sulk + std::cout << trick(b) << "\n"; // spin + // end::dispatch[] + + BOOST_TEST(cout.str() == "sulk\nspin\n"); +} diff --git a/doc/modules/ROOT/snippets/virtual_any.cpp b/doc/modules/ROOT/snippets/virtual_any.cpp new file mode 100644 index 00000000..b832239b --- /dev/null +++ b/doc/modules/ROOT/snippets/virtual_any.cpp @@ -0,0 +1,208 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +namespace std_any { + +// tag::classes[] +struct Dog { + std::string name; +}; + +// `std::any` becomes the common base of the types it may contain. +BOOST_OPENMETHOD_REGISTER(use_std_any_types); +// end::classes[] + +// tag::method[] +BOOST_OPENMETHOD(name, (const virtual_std_any&), std::string); + +// An overrider takes the contained value... +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) { + return std::to_string(value) + " the integer"; +} + +// ...or the `virtual_any` itself, which makes it a catch-all. +BOOST_OPENMETHOD_OVERRIDE(name, (const virtual_std_any& value), std::string) { + return value.get().has_value() ? "something else" : "nothing"; +} +// end::method[] + +} // namespace std_any + +namespace boost_any { + +// tag::boost_classes[] +struct Dog { + std::string name; +}; + +// `boost::any` is a root class of its own, distinct from the one used for +// `std::any`, so both may be used in the same program and registry. +BOOST_OPENMETHOD_REGISTER(use_boost_any_types); + +BOOST_OPENMETHOD(name, (const virtual_boost_any&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; +} +// end::boost_classes[] + +} // namespace boost_any + +namespace any_ref { + +using std_any::Dog; + +// tag::ref[] +BOOST_OPENMETHOD(poke, (virtual_any_ref), std::string); + +// A plain value does not convert to a virtual_any_ref, so overriders +// that take the contained value are registered with the core API. +using poke_method = + BOOST_OPENMETHOD_TYPE(poke, (virtual_any_ref), std::string); + +auto poke_dog(Dog& dog) -> std::string { + dog.name += "!"; + return dog.name; +} + +BOOST_OPENMETHOD_REGISTER(poke_method::override); +// end::ref[] + +} // namespace any_ref + +BOOST_AUTO_TEST_CASE(std_any_examples) { + using namespace std_any; + + initialize(); + + { + capture_cout cout; + + // tag::dispatch[] + virtual_std_any spot = Dog{"Spot"}; + + std::cout << name(spot) << "\n"; // Spot the dog + + // `float` is registered, but has no overrider of its own, so the + // catch-all applies. The value converts to a temporary + // `virtual_std_any` at the call site. + std::cout << name(3.14f) << "\n"; // something else + // end::dispatch[] + + BOOST_TEST(cout.str() == "Spot the dog\nsomething else\n"); + } + + { + capture_cout cout; + + // tag::from_any[] + std::any spot_any = Dog{"Spot"}; + + // the v-table pointer is looked up from the type of the value the + // `any` contains + virtual_std_any spot = spot_any; + + std::cout << name(spot) << "\n"; // Spot the dog + // end::from_any[] + + BOOST_TEST(cout.str() == "Spot the dog\n"); + } + + { + capture_cout cout; + + // tag::from_value[] + // the type is known at compile time, so the v-table pointer is read + // from a static variable - there is no lookup + virtual_std_any answer = 42; + + std::cout << name(answer) << "\n"; // 42 the integer + // end::from_value[] + + BOOST_TEST(cout.str() == "42 the integer\n"); + } + + { + capture_cout cout; + + // tag::emplace[] + virtual_std_any value; + + value.emplace("Felix the cat"); + + std::cout << name(value) << "\n"; // Felix the cat + // end::emplace[] + + BOOST_TEST(cout.str() == "Felix the cat\n"); + } +} + +BOOST_AUTO_TEST_CASE(virtual_any_ref_examples) { + using namespace any_ref; + + initialize(); + + { + capture_cout cout; + + // tag::ref_dispatch[] + std::any spot_any = Dog{"Spot"}; + + // one lookup; the handle borrows the `any` + virtual_any_ref spot = spot_any; + + std::cout << poke(spot) << "\n"; // Spot! + std::cout << poke(spot) << "\n"; // Spot!! - no lookup on any call + // end::ref_dispatch[] + + BOOST_TEST(cout.str() == "Spot!\nSpot!!\n"); + } +} + +BOOST_AUTO_TEST_CASE(boost_any_examples) { + using namespace boost_any; + + initialize(); + + { + capture_cout cout; + + // tag::boost_dispatch[] + virtual_boost_any felix = std::string("Felix the cat"); + + std::cout << name(felix) << "\n"; // Felix the cat + // end::boost_dispatch[] + + BOOST_TEST(cout.str() == "Felix the cat\n"); + } +} diff --git a/doc/modules/ROOT/snippets/virtual_ptr.cpp b/doc/modules/ROOT/snippets/virtual_ptr.cpp new file mode 100644 index 00000000..d60db436 --- /dev/null +++ b/doc/modules/ROOT/snippets/virtual_ptr.cpp @@ -0,0 +1,501 @@ +// 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 openmethod +#include + +#include "capture.hpp" + +using namespace boost::openmethod; + +namespace polymorphic_classes { + +// tag::polymorphic_classes[] +struct Animal { + virtual ~Animal() = default; +}; +struct Dog : Animal {}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +// end::polymorphic_classes[] + +BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace polymorphic_classes + +namespace non_polymorphic_classes { + +// tag::non_polymorphic_classes[] +// classes not required to be polymorphic +struct Animal {}; +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); +// end::non_polymorphic_classes[] + +BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr animal), std::string) { + return "hiss"; +} + +} // namespace non_polymorphic_classes + +BOOST_AUTO_TEST_CASE(virtual_ptr_examples) { + // tag::initialize[] + initialize(); + // end::initialize[] + + { + using namespace non_polymorphic_classes; + poke(make_unique_virtual()); // for coverage + } + + { + using namespace polymorphic_classes; + // tag::ctor_nullptr[] + virtual_ptr p{nullptr}; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::ctor_nullptr[] + } + + { + using namespace polymorphic_classes; + // tag::ctor_ref[] + Dog snoopy; + Animal& animal = snoopy; + + virtual_ptr p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_ref[] + } + + { + using namespace polymorphic_classes; + // tag::ctor_pointer[] + Dog snoopy; + Animal* animal = &snoopy; + + virtual_ptr p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_pointer[] + } + + { + using namespace non_polymorphic_classes; + // tag::ctor_vptr[] + Dog snoopy; + virtual_ptr dog = final_virtual_ptr(snoopy); + + virtual_ptr p = dog; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::ctor_shared_vptr[] + virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::ctor_shared_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::ctor_shared_from_plain_rejected[] + static_assert( + std::is_constructible_v< + shared_virtual_ptr, virtual_ptr> == false); + // end::ctor_shared_from_plain_rejected[] + } + + { + using namespace polymorphic_classes; + // tag::assign_ref[] + virtual_ptr p{nullptr}; + Dog snoopy; + Animal& animal = snoopy; + + p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_ref[] + } + + { + using namespace polymorphic_classes; + // tag::assign_pointer[] + virtual_ptr p{nullptr}; + Dog snoopy; + Animal* animal = &snoopy; + + p = animal; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_pointer[] + } + + { + using namespace non_polymorphic_classes; + // tag::assign_vptr[] + Dog snoopy; + virtual_ptr dog = final_virtual_ptr(snoopy); + virtual_ptr p{nullptr}; + + p = dog; + + BOOST_TEST(p.get() == &snoopy); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::assign_shared_vptr[] + virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr p; + + p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::assign_shared_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::assign_shared_from_plain_rejected[] + static_assert( + std::is_assignable_v< + shared_virtual_ptr&, virtual_ptr> == false); + // end::assign_shared_from_plain_rejected[] + } + + { + using namespace polymorphic_classes; + // tag::assign_nullptr[] + Dog snoopy; + virtual_ptr p(snoopy); + + p = nullptr; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::assign_nullptr[] + } + + { + using namespace polymorphic_classes; + + // tag::cast[] + Dog snoopy; + virtual_ptr animal(snoopy); + + auto dog = animal.cast(); + + BOOST_TEST(dog.get() == &snoopy); + BOOST_TEST(dog.vptr() == animal.vptr()); + // end::cast[] + } + + { + using namespace non_polymorphic_classes; + capture_cout cout; + + // tag::final_virtual_ptr[] + Dog snoopy; + virtual_ptr animal = final_virtual_ptr(snoopy); + std::cout << poke(animal) << "\n"; // bark + + Cat felix; + animal = final_virtual_ptr(felix); + std::cout << poke(animal) << "\n"; // hiss + // end::final_virtual_ptr[] + + BOOST_TEST(cout.str() == "bark\nhiss\n"); + } +} + +BOOST_AUTO_TEST_CASE(shared_virtual_ptr_examples) { + initialize(); + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_default[] + virtual_ptr> p; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::shared_ctor_default[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_nullptr[] + virtual_ptr> p{nullptr}; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + // end::shared_ctor_nullptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_ctor_const_smart_ptr[] + const std::shared_ptr snoopy = std::make_shared(); + virtual_ptr> p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_ctor_const_smart_ptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_ctor_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + virtual_ptr> p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_ctor_smart_ptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_ctor_move_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + Dog* moving = snoopy.get(); + + virtual_ptr> p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::shared_ctor_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_const_vptr[] + const virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr> p = snoopy; + + BOOST_TEST(snoopy.get() != nullptr); + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_ctor_const_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_ctor_move_vptr[] + virtual_ptr> snoopy = make_shared_virtual(); + Dog* dog = snoopy.get(); + + virtual_ptr> p = std::move(snoopy); + + BOOST_TEST(p.get() == dog); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::shared_ctor_move_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_nullptr[] + virtual_ptr> p = make_shared_virtual(); + + p = nullptr; + + BOOST_TEST(p.get() == nullptr); + BOOST_TEST(p.vptr() == nullptr); + BOOST_TEST((p == virtual_ptr>())); + // end::shared_assign_nullptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_assign_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + virtual_ptr> p; + + p = snoopy; + + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + // end::shared_assign_smart_ptr[] + } + + { + using namespace polymorphic_classes; + // tag::shared_assign_move_smart_ptr[] + std::shared_ptr snoopy = std::make_shared(); + Dog* moving = snoopy.get(); + virtual_ptr> p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::shared_assign_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_vptr[] + virtual_ptr> snoopy = make_shared_virtual(); + virtual_ptr> p; + + p = snoopy; + + BOOST_TEST(p.get() != nullptr); + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); + // end::shared_assign_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_const_vptr[] + const virtual_ptr> snoopy = + make_shared_virtual(); + virtual_ptr> p; + + p = snoopy; + + BOOST_TEST(p.get() != nullptr); + BOOST_TEST(p.get() == snoopy.get()); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); + // end::shared_assign_const_vptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::shared_assign_move_vptr[] + virtual_ptr> snoopy = make_shared_virtual(); + Dog* moving = snoopy.get(); + virtual_ptr> p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + BOOST_TEST(snoopy.vptr() == nullptr); + // end::shared_assign_move_vptr[] + } +} + +BOOST_AUTO_TEST_CASE(unique_virtual_ptr_examples) { + initialize(); + + { + using namespace polymorphic_classes; + // tag::unique_copy_rejected[] + static_assert( + std::is_constructible_v< + unique_virtual_ptr, const std::unique_ptr&> == + false); + // end::unique_copy_rejected[] + } + + { + using namespace polymorphic_classes; + // tag::unique_ctor_move_smart_ptr[] + std::unique_ptr snoopy = std::make_unique(); + Dog* moving = snoopy.get(); + + unique_virtual_ptr p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::unique_ctor_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::unique_ctor_move_vptr[] + unique_virtual_ptr snoopy = make_unique_virtual(); + Dog* moving = snoopy.get(); + + unique_virtual_ptr p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::unique_ctor_move_vptr[] + } + + { + using namespace polymorphic_classes; + // tag::unique_assign_move_smart_ptr[] + std::unique_ptr snoopy = std::make_unique(); + Dog* moving = snoopy.get(); + unique_virtual_ptr p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + // end::unique_assign_move_smart_ptr[] + } + + { + using namespace non_polymorphic_classes; + // tag::unique_assign_move_vptr[] + unique_virtual_ptr snoopy = make_unique_virtual(); + Dog* moving = snoopy.get(); + unique_virtual_ptr p; + + p = std::move(snoopy); + + BOOST_TEST(p.get() == moving); + BOOST_TEST(p.vptr() == default_registry::static_vptr); + BOOST_TEST(snoopy.get() == nullptr); + BOOST_TEST(snoopy.vptr() == nullptr); + // end::unique_assign_move_vptr[] + } +} diff --git a/doc/mrdocs-addons/extensions/include.lua b/doc/mrdocs-addons/extensions/include.lua new file mode 100644 index 00000000..0a001c97 --- /dev/null +++ b/doc/mrdocs-addons/extensions/include.lua @@ -0,0 +1,239 @@ +-- Substitute a marker in a doc comment with the contents of a file. +-- +-- A paragraph whose entire text is +-- +-- include:[#[;...]] +-- +-- is replaced by a code block holding , or the named `// tag::name[]` +-- regions of it. The path is relative to `transform-options.include.root`, +-- itself relative to the directory holding this mrdocs.yml. Typical use: +-- +-- //! @par Example +-- //! include:virtual_ptr.cpp#setup;assign_nullptr +-- +-- The point is that the rendered snippet is a region of a file the build +-- compiles and runs, so a reference example cannot drift from the library. +-- +-- Regions are selected in file order, the way Asciidoctor's `tags=` attribute +-- selects them, and each contiguous run is dedented on its own before the runs +-- are joined by a blank line. Per-run dedent is what lets a snippet draw its +-- setup from namespace scope and its body from inside a test case and still +-- render flush. +-- +-- Why the whole block list is rebuilt rather than the marker patched in place: +-- three gaps in the 0.8.0 extension API, reported at +-- https://cpplang.slack.com/archives/C0508A7LWUV/p1785455605224149 +-- +-- * `doc.document[i] = block` fails with "attempt to index a userdata value" +-- -- the Lua binding exposes no __newindex for array proxies, so +-- DescribedArrayProxy::set is unreachable from a script. +-- * A proxy read out of the corpus is rejected as setter input ("expects an +-- object describing a polymorphic value"), so blocks cannot be handed back +-- verbatim; they have to be deep-copied into plain tables. +-- * `level` is refused by the generic setter, hence UNWRITABLE below. +-- +-- If those are fixed upstream this whole file collapses to a few lines. + +-- Fields the generic setter cannot write. `level` is a heading's depth; MrDocs +-- does not parse markdown `##` headings in doc comments, so heading blocks only +-- ever come from `@par` at level 1 -- which is the default -- and dropping it +-- round-trips. `$meta` is metadata MrDocs attaches to a node, not content: it +-- reads back from the proxy, but since a51d1621 the setter rejects it as an +-- unknown sub-field of the node's kind, so the deep copy must leave it out. +local UNWRITABLE = { level = true, ["$meta"] = true } + +local function dirname(path) + return path:match("^(.*)/[^/]*$") or "." +end + +local function is_array(value) + local ok, n = pcall(function() + return #value + end) + return ok and n and n > 0 +end + +local function copy(value) + if type(value) ~= "userdata" then + return value + end + + if is_array(value) then + local out = {} + for i = 1, #value do + out[i] = copy(value[i]) + end + return out + end + + local out, any = {}, false + for key, field in pairs(value) do + any = true + if not UNWRITABLE[key] then + out[key] = copy(field) + end + end + + -- An empty proxy is an absent optional, not an empty object. + if not any then + return nil + end + + return out +end + +-- Drop the common indentation of `lines`, then join them. +local function dedent(lines) + local indent + + for _, line in ipairs(lines) do + local lead = line:match("^([ \t]*)%S") + if lead and (not indent or #lead < #indent) then + indent = lead + end + end + + if indent and #indent > 0 then + for i, line in ipairs(lines) do + lines[i] = line:sub(#indent + 1) + end + end + + return (table.concat(lines, "\n"):gsub("%s+$", "")) +end + +-- Return the regions of `text` covered by `tags`, in file order, or the whole +-- text when `tags` is nil. The second result lists the tags that never opened. +local function select_regions(text, tags) + if not tags then + return (text:gsub("%s+$", "")), {} + end + + local wanted, found = {}, {} + for _, tag in ipairs(tags) do + wanted[tag] = true + end + + local regions, current, depth = {}, nil, 0 + + local function flush() + if current then + regions[#regions + 1] = dedent(current) + current = nil + end + end + + for line in (text .. "\n"):gmatch("([^\n]*)\n") do + local opens = line:match("tag::([%w_%-%.]+)%[%]") + local closes = line:match("end::([%w_%-%.]+)%[%]") + + if opens then + if wanted[opens] then + found[opens] = true + depth = depth + 1 + end + elseif closes then + if wanted[closes] then + depth = depth - 1 + if depth == 0 then + flush() + end + end + elseif depth > 0 then + current = current or {} + current[#current + 1] = line + end + end + + flush() + + local missing = {} + for _, tag in ipairs(tags) do + if not found[tag] then + missing[#missing + 1] = tag + end + end + + return table.concat(regions, "\n\n"), missing +end + +local function read_file(path) + local file = io.open(path, "r") + if not file then + return nil + end + local text = file:read("*a") + file:close() + return text +end + +-- `include:` or `include:#[;...]`, alone in a paragraph. +local function parse_marker(block) + if block.kind ~= "paragraph" then + return nil + end + + local inlines = block.children + if not inlines or #inlines ~= 1 or inlines[1].kind ~= "text" then + return nil + end + + local spec = inlines[1].literal:match("^include:(%S+)$") + if not spec then + return nil + end + + local path, tail = spec:match("^([^#]+)#(.+)$") + if not path then + return spec, nil + end + + local tags = {} + for tag in tail:gmatch("[^;]+") do + tags[#tags + 1] = tag + end + + return path, tags +end + +mrdocs.register_transform("include", function(ctx) + local root = dirname(ctx.config.config) .. "/" .. (ctx.params.root or ".") + local lang = ctx.params.lang or "cpp" + + for _, symbol in ipairs(ctx.corpus.symbols) do + local document = symbol.doc and symbol.doc.document + + if document and #document > 0 then + local blocks, substituted = {}, false + + for i = 1, #document do + local block = document[i] + local path, tags = parse_marker(block) + + if path then + local full = root .. "/" .. path + local text = read_file(full) + if not text then + error("include: cannot read " .. full) + end + + local body, missing = select_regions(text, tags) + if #missing > 0 then + error( + "include: no tag " .. table.concat(missing, ", ") + .. " in " .. full) + end + + blocks[i] = { kind = "code", literal = body, info = lang } + substituted = true + else + blocks[i] = copy(block) + end + end + + if substituted then + symbol.doc.document = blocks + end + end + end +end) diff --git a/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs b/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs new file mode 100644 index 00000000..29f44c7f --- /dev/null +++ b/doc/mrdocs-addons/generator/adoc/partials/markup/a.adoc.hbs @@ -0,0 +1,57 @@ +{{! + Overrides the built-in markup/a partial, adding the `xref:ROOT:` branch + below. Everything else is the upstream template verbatim; keep it that way + so the file is easy to diff against a newer MrDocs. + + Why the extra branch: a doc comment reaches a hand-written guide page with a + markdown link whose target is an Antora resource ID, which the final `else` + emits verbatim: + + //! @see [Methods and Overriders](xref:ROOT:basics.adoc) + + That works only at the output root. MrDocs sets `:relfileprefix: ../../` on + nested pages, Asciidoctor folds it into the xref target Antora resolves, and + `../../ROOT:basics.adoc` is not a valid resource ID. Clearing the attribute + is not an option - the breadcrumbs in the document title are converted by + plain Asciidoctor and need it. See cppalliance/mrdocs#1245. + + So on a nested page emit a `link:` instead: a link macro is not an + inter-document xref, so relfileprefix never touches it. `relfileprefix` + reaches the reference module root and the ROOT module sits one level above + it, hence the extra `../`. At the root the href is passed through unchanged, + so those links stay real xrefs and Antora still validates them. + + The `xref:reference:` branch below is the same treatment for a link from one + reference page to another - a doc comment reaching a specific overload's + Example section, say. Those targets are already relative to the reference + module root, so no extra `../` is needed. + + Delete this file once #1245 is resolved upstream. + + Do not relativize links as asciidoc does not support it. + + https://gitlab.com/antora/antora/-/issues/428 +}} +{{#if (eq href @root.symbol.url)~}} + {{{> @partial-block }}} +{{~else if (starts_with href "#")~}} + link:{{{ href }}}[{{> @partial-block }}] +{{~else if (starts_with href "xref:ROOT:")~}} +{{~#if @root.page.relfileprefix~}} + link:{{{@root.page.relfileprefix}}}../{{{replace (remove_prefix href "xref:ROOT:") ".adoc" ".html"}}}[{{> @partial-block }}] +{{~else~}} + {{{href}}}[{{> @partial-block }}] +{{~/if~}} +{{~else if (starts_with href "xref:reference:")~}} +{{~#if @root.page.relfileprefix~}} + link:{{{@root.page.relfileprefix}}}{{{replace (remove_prefix href "xref:reference:") ".adoc" ".html"}}}[{{> @partial-block }}] +{{~else~}} + {{{href}}}[{{> @partial-block }}] +{{~/if~}} +{{~else if (starts_with href "/")~}} + xref:{{{remove_prefix href "/"}}}[{{> @partial-block }}] +{{~else if (starts_with href ".")~}} + xref:{{{href}}}[{{> @partial-block }}] +{{~else~}} + {{{href}}}[{{> @partial-block }}{{#if blank}}^{{/if}}] +{{~/if~}} diff --git a/doc/mrdocs-addons/generator/common/partials/symbol/section/see-also.hbs b/doc/mrdocs-addons/generator/common/partials/symbol/section/see-also.hbs new file mode 100644 index 00000000..0e0ac365 --- /dev/null +++ b/doc/mrdocs-addons/generator/common/partials/symbol/section/see-also.hbs @@ -0,0 +1,26 @@ +{{! + Overrides the built-in symbol/section/see-also partial. + + The built-in renders each @see entry through `doc/block/see`, i.e. as a + block, and MrDocs separates blocks with a blank line - which AsciiDoc reads + as a paragraph break, so a symbol with several @see entries gets a paragraph + each. This renders them inline instead, comma-separated on one line, the + conventional shape for a See Also list. + + `doc/inline-container` is what `doc/block/see` reaches through + `doc/block/paragraph`; going straight to it is what drops the block + separation. The blank line before the section closes keeps whatever follows + out of the same paragraph. + + Entries are joined with ", ", so each @see should be a bare reference rather + than a sentence. +}} +{{#if symbol.doc.sees}} +{{#> markup/section name="see-also"}} +{{#> markup/dynamic-level-h }}See Also{{/markup/dynamic-level-h~}} +{{#each symbol.doc.sees~}} +{{> doc/inline-container .}}{{#unless @last}}, {{/unless}} +{{~/each}} + +{{/markup/section}} +{{/if}} diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 380e427b..32a92a73 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -25,6 +25,17 @@ exclude-symbols: - 'boost::openmethod::boost_openmethod_registry' - 'boost::openmethod::registry_state::st' +# Macros. Only the public macros carry a doc comment, and with +# `extract-all-macros` off (the default) MrDocs extracts only documented ones. +# The patterns below make that explicit: the library's own implementation +# macros are never documented, whatever they are called. +include-macros: + - 'BOOST_OPENMETHOD*' +exclude-macros: + - 'BOOST_OPENMETHOD_DETAIL_*' + - 'BOOST_OPENMETHOD_GENSYM' + - 'BOOST_OPENMETHOD_GUIDE' + sort-members: false # sort-namespace-members-by: location extract-friends: false @@ -34,9 +45,21 @@ inherit-base-members: never private-bases: false auto-function-metadata: false +# Template overrides and extension scripts, layered on top of the built-in +# addons. See the header comment in each file for what it does and why. +addons-supplemental: + - mrdocs-addons + +# `include:[#[;...]]` alone in a paragraph of a doc comment is +# replaced by the file, or its `// tag::name[]` regions, as a code block. See +# mrdocs-addons/extensions/include.lua. `root` is relative to this file. +transform-options: + include: + root: modules/ROOT/snippets + lang: cpp + # Generator generate: adoc -base-url: https://www.github.com/boostorg/openmethod/blob/master/ # Style verbose: true diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 68f62776..67fcf4da 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -24,6 +24,35 @@ #include #ifndef BOOST_OPENMETHOD_DEFAULT_REGISTRY +//! Default value for `Registry`. +//! +//! The name of the default registry. +//! +//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` is the default value for the `Registry` +//! template parameter of @ref boost::openmethod::method, +//! @ref boost::openmethod::use_classes, @ref boost::openmethod::virtual_ptr, +//! and all the constructs that take a registry as a template argument. +//! +//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` can be defined by a program to change +//! the default registry globally, *before* including +//! ``. After that, changing its value has no effect, +//! even on other macros. +//! +//! To override the default registry, proceed as follows: +//! +//! @li Define a @ref boost::openmethod::registry class, either from scratch, or +//! by tuning an existing registry. Include ``, +//! ``, and headers under +//! `boost/openmethod/policies` as needed. +//! +//! @li Set `BOOST_OPENMETHOD_DEFAULT_REGISTRY` to the new registry class. +//! +//! @li Include ``. +//! +//! @note Use this feature with caution, as it will cause ODR violations if +//! different translation units define different default registries. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) #define BOOST_OPENMETHOD_DEFAULT_REGISTRY ::boost::openmethod::default_registry #endif @@ -412,6 +441,9 @@ using use_classes_tuple_type = boost::mp11::mp_apply< //! //! Virtual and multiple inheritance are supported, with the exclusion of //! repeated inheritance. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template class use_classes { detail::use_classes_tuple_type tuple; @@ -526,12 +558,25 @@ constexpr bool has_vptr_fn = std::is_same_v< std::declval(), std::declval())), vptr_type>; +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(vptr); + template decltype(auto) acquire_vptr(const ArgType& arg) { + // A class with a boost_openmethod_vptr overload does not need to be + // wrapped: virtual_ptr and the hook fill the same goal, fast access + // to the v-table pointer. The hook also returns the vptr by value, + // which indirect registries cannot store (see box_vptr). + static_assert( + !has_vptr_fn, + "do not wrap an object that has a boost_openmethod_vptr overload " + "in a virtual_ptr; call methods directly on the object"); + Registry::require_initialized(); - if constexpr (detail::has_vptr_fn) { - return boost_openmethod_vptr(arg, static_cast(nullptr)); + if constexpr (has_vptr< + virtual_traits, + const ArgType&>) { + return virtual_traits::vptr(arg); } else { return Registry::template policy::dynamic_vptr(arg); } @@ -558,7 +603,7 @@ inline vptr_type null_vptr = nullptr; } // namespace detail -//! Creates a `virtual_ptr` for an object of a known dynamic type. +//! Create a `virtual_ptr` for an object of a known exact class. //! //! Creates a @ref virtual_ptr to an object, setting its v-table pointer //! according to the declared type of its argument. Assumes that the static and @@ -567,6 +612,14 @@ inline vptr_type null_vptr = nullptr; //! //! `Class` is _not_ required to be polymorphic. //! +//! Nothing is looked up at runtime. Constructing a `virtual_ptr` from a +//! reference or a pointer reads the object's dynamic type through the +//! registry's `rtti` policy, then finds the v-table through its `vptr` policy; +//! here the v-table pointer is a static variable, read directly. It is also +//! the only way to create a `virtual_ptr` in a registry that uses +//! @ref policies::static_rtti, which has no dynamic type to consult and +//! disables the constructors that would need one. +//! //! If runtime checks are enabled, and the argument is polymorphic, checks if //! the static and dynamic types are the same. If not, calls the error handler //! with a @ref final_error value, then terminates the program with @ref abort. @@ -576,6 +629,10 @@ inline vptr_type null_vptr = nullptr; //! @li @ref final_error The static and dynamic types of the object are //! different. //! +//! @par Example +//! +//! include:virtual_ptr.cpp#non_polymorphic_classes;final_virtual_ptr +//! //! @tparam Registry A @ref registry. //! @tparam Arg The type of the argument. //! @param obj A reference to an object. @@ -632,11 +689,15 @@ inline auto final_virtual_ptr(Arg&& obj) { detail::box_vptr(vptr)); } -//! Create a `virtual_ptr` for an object of a known dynamic type. +//! Create a `virtual_ptr` for an object of a known exact class. //! //! This is an overload of `final_virtual_ptr` that uses the default //! registry as the `Registry` template parameter. //! +//! @par Example +//! +//! include:virtual_ptr.cpp#non_polymorphic_classes;final_virtual_ptr +//! //! @see @ref final_virtual_ptr // We could give a default value to Registry in the main template, but gcc // doesn't like it. @@ -661,7 +722,7 @@ inline auto final_virtual_ptr(Arg&& obj) { //! the other way around. //! //! The default value for `Registry` can be customized by defining the -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}} +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY //! preprocessor symbol. //! //! @par Requirements @@ -673,6 +734,10 @@ inline auto final_virtual_ptr(Arg&& obj) { //! @tparam Class The class of the object, possibly cv-qualified //! @tparam Registry The registry in which `Class` is registered //! @tparam unnamed Implementation defined, use default +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) +//! @see [Performance](xref:ROOT:performance.adoc) template class virtual_ptr { @@ -717,16 +782,7 @@ class virtual_ptr { //! //! @par Example //! - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr p{nullptr}; - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#ctor_nullptr //! //! @param value A `nullptr`. explicit virtual_ptr(std::nullptr_t) @@ -736,28 +792,17 @@ class virtual_ptr { //! Construct a `virtual_ptr` from a reference to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @param other A reference to a polymorphic object //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! Animal& animal = snoopy; - //! - //! virtual_ptr p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#ctor_ref //! //! @par Requirements //! @li @c Other must be a polymorphic class, according to the @c rtti @@ -784,26 +829,15 @@ class virtual_ptr { //! Construct a `virtual_ptr` from a pointer to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! Animal* animal = &snoopy; - //! - //! virtual_ptr p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#ctor_pointer //! //! @param other A pointer to a polymorphic object //! @@ -840,46 +874,17 @@ class virtual_ptr { //! //! @par Examples //! - //! Assigning from a plain virtual_ptr: - //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! virtual_ptr dog = final_virtual_ptr(snoopy); - //! virtual_ptr p{nullptr}; + //! Constructing from a plain `virtual_ptr`: //! - //! p = dog; + //! include:virtual_ptr.cpp#non_polymorphic_classes;ctor_vptr //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! Constructing from a smart `virtual_ptr`: //! - //! Assigning from a smart virtual_ptr: - //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr p = snoopy; - //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#ctor_shared_vptr //! //! No construction of a smart `virtual_ptr` from a plain `virtual_ptr`: //! - //! @code - //! static_assert( - //! std::is_constructible_v< - //! shared_virtual_ptr, virtual_ptr> == false); - //! @endcode + //! include:virtual_ptr.cpp#ctor_shared_from_plain_rejected //! //! @param other A virtual_ptr to a type-compatible object //! @@ -895,27 +900,15 @@ class virtual_ptr { //! Assign a `virtual_ptr` from a reference to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr p{nullptr}; - //! Dog snoopy; - //! Animal& animal = snoopy; - //! - //! p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#assign_ref //! //! @param other A reference to a polymorphic object //! @@ -947,27 +940,15 @@ class virtual_ptr { //! Assign a `virtual_ptr` from a pointer to an object //! - //! The pointer to the v-table is obtained by calling - //! @ref boost_openmethod_vptr if a suitable overload exists, or the - //! @ref policies::vptr::fn::dynamic_vptr of the registry's - //! `vptr` policy otherwise. + //! The pointer to the v-table is obtained from @ref virtual_traits, + //! if it provides a `vptr` function, or from the + //! @ref policies::VptrFn::dynamic_vptr of the registry's `vptr` + //! policy otherwise. An object with a @ref boost_openmethod_vptr + //! overload is rejected at compile time: it carries its own v-table + //! pointer, and does not need to be wrapped in a `virtual_ptr`. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr p{nullptr}; - //! Dog snoopy; - //! Animal* animal = &snoopy; - //! - //! p = animal; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#assign_pointer //! //! @param other A pointer to a polymorphic object //! @@ -1003,48 +984,17 @@ class virtual_ptr { //! //! @par Examples //! - //! Assigning from a plain virtual_ptr: - //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! virtual_ptr dog = final_virtual_ptr(snoopy); - //! virtual_ptr p{nullptr}; - //! - //! p = dog; - //! - //! BOOST_TEST(p.get() == &snoopy); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode - //! - //! Assigning from a smart virtual_ptr: - //! - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); + //! Assigning from a plain `virtual_ptr`: //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr p; + //! include:virtual_ptr.cpp#non_polymorphic_classes;assign_vptr //! - //! p = snoopy; + //! Assigning from a smart `virtual_ptr`: //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#assign_shared_vptr //! //! No assignment from a plain `virtual_ptr` to a smart `virtual_ptr`: //! - //! @code - //! static_assert( - //! std::is_assignable_v< - //! shared_virtual_ptr&, virtual_ptr> == false); - //! @endcode + //! include:virtual_ptr.cpp#assign_shared_from_plain_rejected //! //! @param other A virtual_ptr to a type-compatible object //! @@ -1065,20 +1015,7 @@ class virtual_ptr { //! Set both object and v-table pointers to `nullptr`. //! //! @par Example - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! Dog snoopy; - //! virtual_ptr p = final_virtual_ptr(snoopy); - //! - //! p = nullptr; - //! - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! //! @code - //! @endcode + //! include:virtual_ptr.cpp#assign_nullptr virtual_ptr& operator=(std::nullptr_t) { obj = nullptr; vp = detail::box_vptr(detail::null_vptr); @@ -1116,8 +1053,7 @@ class virtual_ptr { //! Cast to another `virtual_ptr` type //! //! @par Example - //! @code - //! @endcode + //! include:virtual_ptr.cpp#cast //! //! @tparam Other The target class of the cast //! @return A `virtual_ptr` pointing to the same object @@ -1133,7 +1069,7 @@ class virtual_ptr { traits::template cast(*obj), vp); } - //! Construct a `virtual_ptr` from a reference to an object + //! Construct a `virtual_ptr` for an object of a known exact class //! //! This function forwards to @ref final_virtual_ptr. //! @@ -1160,6 +1096,8 @@ class virtual_ptr { //! //! @tparam SmartPtr A smart pointer type //! @tparam Registry The registry in which the underlying class is registered +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template class virtual_ptr< SmartPtr, Registry, @@ -1202,16 +1140,7 @@ class virtual_ptr< //! v-table pointer to `nullptr`. //! //! @par Example - //! @code - //! struct Dog {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Dog); - //! initialize(); - //! - //! virtual_ptr> p; - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! @par Example - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_default virtual_ptr() : vp(detail::box_vptr(detail::null_vptr)) { } @@ -1222,15 +1151,7 @@ class virtual_ptr< //! v-table pointer to `nullptr`. //! //! @par Example - //! @code - //! struct Dog {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Dog); - //! initialize(); - //! - //! virtual_ptr> p{nullptr}; - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_nullptr //! //! @param value A `nullptr`. explicit virtual_ptr(std::nullptr_t) @@ -1251,19 +1172,16 @@ class virtual_ptr< //! Set the object pointer with a copy of `other`. Set the v-table pointer //! according to the dynamic type of `*other`. //! - //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); + //! @par Examples + //! + //! Constructing from a `std::shared_ptr`: + //! + //! include:virtual_ptr.cpp#shared_ctor_const_smart_ptr //! - //! const std::shared_ptr snoopy = std::make_shared(); - //! virtual_ptr> p = snoopy; + //! A move-only smart pointer cannot be copied from. Use the move + //! constructor instead: //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#unique_copy_rejected //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1299,18 +1217,7 @@ class virtual_ptr< //! according to the dynamic type of `*other`. //! //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! std::shared_ptr snoopy = std::make_shared(); - //! virtual_ptr> p = snoopy; - //! - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_ctor_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1345,22 +1252,15 @@ class virtual_ptr< //! Move object pointer from `other` to `this`. Set the v-table pointer //! according to the dynamic type of `*other`. //! - //! @par Example - //! @code - //! struct Animal { virtual ~Animal() { } }; // polymorphic - //! struct Dog : Animal {}; // polymorphic - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); + //! @par Examples + //! + //! Move-constructing from a `std::shared_ptr`: //! - //! std::shared_ptr snoopy = std::make_shared(); - //! Dog* moving = snoopy.get(); + //! include:virtual_ptr.cpp#shared_ctor_move_smart_ptr //! - //! virtual_ptr> p = std::move(snoopy); + //! Move-constructing from a `std::unique_ptr`: //! - //! BOOST_TEST(p.get() == moving); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#unique_ctor_move_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1396,19 +1296,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! const virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p = snoopy; - //! - //! BOOST_TEST(snoopy.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_const_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1433,22 +1321,15 @@ class virtual_ptr< //! //! `Other` is _not_ required to be a pointer to a polymorphic class. //! - //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); + //! @par Examples //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! Dog* dog = snoopy.get(); + //! Move-constructing from a shared `virtual_ptr`: //! - //! virtual_ptr> p = std::move(snoopy); + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_move_vptr //! - //! BOOST_TEST(p.get() == dog); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! @endcode + //! Move-constructing from a unique `virtual_ptr`: + //! + //! include:virtual_ptr.cpp#unique_ctor_move_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1475,19 +1356,7 @@ class virtual_ptr< //! v-table pointer to `nullptr`. //! //! @par Example - //! @code - //! struct Dog {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Dog); - //! initialize(); - //! - //! virtual_ptr> p = make_shared_virtual(); - //! - //! p = nullptr; - //! - //! BOOST_TEST(p.get() == nullptr); - //! BOOST_TEST(p.vptr() == nullptr); - //! BOOST_TEST((p == virtual_ptr>())); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_nullptr //! //! @param value A `nullptr`. virtual_ptr& operator=(std::nullptr_t) { @@ -1502,17 +1371,7 @@ class virtual_ptr< //! according to the dynamic type of `*other`. //! //! @par Example - //! @code - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p; - //! - //! p = snoopy; - //! - //! BOOST_TEST(p.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_assign_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1540,19 +1399,15 @@ class virtual_ptr< //! Move object pointer from `other` to `this`. Set the v-table pointer //! according to the dynamic type of `*other`. //! - //! @par Example - //! @code - //! virtual_ptr> snoopy = make_shared_virtual(); - //! Dog* moving = snoopy.get(); - //! virtual_ptr> p; + //! @par Examples //! - //! p = std::move(snoopy); + //! Move-assigning from a `std::shared_ptr`: //! - //! BOOST_TEST(p.get() == moving); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! BOOST_TEST(snoopy.vptr() == nullptr); - //! @endcode + //! include:virtual_ptr.cpp#shared_assign_move_smart_ptr + //! + //! Move-assigning from a `std::unique_ptr`: + //! + //! include:virtual_ptr.cpp#unique_assign_move_smart_ptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1582,22 +1437,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p; - //! - //! p = snoopy; - //! - //! BOOST_TEST(p.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1626,22 +1466,7 @@ class virtual_ptr< //! `Other` is _not_ required to be a pointer to a polymorphic class. //! //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); - //! - //! const virtual_ptr> snoopy = make_shared_virtual(); - //! virtual_ptr> p; - //! - //! p = snoopy; - //! - //! BOOST_TEST(p.get() != nullptr); - //! BOOST_TEST(p.get() == snoopy.get()); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - //! @endcode + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_const_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1668,25 +1493,15 @@ class virtual_ptr< //! //! `Other` is _not_ required to be a pointer to a polymorphic class. //! - //! @par Example - //! @code - //! struct Animal {}; // polymorphism not required - //! struct Dog : Animal {}; // polymorphism not required - //! BOOST_OPENMETHOD_CLASSES(Animal, Dog); - //! initialize(); + //! @par Examples //! - //! virtual_ptr> snoopy = - //! make_shared_virtual(); - //! Dog* moving = snoopy.get(); - //! virtual_ptr> p; + //! Move-assigning from a shared `virtual_ptr`: //! - //! p = std::move(snoopy); + //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_assign_move_vptr //! - //! BOOST_TEST(p.get() == moving); - //! BOOST_TEST(p.vptr() == default_registry::static_vptr); - //! BOOST_TEST(snoopy.get() == nullptr); - //! BOOST_TEST(snoopy.vptr() == nullptr); - //! @endcode + //! Move-assigning from a unique `virtual_ptr`: + //! + //! include:virtual_ptr.cpp#unique_assign_move_vptr //! //! @par Requirements //! @li @c SmartPtr and @c Other must be instantiated from the same template - @@ -1777,7 +1592,8 @@ class virtual_ptr< traits::template cast(std::move(obj)), vp); } - //! Construct a `virtual_ptr` from a smart pointer to an object + //! Construct a `virtual_ptr` from a smart pointer to an object of a known + //! exact class //! //! This function forwards to @ref final_virtual_ptr. //! @@ -2093,7 +1909,7 @@ struct validate_method_parameter< //! //! The default value for `Registry` is @ref default_registry, but it can be //! overridden by defining the preprocessor symbol -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}}, *before* including +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, *before* including //! ``. Setting the symbol afterwards has no effect. //! //! Specializations of `method` have a single instance: the static member `fn`, @@ -2148,6 +1964,8 @@ struct validate_method_parameter< //! @tparam Id A type //! @tparam Fn A function type //! @tparam Registry The registry in which the method is defined +//! +//! @see [Core API](xref:ROOT:core_api.adoc) template< typename Id, typename Fn, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY> @@ -2341,7 +2159,7 @@ class method void resolve_type_ids(); - template + template auto vptr(const ArgType& arg) const -> vptr_type; template @@ -2495,7 +2313,7 @@ method::operator()( typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS StripVirtualDecorator::type... args) const -> ReturnType { using namespace detail; - auto pf = resolve(parameter_traits::peek(args)...); + auto pf = resolve(args...); return pf(std::forward::type>( args)...); @@ -2527,13 +2345,23 @@ BOOST_FORCEINLINE template< typename Id, typename... Parameters, typename ReturnType, class Registry> -template +template BOOST_FORCEINLINE auto method::vptr( const ArgType& arg) const -> vptr_type { if constexpr (detail::is_virtual_ptr) { return arg.vptr(); } else { - return detail::acquire_vptr(arg); + decltype(auto) obj = virtual_traits::peek(arg); + + if constexpr (detail::has_vptr_fn) { + return boost_openmethod_vptr(obj, static_cast(nullptr)); + } else if constexpr (detail::has_vptr< + virtual_traits, + decltype(obj)>) { + return virtual_traits::vptr(obj); + } else { + return Registry::template policy::dynamic_vptr(obj); + } } } @@ -2550,7 +2378,7 @@ method::resolve_uni( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr(arg); + vptr_type vtbl = vptr>>(arg); return vtbl[this->slots_strides[0]]; } else { return resolve_uni>(more_args...); @@ -2569,7 +2397,7 @@ method::resolve_multi_first( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr(arg); + vptr_type vtbl = vptr>>(arg); std::size_t slot = this->slots_strides[0]; // The first virtual parameter is special. Since its stride is @@ -2599,7 +2427,7 @@ method::resolve_multi_next( using namespace boost::mp11; if constexpr (is_virtual>::value) { - vptr_type vtbl = vptr(arg); + vptr_type vtbl = vptr>>(arg); std::size_t slot = this->slots_strides[VirtualArg]; std::size_t stride = this->slots_strides[Arity + VirtualArg - 1]; dispatch = dispatch + vtbl[slot].i * stride; @@ -2907,6 +2735,35 @@ struct VirtualTraits { //! @return A reference to an object. static auto peek(T arg) -> const virtual_type&; + // Added by the `std::any` interop, under the name `type_vptr`. An `any` + // dispatches on the type of the value it contains, which the rtti policy + // cannot see: `dynamic_type` on the `any` itself yields the wrapper. + + //! Returns a *reference* to the v-table pointer for an object. + //! + //! `vptr` is optional. It is called on the object returned by @ref peek, + //! not on the method argument itself. A method acquires the v-table + //! pointer of a virtual argument from the first of the following that is + //! available: a `boost_openmethod_vptr` function, found by ADL on the + //! peeked object; `vptr`; @ref policies::VptrFn::dynamic_vptr of the + //! registry's @ref policies::vptr policy. + //! + //! Implement `vptr` only if the v-table pointer cannot be obtained from + //! the dynamic type of the peeked object, as reported by the registry's + //! @ref policies::rtti policy. This is the case for `any`-like types: + //! their dynamic type is the wrapper, not the value they contain. The + //! `std::any` specializations read the @ref type_id of the contained + //! value from `arg.type()`, and pass it to + //! @ref policies::VptrFn::vptr. + //! + //! `vptr` must return a *reference*, not a value, so that the caller + //! observes the current v-table pointer if the registry contains the + //! @ref policies::indirect_vptr policy and `initialize` is called again. + //! + //! @param arg The object returned by @ref peek. + //! @return A reference to the v-table pointer for `arg`. + static auto vptr(const virtual_type& arg) -> const vptr_type&; + //! Casts a virtual argument. //! //! `cast` is responsible for passing virtual arguments from method to diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 96317411..9df3c7bc 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -18,7 +18,7 @@ namespace boost::openmethod { //! Default registry. //! //! `default_registry` is a predefined @ref registry, and the default value of -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}}. +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. //! It contains the following policies: //! @li @ref policies::std_rtti: Use standard RTTI. //! @li @ref policies::fast_perfect_hash: Use a fast perfect hash function to @@ -27,8 +27,7 @@ namespace boost::openmethod { //! @li @ref policies::default_error_handler: Write short diagnostic messages. //! @li @ref policies::stderr_output: Write messages to @c stderr. //! -//! If -//! {{BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS}} +//! If @ref BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS //! is defined, `default_registry` also includes the @ref runtime_checks policy. //! //! @note Use `BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS` with caution, as @@ -38,8 +37,8 @@ namespace boost::openmethod { //! //! For a program and its shared libraries to contribute to the same //! `default_registry`, its state must be shared across the modules, with -//! {{BOOST_OPENMETHOD_IMPORT_REGISTRY}}, {{BOOST_OPENMETHOD_EXPORT_REGISTRY}} -//! and {{BOOST_OPENMETHOD_INSTANTIATE_REGISTRY}}: +//! @ref BOOST_OPENMETHOD_IMPORT_REGISTRY, @ref BOOST_OPENMETHOD_EXPORT_REGISTRY +//! and @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY: //! @code //! // header, every translation unit of a client module: //! BOOST_OPENMETHOD_IMPORT_REGISTRY(boost::openmethod::default_registry); @@ -75,9 +74,51 @@ static odr_check default_registry_odr_check_instance; //! Share it across shared libraries exactly as for @ref default_registry, //! naming `indirect_registry` in the macros. //! -//! @see indirect_vptr. +//! @see @ref policies::indirect_vptr struct indirect_registry : default_registry::with {}; } // namespace boost::openmethod +// The library only tests BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS, it never +// defines it - that is up to the program. MrDocs extracts macros from +// `#define` directives, so give it one to extract. It is placed after +// `default_registry`, whose definition tests the macro, so that documenting it +// cannot change what is documented. +#ifdef __MRDOCS__ +#ifndef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS +//! Enable runtime checks in @ref boost::openmethod::default_registry. +//! +//! May be defined by a program before including +//! `` to enable runtime checks. See +//! @ref boost::openmethod::default_registry for details. +//! +//! @par Example +//! +//! Define the symbol before including the library, or on the compiler command +//! line: +//! +//! @code +//! #define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS +//! #include +//! @endcode +//! +//! @note The error goes to the registry's +//! @ref boost::openmethod::policies::error_handler policy, which writes the +//! description shown in the comments; the program is then terminated. A +//! handler may throw instead, to keep the program running. +//! +//! The checks catch what @ref boost::openmethod::initialize cannot. Below, +//! `Bulldog` is never registered; nothing is amiss until a call passes one, +//! and only then is @ref boost::openmethod::missing_class reported: +//! +//! include:errors_missing_class_call.cpp#classes;use +//! +//! Without the checks the same call proceeds on a v-table pointer that was +//! never set up, and the behavior is undefined. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) +#define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS +#endif +#endif + #endif diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 2890050d..1feb155d 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -1838,7 +1838,7 @@ void registry::compiler::print( //! //! Initialize the @ref registry passed as an explicit function template //! argument, or @ref default_registry if the registry is not specified. The -//! default can be changed by defining {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}}. +//! default can be changed by defining @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. //! Option objects can be passed to change the behavior of the function. //! Currently two options exist: //! @li @ref trace Enable tracing of the initialization process. @@ -1889,24 +1889,10 @@ void registry::compiler::print( //! the program again after setting environment variable //! `BOOST_OPENMETHOD_TRACE` to `1` to troubleshoot. //! -//! @code -//! #include +//! include:initialize.cpp#report //! -//! #include -//! #include -//! -//! int main() { -//! namespace bom = boost::openmethod; -//! auto report = bom::initialize(bom::trace::from_env()).report; -//! -//! if (report.not_implemented != 0 || report.ambiguous != 0) { -//! std::cerr << "missing overriders or ambiguous methods\n"; -//! return 1; -//! } -//! -//! // ... -//! } -//! @endcode +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) template inline auto initialize(Options&&... options) { if (detail::odr_check::count > 1) { @@ -1987,6 +1973,8 @@ auto registry::finalize(Options... opts) -> void { //! @tparam Options... Zero or more option types, deduced from the function //! arguments. //! @param options Zero or more option objects. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) template inline auto finalize(Options&&... opts) -> void { Registry::finalize(std::forward(opts)...); diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index 0ddf5340..cf1328b0 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -60,15 +60,15 @@ class inplace_vptr_base_tag {}; //! Embed a v-table pointer in a class. //! -//! `inplace_vptr_base` is a [CRTP -//! mixin](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) -//! that embeds a v-table pointer at the root of a class hierarchy. It also -//! declares a @ref boost_openmethod_vptr free function that returns the v-table -//! pointer stored in the object. +//! `inplace_vptr_base` is a +//! [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +//! mixin that embeds a v-table pointer at the root of a class hierarchy. It +//! also declares a @ref boost_openmethod_vptr free function that returns the +//! v-table pointer stored in the object. //! //! `inplace_vptr_base` registers the class in `Registry`. It is not necessary //! to register the class with @ref use_class or -//! {{BOOST_OPENMETHOD_REGISTER}} +//! @ref BOOST_OPENMETHOD_REGISTER. //! //! The v-table pointer is obtained directly from the `Registry`\'s @ref //! static_vptr variable. No hashing is involved. If all the classes in @@ -76,54 +76,25 @@ class inplace_vptr_base_tag {}; //! @ref policies::vptr policy, nor any policy it depends on (like @ref //! policies::type_hash). //! +//! An object that embeds its v-table pointer does not need to be wrapped +//! in a @ref virtual_ptr - the two fill the same goal, fast access to the +//! v-table pointer - and wrapping one is rejected at compile time. +//! //! If `Registry` contains the @ref has_indirect_vptr policy, the v-table //! pointer is stored as a pointer to a pointer, and remains valid after a call //! to @ref initialize. //! //! The default value of `Registry` can be changed by defining -//! {{BOOST_OPENMETHOD_DEFAULT_REGISTRY}} +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY. //! //! @tparam Class The class in which to embed the v-table pointer. //! @tparam Registry The @ref registry in which `Class` and its derived classes //! are registered. //! //! @par Example -//! @code -//! #include -//! #include -//! #include -//! -//! using namespace boost::openmethod; -//! -//! struct Animal : inplace_vptr_base {}; -//! -//! struct Cat : Animal, inplace_vptr_derived {}; -//! -//! struct Dog : Animal, inplace_vptr_derived {}; -//! -//! BOOST_OPENMETHOD( -//! poke, (virtual_ animal, std::ostream& os), void); -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (Cat&, std::ostream& os), void) { -//! os << "hiss\n"; -//! } +//! include:inplace_vptr.cpp#classes;dispatch //! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&, std::ostream& os), void) { -//! os << "bark\n"; -//! } -//! -//! int main() { -//! initialize(); -//! -//! std::unique_ptr a = std::make_unique(); -//! std::unique_ptr b = std::make_unique(); -//! -//! poke(*a, std::cout); // hiss -//! poke(*b, std::cout); // bark -//! -//! return 0; -//! } -//! @endcode +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) template class inplace_vptr_base : protected detail::inplace_vptr_base_tag { template @@ -159,15 +130,15 @@ class inplace_vptr_base : protected detail::inplace_vptr_base_tag { #ifdef __MRDOCS__ //! Adjust the v-table pointer embedded in a class. //! -//! `inplace_vptr_derived` is a [CRTP -//! mixin](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) -//! that adjusts the v-table pointer in a @ref inplace_vptr_base. It can be used -//! only with classes that have @ref inplace_vptr_base as a direct or indirect -//! base class. +//! `inplace_vptr_derived` is a +//! [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +//! mixin that adjusts the v-table pointer in a @ref inplace_vptr_base. It can +//! be used only with classes that have @ref inplace_vptr_base as a direct or +//! indirect base class. //! //! `inplace_vptr_derived` registers the class and its bases in `Registry`. It //! is not necessary to register them with @ref use_class or -//! {{BOOST_OPENMETHOD_REGISTER}} +//! @ref BOOST_OPENMETHOD_REGISTER. //! //! The v-table pointer is obtained directly from the `Registry`\'s @ref //! static_vptr variable. No hashing is involved. If all the classes in @@ -175,11 +146,15 @@ class inplace_vptr_base : protected detail::inplace_vptr_base_tag { //! @ref policies::vptr policy, nor any policy it depends on (like @ref //! policies::type_hash). //! -//! @see @ref inplace_vptr_base for an example. +//! @ref inplace_vptr_base carries an example. +//! +//! @see @ref inplace_vptr_base //! //! @tparam Class The class in which to embed the v-table pointer. //! @tparam Base A direct base class of `Class`. //! @tparam MoreBases More direct base classes of `Class`. +//! +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) template class inplace_vptr_derived { protected: @@ -200,7 +175,7 @@ class inplace_vptr_derived; //! Specialization for a single base class. //! //! -//! @see The main template for documentation. +//! @see @ref inplace_vptr_derived for documentation. template class inplace_vptr_derived { static_assert( @@ -233,7 +208,7 @@ class inplace_vptr_derived { //! Specialization for multiple base classes. //! -//! @see The main template for documentation. +//! @see @ref inplace_vptr_derived for documentation. template class inplace_vptr_derived { static_assert( diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp new file mode 100644 index 00000000..e8d58473 --- /dev/null +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -0,0 +1,320 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_INTEROP_BOOST_ANY_HPP +#define BOOST_OPENMETHOD_INTEROP_BOOST_ANY_HPP + +#include +#include +#include + +namespace boost::openmethod { + +namespace detail { + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +} // namespace detail + +//! Specialize virtual_traits for `const boost::any&` (const reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `boost::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = boost::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `boost::any`. + //! @return A const reference to `arg`. + static auto peek(const boost::any& arg) -> const boost::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the dynamic @ref type_id of the value stored in `arg`, using + //! `boost::any::type()`. This requires the registry's @ref rtti policy to + //! identify classes by `&typeid(T)`, as @ref std_rtti does; + //! `boost::any::type()` yields the same `std::type_info` object, provided + //! Boost.TypeIndex uses `stl_type_index`. + //! + //! Passes the type id to the registry's @ref policies::vptr policy, which + //! must provide @ref policies::VptrFn::vptr. Both + //! @ref policies::vptr_vector and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const boost::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `boost::any_cast`. + //! + //! Since the `any` argument is const, `U` cannot be a mutable reference. + //! `boost::any_cast` rewrites `U` to a const reference for a const `any`, + //! and would fail inside Boost.Any; this overload is removed from the + //! overload set instead. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg A reference to a const `boost::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + !std::is_reference_v || + std::is_const_v>>> + static auto cast(const boost::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `boost::any&` (mutable reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `boost::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = boost::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `boost::any`. + //! @return A const reference to `arg`. + static auto peek(const boost::any& arg) -> const boost::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the dynamic @ref type_id of the value stored in `arg`, using + //! `boost::any::type()`. This requires the registry's @ref rtti policy to + //! identify classes by `&typeid(T)`, as @ref std_rtti does; + //! `boost::any::type()` yields the same `std::type_info` object, provided + //! Boost.TypeIndex uses `stl_type_index`. + //! + //! Passes the type id to the registry's @ref policies::vptr policy, which + //! must provide @ref policies::VptrFn::vptr. Both + //! @ref policies::vptr_vector and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a `boost::any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const boost::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `boost::any_cast`. Supports mutable + //! references (e.g. `Dog&`) because the `any` argument is not const; + //! modifications through the result are visible through the `any`. + //! + //! `U` cannot be an rvalue reference. Unlike `std::any_cast`, + //! `boost::any_cast` binds an rvalue reference to the value stored in an + //! lvalue `any`; moving the value out must go through an explicit + //! `virtual_` parameter, so this overload is removed from + //! the overload set. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg A mutable reference to the `boost::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template< + typename U, typename = std::enable_if_t>> + static auto cast(boost::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return (arg); + } else { + return boost::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `boost::any&&` (xvalue reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `boost::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = boost::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `boost::any`. + //! @return A const reference to `arg`. + static auto peek(const boost::any& arg) -> const boost::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the dynamic @ref type_id of the value stored in `arg`, using + //! `boost::any::type()`. This requires the registry's @ref rtti policy to + //! identify classes by `&typeid(T)`, as @ref std_rtti does; + //! `boost::any::type()` yields the same `std::type_info` object, provided + //! Boost.TypeIndex uses `stl_type_index`. + //! + //! Passes the type id to the registry's @ref policies::vptr policy, which + //! must provide @ref policies::VptrFn::vptr. Both + //! @ref policies::vptr_vector and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a `boost::any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const boost::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `boost::any_cast`. + //! + //! `U` cannot be a mutable lvalue reference: that would bind a reference + //! to the value contained in a temporary. Boost.Any rejects it with a + //! static assertion; this overload is removed from the overload set + //! instead, for consistency with the other reference categories. + //! + //! @tparam U The target type (e.g. `Dog&&`, `const Dog&`, `Dog`). + //! @param arg An rvalue reference to the `boost::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template< + typename U, + typename = std::enable_if_t< + !std::is_lvalue_reference_v || + std::is_const_v>>> + static auto cast(boost::any&& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + boost::any>) { + return std::move(arg); + } else { + return boost::any_cast(std::move(arg)); + } + } +}; + +//! Register the types that a `boost::any` virtual parameter may contain. +//! +//! Registers `boost::any` as a class, and each `T` as a class derived from +//! `boost::any`. This makes the contained types visible to the dispatch +//! machinery, which resolves a call on the `type_id` returned by +//! `boost::any::type()`. +//! +//! The root class is `boost::any`, distinct from the one used by +//! @ref use_std_any_types for `std::any`, so both may be used in the same +//! program, and with the same registry. +//! +//! @tparam T... The types that may be stored in the `any`, optionally +//! followed by a @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#boost_classes;boost_dispatch +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +struct use_boost_any_types + : detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>, + detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>... {}; + +//! Alias for a `virtual_any`, in the default registry. +//! +//! With another registry, use `virtual_any` directly. +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +using virtual_boost_any = virtual_any; + +// The primary final_virtual_ptr would silently use static_vptr +// - the v-table of the `any` root class, not of the contained value. +// Delete the combination. Both call forms need covering: the non-template +// overloads catch calls that deduce the default registry, and are removed +// from consideration when an explicit template argument list is given, so +// the Registry-only templates - more specialized than the primary - catch +// those. +// +// Hidden from the reference: they are a guard, not API, and six deleted +// overloads would crowd the `final_virtual_ptr` overload list. + +#ifndef __MRDOCS__ +template +void final_virtual_ptr(const boost::any&) = delete; +template +void final_virtual_ptr(boost::any&) = delete; +template +void final_virtual_ptr(boost::any&&) = delete; +void final_virtual_ptr(const boost::any&) = delete; +void final_virtual_ptr(boost::any&) = delete; +void final_virtual_ptr(boost::any&&) = delete; +#endif + +namespace aliases { +using boost::openmethod::use_boost_any_types; +using boost::openmethod::virtual_boost_any; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp index 5ab68294..a7ee68f2 100644 --- a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp +++ b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp @@ -14,8 +14,13 @@ namespace boost::openmethod { //! Specialize virtual_traits for boost::intrusive_ptr. //! +//! @par Example +//! include:intrusive_ptr.cpp#classes;by_value +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits, Registry> { //! Rebind to a different element type. @@ -60,8 +65,13 @@ struct virtual_traits, Registry> { //! Specialize virtual_traits for const boost::intrusive_ptr&. //! +//! @par Example +//! include:intrusive_ptr.cpp#classes;by_reference +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits&, Registry> { public: @@ -114,6 +124,11 @@ struct virtual_traits&, Registry> { }; //! Alias for a `virtual_ptr>`. +//! +//! @par Example +//! include:intrusive_ptr.cpp#boost_intrusive_virtual_ptr_alias +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using boost_intrusive_virtual_ptr = virtual_ptr, Registry>; @@ -132,6 +147,11 @@ using boost_intrusive_virtual_ptr = //! @param args Arguments to pass to the constructor of `Class`. //! @return A `boost_intrusive_virtual_ptr` pointing to a newly //! created object of type `Class`. +//! +//! @par Example +//! include:intrusive_ptr.cpp#make_boost_intrusive_virtual +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp new file mode 100644 index 00000000..54772417 --- /dev/null +++ b/include/boost/openmethod/interop/std_any.hpp @@ -0,0 +1,283 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_INTEROP_STD_ANY_HPP +#define BOOST_OPENMETHOD_INTEROP_STD_ANY_HPP + +#include +#include +#include + +namespace boost::openmethod { + +namespace detail { + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +template +struct validate_method_parameter, Registry, void> + : std::true_type {}; + +} // namespace detail + +//! Specialize virtual_traits for `const std::any&` (const reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = std::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `std::any`. + //! @return A const reference to `arg`. + static auto peek(const std::any& arg) -> const std::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. + //! + //! Passes it to the registry's @ref policies::vptr policy, which must + //! provide @ref policies::VptrFn::vptr. Both @ref policies::vptr_vector + //! and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const std::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself (by any reference category), returns + //! `arg` unchanged, which is how a catch-all overrider is written. + //! Otherwise, extracts the stored value using `std::any_cast`. Since + //! the `any` argument is const, `U` cannot be a mutable reference. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg A reference to a const `std::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(const std::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `std::any&` (mutable reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = std::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `std::any`. + //! @return A const reference to `arg`. + static auto peek(const std::any& arg) -> const std::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. + //! + //! Passes it to the registry's @ref policies::vptr policy, which must + //! provide @ref policies::VptrFn::vptr. Both @ref policies::vptr_vector + //! and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a `std::any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const std::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself, returns `arg` unchanged, which is how a + //! catch-all overrider is written. Otherwise, extracts the stored value + //! using `std::any_cast`. Supports mutable references (e.g. `Dog&`) + //! because the `any` argument is not const; modifications through the + //! result are visible through the `any`. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg A mutable reference to the `std::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(std::any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return (arg); + } else { + return std::any_cast(arg); + } + } +}; + +//! Specialize virtual_traits for `std::any&&` (xvalue reference). +//! +//! Dispatch is based on the runtime type of the value stored in the `any`, +//! obtained via `std::any::type()`. +//! +//! @tparam Registry A @ref registry. +template +struct virtual_traits { + //! The type used for dispatch. + using virtual_type = std::any; + + //! Returns a const reference to the `any` argument. + //! @param arg A reference to a `std::any`. + //! @return A const reference to `arg`. + static auto peek(const std::any& arg) -> const std::any& { + return arg; + } + + //! Returns a *reference* to a v-table pointer for an object. + //! + //! Acquires the @ref type_id of the value stored in `arg`, using + //! `std::any::type()`. + //! + //! Passes it to the registry's @ref policies::vptr policy, which must + //! provide @ref policies::VptrFn::vptr. Both @ref policies::vptr_vector + //! and @ref policies::vptr_map do. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param arg A reference to a const `any`. + //! @return A reference to the v-table pointer for the stored value. + static auto vptr(const std::any& arg) -> const vptr_type& { + return Registry::vptr::vptr(&arg.type()); + } + + //! Cast to a type. + //! + //! If `U` is the `any` itself, returns `arg` unchanged, which is how a + //! catch-all overrider is written. Otherwise, extracts the stored value + //! using `std::any_cast`. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg An rvalue reference to the `std::any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(std::any&& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + std::any>) { + return std::move(arg); + } else { + return std::any_cast(std::move(arg)); + } + } +}; + +//! Register the types that a `std::any` virtual parameter may contain. +//! +//! Registers `std::any` as a class, and each `T` as a class derived from +//! `std::any`. This makes the contained types visible to the dispatch +//! machinery, which resolves a call on the `type_id` returned by +//! `std::any::type()`. +//! +//! @tparam T... The types that may be stored in the `any`, optionally +//! followed by a @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#classes +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +struct use_std_any_types + : detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>, + detail::use_class_aux< + typename detail::extract_registry::registry, + mp11::mp_list>... {}; + +//! Alias for a `virtual_any`, in the default registry. +//! +//! With another registry, use `virtual_any` directly. +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +using virtual_std_any = virtual_any; + +// The primary final_virtual_ptr would silently use static_vptr +// - the v-table of the `any` root class, not of the contained value. +// Delete the combination. Both call forms need covering: the non-template +// overloads catch calls that deduce the default registry, and are removed +// from consideration when an explicit template argument list is given, so +// the Registry-only templates - more specialized than the primary - catch +// those. +// +// Hidden from the reference: they are a guard, not API, and six deleted +// overloads would crowd the `final_virtual_ptr` overload list. + +#ifndef __MRDOCS__ +template +void final_virtual_ptr(const std::any&) = delete; +template +void final_virtual_ptr(std::any&) = delete; +template +void final_virtual_ptr(std::any&&) = delete; +void final_virtual_ptr(const std::any&) = delete; +void final_virtual_ptr(std::any&) = delete; +void final_virtual_ptr(std::any&&) = delete; +#endif + +namespace aliases { +using boost::openmethod::use_std_any_types; +using boost::openmethod::virtual_std_any; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index d0ac4f9e..1a804f57 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -53,8 +53,13 @@ struct validate_method_parameter< //! Specialize virtual_traits for std::shared_ptr by value. //! +//! @par Example +//! include:smart_pointers.cpp#classes;shared_by_value +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits, Registry> { //! Rebind to a different element type. @@ -131,6 +136,9 @@ struct virtual_traits, Registry> { //! Specialize virtual_traits for std::shared_ptr by reference. //! +//! @par Example +//! include:smart_pointers.cpp#classes;shared_by_reference +//! //! @note Passing a `std::shared_ptr` in a method call by const reference //! creates a temporary `std::shared_ptr` and passes it by const reference to //! the overrider. This is necessary because virtual arguments need to be cast @@ -138,6 +146,8 @@ struct virtual_traits, Registry> { //! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits&, Registry> { public: @@ -186,6 +196,11 @@ struct virtual_traits&, Registry> { }; //! Alias for a `virtual_ptr>`. +//! +//! @par Example +//! include:smart_pointers.cpp#shared_virtual_ptr_alias +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using shared_virtual_ptr = virtual_ptr, Registry>; @@ -203,6 +218,11 @@ using shared_virtual_ptr = virtual_ptr, Registry>; //! @param args Arguments to pass to the constructor of `Class`. //! @return A `shared_virtual_ptr` pointing to a newly //! created object of type `Class`. +//! +//! @par Example +//! include:smart_pointers.cpp#make_shared_virtual +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/std_unique_ptr.hpp b/include/boost/openmethod/interop/std_unique_ptr.hpp index bcda19fa..c136943f 100644 --- a/include/boost/openmethod/interop/std_unique_ptr.hpp +++ b/include/boost/openmethod/interop/std_unique_ptr.hpp @@ -14,8 +14,13 @@ namespace boost::openmethod { //! Specialize virtual_traits for std::unique_ptr by value. //! +//! @par Example +//! include:smart_pointers.cpp#classes;unique_by_value +//! //! @tparam Class A class type, possibly cv-qualified. //! @tparam Registry A @ref registry. +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template struct virtual_traits, Registry> { //! `Class`, stripped from cv-qualifiers. @@ -62,6 +67,11 @@ struct virtual_traits, Registry> { }; //! Alias for a `virtual_ptr>`. +//! +//! @par Example +//! include:smart_pointers.cpp#unique_virtual_ptr_alias +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template using unique_virtual_ptr = virtual_ptr, Registry>; @@ -79,6 +89,11 @@ using unique_virtual_ptr = virtual_ptr, Registry>; //! @param args Arguments to pass to the constructor of `Class`. //! @return A `unique_virtual_ptr` pointing to a newly //! created object of type `Class`. +//! +//! @par Example +//! include:smart_pointers.cpp#make_unique_virtual +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template< class Class, class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T> diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp new file mode 100644 index 00000000..109e1b00 --- /dev/null +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -0,0 +1,741 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_INTEROP_VIRTUAL_ANY_HPP +#define BOOST_OPENMETHOD_INTEROP_VIRTUAL_ANY_HPP + +#include + +#include +#include + +namespace boost::openmethod { + +template +class virtual_any; + +template +class virtual_any_ref; + +namespace detail { + +template +struct is_virtual_any_aux : std::false_type {}; + +template +struct is_virtual_any_aux> : std::true_type {}; + +template +struct is_virtual_any_aux> : std::true_type {}; + +} // namespace detail + +//! A wide `any`, combining an `any` and a pointer to a v-table. +//! +//! `virtual_any` is to `any` what @ref virtual_ptr is to a pointer: it +//! carries the v-table pointer for the value stored in the `any`, so +//! methods dispatch on the contained type without looking it up on every +//! call. Unlike `virtual_ptr`, it *owns* its object: the `any` is held by +//! value. +//! +//! The v-table pointer is acquired when the `virtual_any` is created: +//! either from the dynamic type of an existing `any` (a hash table +//! lookup, via `virtual_traits::vptr`), or +//! statically, when the contained type is known at compile time (the +//! value constructor and @ref emplace use @ref registry::static_vptr). +//! +//! Methods take `virtual_any` parameters by reference: `const +//! virtual_any&`, `virtual_any&` or `virtual_any&&`. Overriders receive +//! the *contained* type, by a reference of a compatible category - or the +//! `virtual_any` itself, unchanged, for a catch-all overrider. +//! +//! The contained value cannot be replaced through a `virtual_any` other +//! than via assignment or @ref emplace, which re-derive the v-table +//! pointer, thus maintaining the invariant that the v-table pointer +//! corresponds to the contained type. +//! +//! `Any` can be `std::any`, `boost::any`, or any type that has an +//! `any`-like interface, and specializes `virtual_traits` for its +//! reference types, providing `vptr` and `cast`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#classes;method;dispatch +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +class virtual_any { + static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; + + Any obj; + std::conditional_t vp; + + template + friend struct virtual_traits; + + template + friend class virtual_any_ref; + + public: + //! Construct an empty `virtual_any`. + //! + //! The `any` is empty, and the v-table pointer is null. + virtual_any() + : obj(), vp(detail::box_vptr(detail::null_vptr)) { + } + + //! Construct from an `any` (copy). + //! + //! Copies `other`, and acquires the v-table pointer for the contained + //! value, using `virtual_traits::vptr`. + //! + //! @param other An `any`. + //! + //! @par Example + //! include:virtual_any.cpp#from_any + virtual_any(const Any& other) + : obj(other), vp(detail::box_vptr( + detail::acquire_vptr(obj))) { + } + + //! Construct from an `any` (move). + //! + //! Moves `other`, and acquires the v-table pointer for the contained + //! value, using `virtual_traits::vptr`. + //! + //! @param other An `any`. + virtual_any(Any&& other) + : obj(std::move(other)), vp(detail::box_vptr( + detail::acquire_vptr(obj))) { + } + + //! Construct from a value. + //! + //! Stores `value` in the `any`, and sets the v-table pointer to the + //! @ref registry::static_vptr for its type - no hash table lookup is + //! involved. The type of `value`, stripped from reference and + //! cv-qualifiers, must be registered in `Registry`. + //! + //! @tparam T The type of the value. + //! @param value The value to store. + //! + //! @par Example + //! include:virtual_any.cpp#from_value + template< + typename T, + typename = std::enable_if_t< + !detail::is_virtual_any_aux>::value && + !std::is_same_v, Any> && + std::is_constructible_v>> + virtual_any(T&& value) + : obj(std::forward(value)), + vp(detail::box_vptr( + Registry::template static_vptr>)) { + Registry::require_initialized(); + BOOST_ASSERT(detail::unbox_vptr(vp) != nullptr); + } + + //! Copy constructor. + virtual_any(const virtual_any& other) = default; + + //! Move constructor. + //! + //! Moves the `any`, and sets `other`'s v-table pointer to null. + //! + //! @param other A `virtual_any`. + virtual_any(virtual_any&& other) : obj(std::move(other.obj)), vp(other.vp) { + other.vp = detail::box_vptr(detail::null_vptr); + } + + //! Copy assignment operator. + auto operator=(const virtual_any& other) -> virtual_any& = default; + + //! Move assignment operator. + //! + //! Moves the `any`, and sets `other`'s v-table pointer to null. + //! + //! @param other A `virtual_any`. + auto operator=(virtual_any&& other) -> virtual_any& { + obj = std::move(other.obj); + vp = other.vp; + other.vp = detail::box_vptr(detail::null_vptr); + return *this; + } + + //! Assign from an `any` (copy). + //! + //! Copies `other`, and re-acquires the v-table pointer for the + //! contained value. + //! + //! @param other An `any`. + auto operator=(const Any& other) -> virtual_any& { + obj = other; + vp = detail::box_vptr( + detail::acquire_vptr(obj)); + return *this; + } + + //! Assign from an `any` (move). + //! + //! Moves `other`, and re-acquires the v-table pointer for the + //! contained value. + //! + //! @param other An `any`. + auto operator=(Any&& other) -> virtual_any& { + obj = std::move(other); + vp = detail::box_vptr( + detail::acquire_vptr(obj)); + return *this; + } + + //! Assign from a value. + //! + //! Stores `value` in the `any`, and sets the v-table pointer to the + //! @ref registry::static_vptr for its type - no hash table lookup is + //! involved. + //! + //! @tparam T The type of the value. + //! @param value The value to store. + template< + typename T, + typename = std::enable_if_t< + !detail::is_virtual_any_aux>::value && + !std::is_same_v, Any> && + std::is_constructible_v>> + auto operator=(T&& value) -> virtual_any& { + obj = std::forward(value); + Registry::require_initialized(); + vp = detail::box_vptr( + Registry::template static_vptr>); + BOOST_ASSERT(detail::unbox_vptr(vp) != nullptr); + return *this; + } + + //! Construct a value in place. + //! + //! Stores a `Class` constructed from `args`, and sets the v-table + //! pointer to the @ref registry::static_vptr for `Class` - no hash + //! table lookup is involved. + //! + //! @tparam Class The type of the value to construct. + //! @tparam T Types of the arguments to pass to the constructor. + //! @param args Arguments to pass to the constructor of `Class`. + //! + //! @par Example + //! include:virtual_any.cpp#emplace + template + auto emplace(T&&... args) -> void { + obj = Class(std::forward(args)...); + Registry::require_initialized(); + vp = detail::box_vptr( + Registry::template static_vptr); + BOOST_ASSERT(detail::unbox_vptr(vp) != nullptr); + } + + //! Return a reference to the (non-modifiable) `any`. + auto get() const -> const Any& { + return obj; + } + + //! Return the v-table pointer. + auto vptr() const -> vptr_type { + return detail::unbox_vptr(vp); + } + +#ifndef __MRDOCS__ + // The parameter is deduced, and constrained to be exactly this + // `virtual_any`, so that the function is not viable for a type that + // is merely convertible to it. MSVC, in its default (permissive) + // mode, injects friend functions into the enclosing namespace, where + // ordinary lookup finds them. An unconstrained `const virtual_any&` + // parameter would then make this a candidate for a plain `Any`, + // which converts implicitly to `virtual_any` - and the conversion + // acquires the v-table pointer, which calls this function, ad + // infinitum. + template + friend auto boost_openmethod_vptr(const Self& va, Registry*) + -> std::enable_if_t, vptr_type> { + return detail::unbox_vptr(va.vp); + } +#endif +}; + +//! Specialize virtual_traits for `const virtual_any&`. +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&, Registry> { + //! The type used for dispatch. + using virtual_type = Any; + + //! Returns a const reference to the `virtual_any` argument. + //! @param arg A reference to a `virtual_any`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any& arg) + -> const virtual_any& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any` itself (by any reference category), + //! returns `arg` unchanged. Otherwise, extracts the stored value + //! using `virtual_traits::cast`. Since the + //! `any` is not modifiable, `U` cannot be a mutable reference. + //! + //! @tparam U The target type (e.g. `const Dog&`, `Dog`). + //! @param arg A reference to a const `virtual_any` method argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(const virtual_any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any>) { + return (arg); + } else { + return virtual_traits::template cast( + arg.obj); + } + } +}; + +//! Specialize virtual_traits for `virtual_any&` (mutable reference). +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&, Registry> { + //! The type used for dispatch. + using virtual_type = Any; + + //! Returns a const reference to the `virtual_any` argument. + //! @param arg A reference to a `virtual_any`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any& arg) + -> const virtual_any& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any` itself (by mutable reference), returns + //! `arg` unchanged. Otherwise, extracts the stored value using + //! `virtual_traits::cast`. Supports mutable + //! references (e.g. `Dog&`); modifications through the result are + //! visible through the `virtual_any`. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg A mutable reference to the `virtual_any` method + //! argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(virtual_any& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any>) { + return (arg); + } else { + return virtual_traits::template cast(arg.obj); + } + } +}; + +//! Specialize virtual_traits for `virtual_any&&` (xvalue reference). +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any`. +//! +//! @tparam Any An `any` type. +//! @tparam Registry A @ref registry. +template +struct virtual_traits&&, Registry> { + //! The type used for dispatch. + using virtual_type = Any; + + //! Returns a const reference to the `virtual_any` argument. + //! @param arg A reference to a `virtual_any`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any& arg) + -> const virtual_any& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any` itself (by rvalue reference), returns + //! `arg` unchanged. Otherwise, extracts the stored value using + //! `virtual_traits::cast`. + //! + //! @tparam U The target type (e.g. `Dog&&`, `const Dog&`, `Dog`). + //! @param arg An rvalue reference to the `virtual_any` method + //! argument. + //! @return The value stored in `arg`, cast to `U`. + template + static auto cast(virtual_any&& arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any>) { + return std::move(arg); + } else { + return virtual_traits::template cast( + std::move(arg.obj)); + } + } +}; + +namespace detail { + +template +struct is_virtual&> : std::true_type {}; + +template +struct is_virtual&> : std::true_type {}; + +template +struct is_virtual&&> : std::true_type {}; + +template +struct parameter_traits&, Registry> + : virtual_traits&, Registry> {}; + +template +struct parameter_traits&, Registry> + : virtual_traits&, Registry> {}; + +template +struct parameter_traits&&, Registry> + : virtual_traits&&, Registry> {}; + +template +struct validate_method_parameter< + virtual_any, MethodRegistry, void> : std::false_type { + static_assert( + false_t, "virtual_any must be passed by reference"); +}; + +template +struct validate_method_parameter< + virtual_any&, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +template +struct validate_method_parameter< + const virtual_any&, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +template +struct validate_method_parameter< + virtual_any&&, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +// A virtual_any method parameter places no compile-time constraint on the +// corresponding overrider parameter: the adjustment is delegated entirely +// to virtual_traits::cast, like for virtual_ +// parameters. The exact-pair specializations disambiguate with the +// generic specialization in core.hpp, which is neither more nor +// less specialized than . + +template +struct validate_overrider_parameter&, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + virtual_any&, virtual_any&, void> + : std::true_type {}; + +template +struct validate_overrider_parameter&, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + const virtual_any&, const virtual_any&, void> + : std::true_type {}; + +template +struct validate_overrider_parameter&&, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + virtual_any&&, virtual_any&&, void> + : std::true_type {}; + +template +struct select_overrider_virtual_type_aux< + virtual_any&, Q, Registry> { + using type = virtual_type; +}; + +template +struct select_overrider_virtual_type_aux< + const virtual_any&, Q, Registry> { + using type = virtual_type; +}; + +template +struct select_overrider_virtual_type_aux< + virtual_any&&, Q, Registry> { + using type = virtual_type; +}; + +} // namespace detail + +//! A wide reference to an `any`: a pointer to an `any`, and a pointer to +//! a v-table. +//! +//! `virtual_any_ref` is the non-owning counterpart of @ref virtual_any: +//! it *borrows* an existing `any` instead of holding a copy, and carries +//! the v-table pointer for the contained value, so methods dispatch on +//! the contained type without looking it up on every call. It is a +//! cheap, two-word handle with pointer semantics - copying it copies the +//! two words - and, like the reference-wrapper flavors of +//! Boost.TypeErasure's `any`, it is passed to methods *by value*. +//! +//! `Any` may be const-qualified: through `virtual_any_ref`, +//! overriders can only take the contained value by value or by const +//! reference; `virtual_any_ref` also supports mutable references, +//! and modifications reach the referent. A `virtual_any_ref` +//! converts to a `virtual_any_ref`. +//! +//! The v-table pointer is acquired when the handle is created: from the +//! dynamic type of the value contained in the `any` (a hash table +//! lookup), or at no cost from a @ref virtual_any, which already carries +//! it. The handle does not track its referent: if the value inside the +//! `any` is replaced, the handle is stale - like an iterator into a +//! modified container - and must be re-created. +//! +//! An overrider takes the *contained* value - or, for a catch-all +//! overrider, the `virtual_any_ref` itself, by value. Since a plain +//! value does not convert to a `virtual_any_ref`, overriders taking the +//! contained value are registered with the core API +//! (`method<...>::override`) rather than with +//! @ref BOOST_OPENMETHOD_OVERRIDE, which locates the method by +//! convertibility. +//! +//! `Any` can be `std::any`, `boost::any`, or any type that has an +//! `any`-like interface, and specializes `virtual_traits` for its +//! reference types, providing `vptr` and `cast`. +//! +//! @tparam Any An `any` type, possibly const-qualified. +//! @tparam Registry A @ref registry. +//! +//! @par Example +//! include:virtual_any.cpp#ref;ref_dispatch +//! +//! @see [Interoperation with `any`](xref:ROOT:interop_any.adoc) +template +class virtual_any_ref { + static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; + + using owner_type = std::conditional_t< + std::is_const_v, + const virtual_any, Registry>, + virtual_any, Registry>>; + + Any* obj; + std::conditional_t vp; + + template + friend struct virtual_traits; + + template + friend class virtual_any_ref; + + public: + //! Construct from an `any`. + //! + //! Acquires the v-table pointer for the contained value, using + //! `virtual_traits::vptr` - a hash table + //! lookup. + //! + //! @param other An `any` lvalue. + virtual_any_ref(Any& other) + : obj(&other), vp(detail::box_vptr( + detail::acquire_vptr(other))) { + } + + //! A `virtual_any_ref` cannot borrow a temporary `any`. + virtual_any_ref(std::remove_const_t&&) = delete; + + //! Construct from a `virtual_any`. + //! + //! Borrows the `any` held by `other`, and copies its v-table pointer + //! - no lookup is involved. A `virtual_any_ref` can + //! borrow from a const `virtual_any`; a mutable one requires a + //! mutable `virtual_any`. + //! + //! @param other A `virtual_any` lvalue. + virtual_any_ref(owner_type& other) : obj(&other.obj), vp(other.vp) { + } + + //! A `virtual_any_ref` cannot borrow a temporary `virtual_any`. + virtual_any_ref(std::remove_const_t&&) = delete; + + //! Convert a mutable `virtual_any_ref` to a const one. + template< + class Other, + typename = std::enable_if_t< + std::is_const_v && + std::is_same_v>>> + virtual_any_ref(virtual_any_ref other) + : obj(other.obj), vp(other.vp) { + } + + //! Return a reference to the (non-modifiable) `any`. + auto get() const -> const Any& { + return *obj; + } + + //! Return the v-table pointer. + auto vptr() const -> vptr_type { + return detail::unbox_vptr(vp); + } + +#ifndef __MRDOCS__ + // Constrained to exactly this `virtual_any_ref`, for the same reason + // as in `virtual_any`: MSVC, in its default (permissive) mode, + // injects friend functions into the enclosing namespace, where an + // unconstrained parameter would make this a candidate for anything + // convertible to `virtual_any_ref`. + template + friend auto boost_openmethod_vptr(const Self& va, Registry*) + -> std::enable_if_t, vptr_type> { + return detail::unbox_vptr(va.vp); + } +#endif +}; + +//! Specialize virtual_traits for `virtual_any_ref`, passed by value. +//! +//! Dispatch is on the v-table pointer stored in the `virtual_any_ref`. +//! +//! @tparam Any An `any` type, possibly const-qualified. +//! @tparam Registry A @ref registry. +template +struct virtual_traits, Registry> { + //! The type used for dispatch. + using virtual_type = std::remove_const_t; + + //! Returns a const reference to the `virtual_any_ref` argument. + //! @param arg A reference to a `virtual_any_ref`. + //! @return A const reference to `arg`. + static auto peek(const virtual_any_ref& arg) + -> const virtual_any_ref& { + return arg; + } + + //! Cast to a type. + //! + //! If `U` is the `virtual_any_ref` itself, returns a copy of the + //! handle. Otherwise, extracts the referent's value using the + //! `virtual_traits` for the `any`'s reference type: mutable + //! references (e.g. `Dog&`) are supported unless `Any` is + //! const-qualified. + //! + //! @tparam U The target type (e.g. `Dog&`, `const Dog&`, `Dog`). + //! @param arg The `virtual_any_ref` method argument. + //! @return The value referred to by `arg`, cast to `U`. + template + static auto cast(virtual_any_ref arg) -> decltype(auto) { + if constexpr (std::is_same_v< + std::remove_cv_t>, + virtual_any_ref>) { + // by value: a reference would dangle when this function's + // parameter goes out of scope + return arg; + } else if constexpr (std::is_const_v) { + return virtual_traits::template cast< + U>(*arg.obj); + } else { + return virtual_traits::template cast( + *arg.obj); + } + } +}; + +namespace detail { + +template +struct is_virtual> : std::true_type {}; + +template +struct parameter_traits, Registry> + : virtual_traits, Registry> {}; + +template +struct validate_method_parameter< + virtual_any_ref, MethodRegistry, void> + : std::bool_constant> { + static_assert( + std::is_same_v, "registry mismatch"); +}; + +template +struct validate_method_parameter< + virtual_any_ref&, MethodRegistry, void> : std::false_type { + static_assert( + false_t, + "virtual_any_ref is a cheap handle, pass it by value"); +}; + +template +struct validate_method_parameter< + const virtual_any_ref&, MethodRegistry, void> + : std::false_type { + static_assert( + false_t, + "virtual_any_ref is a cheap handle, pass it by value"); +}; + +template +struct validate_method_parameter< + virtual_any_ref&&, MethodRegistry, void> : std::false_type { + static_assert( + false_t, + "virtual_any_ref is a cheap handle, pass it by value"); +}; + +template +struct validate_overrider_parameter, T2, void> + : std::true_type {}; + +template +struct validate_overrider_parameter< + virtual_any_ref, virtual_any_ref, void> + : std::true_type {}; + +template +struct select_overrider_virtual_type_aux< + virtual_any_ref, Q, Registry> { + using type = virtual_type; +}; + +} // namespace detail + +namespace aliases { +using boost::openmethod::virtual_any; +using boost::openmethod::virtual_any_ref; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 7417740c..5dc0f46e 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -44,84 +44,297 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_GENSYM BOOST_PP_CAT(openmethod_gensym_, __COUNTER__) +//! Create a registrar object. +//! +//! Creates a registrar for a type, i.e. a static object of that type with a +//! unique generated name. At static initialization time, the object adds +//! itself to a list: methods and class registrations add themselves to a +//! @ref boost::openmethod::registry, and overriders add themselves to a +//! method's overrider list. +//! +//! @param ... The registrar's type. It is variadic so that it may contain +//! unparenthesized commas, as in `std::pair`. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) #define BOOST_OPENMETHOD_REGISTER(...) \ static __VA_ARGS__ BOOST_OPENMETHOD_GENSYM -#define BOOST_OPENMETHOD_ID(NAME) NAME##_boost_openmethod - -#define BOOST_OPENMETHOD_OVERRIDERS(NAME) \ - BOOST_PP_CAT(BOOST_OPENMETHOD_ID(NAME), _overriders) - -#define BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS> - -#define BOOST_OPENMETHOD_GUIDE(NAME) \ - BOOST_PP_CAT(BOOST_OPENMETHOD_ID(NAME), _guide) - -#define BOOST_OPENMETHOD_TYPE(NAME, ARGS, ...) \ +//! Generate a method id. +//! +//! Generates a long, obfuscated name from a short name. All the other names +//! generated by macros are based on this name. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) +#define BOOST_OPENMETHOD_ID(ID) ID##_boost_openmethod + +//! Return the class template containing the overriders for all the methods +//! with a given name. +//! +//! `BOOST_OPENMETHOD_OVERRIDERS` expands to the name of the class template that +//! contains the overriders for all the methods with a given name. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) +#define BOOST_OPENMETHOD_OVERRIDERS(ID) \ + BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _overriders) + +//! Return the class template specialization containing an overrider. +//! +//! Expands to the specialization of the class template that contains the +//! overrider with the given name, parameter list and return type. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) +#define BOOST_OPENMETHOD_OVERRIDER(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_OVERRIDERS(ID)<__VA_ARGS__ PARAMETERS> + +#define BOOST_OPENMETHOD_GUIDE(ID) BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _guide) + +//! Expand to a core `method` specialization. +//! +//! Expands to the core @ref boost::openmethod::method specialization created by +//! @ref BOOST_OPENMETHOD called with the same arguments. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @param ID The method's name. +//! @param PARAMETERS The method's parameter list, in parentheses. +//! @param ... The method's return type, optionally followed by the registry. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) +#define BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, ...) \ ::boost::openmethod::method< \ - BOOST_OPENMETHOD_ID(NAME), \ - ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type ARGS, \ + BOOST_OPENMETHOD_ID(ID), \ + ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type \ + PARAMETERS, \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::registry> -#define BOOST_OPENMETHOD(NAME, ARGS, ...) \ - struct BOOST_OPENMETHOD_ID(NAME); \ +//! Declare a method. +//! +//! Declares a method, called `ID`, with the given parameters and return type, +//! and adds it to a registry. +//! +//! `PARAMETERS` is a comma-separated list of types, possibly followed by +//! parameter names, just like in a function declaration. Parameters with a type +//! in the form `virtual_ptr` or `virtual_` are called virtual parameters. +//! The dynamic type of the arguments passed in virtual parameters determines +//! which overrider to call, following the same rules as overloaded function +//! resolution: +//! +//! @li Form the set of all applicable overriders. An overrider is applicable +//! if it can be called with the arguments passed to the method. +//! +//! @li If the set is empty, call the error handler (if present in the +//! registry), then terminate the program with `abort`. +//! +//! @li Remove the overriders that are dominated by other overriders in the +//! set. Overrider A dominates overrider B if any of its virtual formal +//! parameters is more specialized than B's, and if none of B's virtual +//! parameters is more specialized than A's. +//! +//! @li If the resulting set contains exactly one overrider, call it. +//! +//! If a single most specialized overrider does not exist, the program is +//! terminated via `abort`. If the registry contains an `error_handler` policy, +//! its `error` function is called with an object that describes the error, +//! prior to calling `abort`. `error` may prevent termination by throwing an +//! exception. +//! +//! For each virtual argument `arg`, the dispatch mechanism calls +//! `virtual_traits::peek(arg)` and deduces the v-table pointer from the +//! `result`, using the first of the following methods that applies: +//! +//! @li If `result` is a `virtual_ptr`, get the pointer to the v-table from it. +//! +//! @li If `boost_openmethod_vptr` can be called with `result` and a +//! `Registry*`, and it returns a `vptr_type`, call it. +//! +//! @li Call `Registry::vptr::dynamic_vptr(result)`. +//! +//! The macro creates an ordinary inline function in the current scope, with the +//! `virtual_` decorators removed from the parameter types. `virtual_ptr` +//! parameters are preserved. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @note The default registry is the value of +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY at the point +//! `` is included. Changing the value of this symbol +//! has no effect after that point. +//! +//! @par Example +//! +//! See [BOOST_OPENMETHOD_OVERRIDE](xref:reference:BOOST_OPENMETHOD_OVERRIDE.adoc#_example) +//! for an example. +//! +//! @par Implementation Notes +//! +//! The macro creates several additional constructs: +//! +//! @li A `struct` forward declaration that acts as the method's identifier: +//! @code +//! struct BOOST_OPENMETHOD_ID(ID); +//! @endcode +//! +//! @li A class template declaration that acts as a container for the method's +//! overriders in the current scope: +//! @code +//! template struct BOOST_OPENMETHOD_OVERRIDERS(ID); +//! @endcode +//! +//! @li A guide function used to match overriders with the method: +//! @code +//! auto BOOST_OPENMETHOD_ID(ID)_guide(...) +//! -> ::boost::openmethod::method< +//! BOOST_OPENMETHOD_ID(ID)(PARAMETERS...), RETURN_TYPE [, REGISTRY]>; +//! @endcode +//! +//! @li A registrar (see @ref BOOST_OPENMETHOD_REGISTER) that adds the method to +//! the registry. +//! +//! @param ID The method's name. +//! @param PARAMETERS The method's parameter list, in parentheses. +//! @param ... The method's return type, optionally followed by the registry. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) +#define BOOST_OPENMETHOD(ID, PARAMETERS, ...) \ + struct BOOST_OPENMETHOD_ID(ID); \ template \ typename ::boost::openmethod::detail::enable_forwarder< \ - void, BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ - typename BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ + void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ + typename BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ ForwarderParameters...>::type \ - BOOST_OPENMETHOD_GUIDE(NAME)(ForwarderParameters && ... args); \ + BOOST_OPENMETHOD_GUIDE(ID)(ForwarderParameters && ... args); \ template \ - inline auto NAME(ForwarderParameters&&... args) -> \ + inline auto ID(ForwarderParameters&&... args) -> \ typename ::boost::openmethod::detail::enable_forwarder< \ - void, BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ + void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type, \ ForwarderParameters...>::type { \ - return BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__)::fn( \ + return BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__)::fn( \ std::forward(args)...); \ } \ template \ - struct BOOST_OPENMETHOD_OVERRIDERS(NAME) + struct BOOST_OPENMETHOD_OVERRIDERS(ID) -#define BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(NAME, ARGS) \ +#define BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(ID, PARAMETERS) \ template \ struct boost_openmethod_detail_locate_method_aux { \ static_assert( \ ::boost::openmethod::detail::method_not_found, \ - "BOOST_OPENMETHOD_OVERRIDE: cannot find '" #NAME \ + "BOOST_OPENMETHOD_OVERRIDE: cannot find '" #ID \ "' method that accepts the same arguments as the overrider"); \ }; \ template \ struct boost_openmethod_detail_locate_method_aux< \ void(A...), \ - std::void_t()...))>> { \ using type = \ - decltype(BOOST_OPENMETHOD_GUIDE(NAME)(std::declval()...)); \ + decltype(BOOST_OPENMETHOD_GUIDE(ID)(std::declval()...)); \ } -#define BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, ...) \ +//! Declare a method overrider. +//! +//! Declares an overrider for a method, but does not start its definition. This +//! macro can be used in header files. +//! +//! `ID` is the identifier of the method to which the overrider is added. +//! +//! `PARAMETERS` is a comma-separated list of types, possibly followed by +//! parameter names, just like in a function declaration. +//! +//! The macro tries to locate a method that can be called with the same argument +//! list as the overrider, possibly via argument dependent lookup. +//! +//! 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. +//! +//! 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 accessible unambiguous base. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @par Example +//! +//! Use this macro, rather than @ref BOOST_OPENMETHOD_OVERRIDE, to split an +//! overrider across a header and an implementation file. The header declares +//! the overrider without a body: +//! +//! include:../examples/rolex/2/roles.hpp#content +//! +//! The implementation file supplies the body with +//! @ref BOOST_OPENMETHOD_DEFINE_OVERRIDER: +//! +//! include:../examples/rolex/2/employee.cpp#content +//! +//! This specific overrider can be called from other overriders explictly. No +//! dynamic dispatch is performed. +//! +//! include:../examples/rolex/2/salesman.cpp#content +//! +//! @par Implementation Notes +//! +//! The macro creates additional entities in the current scope. +//! +//! @li A class template declaration that acts as a container for the method's +//! overriders in the current scope: +//! @code +//! template struct BOOST_OPENMETHOD_OVERRIDERS(ID); +//! @endcode +//! +//! @li A specialization of the container for the overrider: +//! @code +//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { +//! static auto fn(PARAMETERS...) -> RETURN_TYPE; +//! static auto has_next() -> bool; +//! template +//! static auto next(typename... Args) -> RETURN_TYPE; +//! }; +//! @endcode +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) +#define BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, ...) \ template \ - struct BOOST_OPENMETHOD_OVERRIDERS(NAME); \ + struct BOOST_OPENMETHOD_OVERRIDERS(ID); \ template<> \ - struct BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS> { \ - BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(NAME, ARGS); \ - static auto fn ARGS->__VA_ARGS__; \ + struct BOOST_OPENMETHOD_OVERRIDERS(ID)<__VA_ARGS__ PARAMETERS> { \ + BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(ID, PARAMETERS); \ + static auto fn PARAMETERS->__VA_ARGS__; \ static auto has_next() -> bool; \ template \ static auto next(Args&&... args) -> decltype(auto); \ }; \ inline auto BOOST_OPENMETHOD_OVERRIDERS( \ - NAME)<__VA_ARGS__ ARGS>::has_next() -> bool { \ + ID)<__VA_ARGS__ PARAMETERS>::has_next() -> bool { \ return boost_openmethod_detail_locate_method_aux< \ - void ARGS>::type::has_next(); \ + void PARAMETERS>::type::has_next(); \ } \ template \ - inline auto BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS>::next( \ + inline auto BOOST_OPENMETHOD_OVERRIDERS(ID)<__VA_ARGS__ PARAMETERS>::next( \ Args&&... args) -> decltype(auto) { \ return boost_openmethod_detail_locate_method_aux< \ - void ARGS>::type::next(std::forward(args)...); \ + void PARAMETERS>::type::next(std::forward(args)...); \ } // REGISTRAR selects which of method<...>::override (plain) or @@ -137,25 +350,127 @@ inline constexpr bool method_not_found = false; // overrider's return type, which may contain an unprotected top-level comma, // e.g. an un-aliased std::pair) as one argument. #define BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER_AUX( \ - NAME, ARGS, REGISTRAR, ...) \ + ID, PARAMETERS, REGISTRAR, ...) \ BOOST_OPENMETHOD_REGISTER( \ - BOOST_OPENMETHOD_OVERRIDERS(NAME) < __VA_ARGS__ ARGS > \ - ::boost_openmethod_detail_locate_method_aux::type:: \ + BOOST_OPENMETHOD_OVERRIDERS(ID) < __VA_ARGS__ PARAMETERS > \ + ::boost_openmethod_detail_locate_method_aux::type:: \ REGISTRAR< \ - BOOST_OPENMETHOD_OVERRIDERS(NAME) < __VA_ARGS__ ARGS>::fn >); + BOOST_OPENMETHOD_OVERRIDERS(ID) < \ + __VA_ARGS__ PARAMETERS>::fn >); -#define BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(NAME, ARGS, ...) \ +#define BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(ID, PARAMETERS, ...) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER_AUX( \ - NAME, ARGS, override, __VA_ARGS__) - -#define BOOST_OPENMETHOD_DEFINE_OVERRIDER(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ - auto BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, __VA_ARGS__)::fn ARGS \ + ID, PARAMETERS, override, __VA_ARGS__) + +//! Define the body of a method overrider. +//! +//! Defines the body of an overrider declared with +//! @ref BOOST_OPENMETHOD_DECLARE_OVERRIDER. It should be called in an +//! implementation file, and followed by a function body. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @par Example +//! +//! See [BOOST_OPENMETHOD_DECLARE_OVERRIDER](xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc#_example) +//! for an example. +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) +#define BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ + auto BOOST_OPENMETHOD_OVERRIDER( \ + ID, PARAMETERS, __VA_ARGS__)::fn PARAMETERS \ -> boost::mp11::mp_back> -#define BOOST_OPENMETHOD_OVERRIDE(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ - BOOST_OPENMETHOD_DEFINE_OVERRIDER(NAME, ARGS, __VA_ARGS__) +//! Add an overrider to a method. +//! +//! `BOOST_OPENMETHOD_OVERRIDE` adds an overrider to a method. It is followed by +//! the overrider's body. +//! +//! `ID` is the identifier of the method to which the overrider is added. +//! +//! `PARAMETERS` is a comma-separated list of types, possibly followed by +//! parameter names, just like in a function declaration. +//! +//! The macro tries to locate a method that can be called with the same argument +//! list as the overrider, possibly via argument dependent lookup. +//! +//! 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. +//! +//! 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 accessible unambiguous base. +//! +//! The following names are available inside the overrider's body: +//! +//! @li `fn`: a pointer to a function, the overrider itself. Can be used for +//! recursion. +//! +//! @li `next`: a function with the same signature as the method (minus the +//! `virtual_<>` decorators). It forwards to the next most specialized +//! overrider, if it exists and it is unique. If the next overrider does not +//! exist, or is ambiguous, calling `next` reports a +//! @ref boost::openmethod::no_overrider or a +//! @ref boost::openmethod::ambiguous_call and terminates the program. +//! +//! @li `has_next()`: returns `true` if the next most specialized overrider +//! exists. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @par Example +//! +//! include:macros.cpp#declare;override;call +//! +//! @par Implementation Notes +//! +//! The macro creates additional entities in the current scope. +//! +//! @li A class template declaration that acts as a container for the method's +//! overriders in the current scope: +//! @code +//! template struct BOOST_OPENMETHOD_OVERRIDERS(ID); +//! @endcode +//! +//! @li A specialization of the container for the overrider: +//! @code +//! struct BOOST_OPENMETHOD_OVERRIDERS(ID) { +//! static auto fn(PARAMETERS...) -> RETURN_TYPE; +//! static auto has_next() -> bool; +//! template +//! static auto next(typename... Args) -> RETURN_TYPE; +//! }; +//! @endcode +//! +//! @li A registrar (see @ref BOOST_OPENMETHOD_REGISTER) adding the overrider to +//! the method. +//! +//! @li Finally, the macro starts the definition of the overrider function: +//! @code +//! auto BOOST_OPENMETHOD_OVERRIDERS(ID)::fn( +//! PARAMETERS...) -> RETURN_TYPE +//! @endcode +//! +//! The `{}` block following the call to the macro is the body of the function. +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) +//! @see [Namespaces](xref:ROOT:namespaces.adoc) +//! @see [Friends](xref:ROOT:friends.adoc) +#define BOOST_OPENMETHOD_OVERRIDE(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ + BOOST_OPENMETHOD_DEFINE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) // Unlike BOOST_OPENMETHOD_OVERRIDE, registers via method<...>::inline_override // instead of method<...>::override, marking the overrider_info as @@ -165,33 +480,111 @@ inline constexpr bool method_not_found = false; // identical definition appear in more than one translation unit/module in // the first place, which is why plain BOOST_OPENMETHOD_OVERRIDE never sets // this. -#define BOOST_OPENMETHOD_INLINE_OVERRIDE(NAME, ARGS, ...) \ - BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ + +//! Add an overrider to a method as an inline function. +//! +//! `BOOST_OPENMETHOD_INLINE_OVERRIDE` performs the same function as +//! @ref BOOST_OPENMETHOD_OVERRIDE, except that the overrider is marked +//! `inline`. +//! +//! Use it for an overrider defined in a header, where the same definition +//! reaches more than one translation unit. `inline` is what makes the repeated +//! definition legal, and it lets @ref boost::openmethod::initialize merge the +//! repeated registrations. @ref BOOST_OPENMETHOD_OVERRIDE would instead record +//! them as distinct overriders for the same class, making the call ambiguous. +//! +//! @note `ID` must be an *identifier*. Qualified names are not allowed. +//! +//! @par Example +//! +//! A header that declares a method and supplies a default overrider for it. +//! Every translation unit including it gets the same definition: +//! +//! include:../examples/rolex/3/roles.hpp#content +//! +//! A translation unit that includes the header adds a more specialized +//! overrider of its own. That one is defined once, so it uses +//! @ref BOOST_OPENMETHOD_OVERRIDE; it reaches the header's overrider through +//! @ref BOOST_OPENMETHOD_OVERRIDER: +//! +//! include:../examples/rolex/3/salesman.cpp#content +//! +//! @param ID The method's name. +//! @param PARAMETERS The overrider's parameter list, in parentheses. +//! @param ... The overrider's return type. +//! +//! @see [Header and Implementation Files](xref:ROOT:headers.adoc) +#define BOOST_OPENMETHOD_INLINE_OVERRIDE(ID, PARAMETERS, ...) \ + BOOST_OPENMETHOD_DECLARE_OVERRIDER(ID, PARAMETERS, __VA_ARGS__) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER_AUX( \ - NAME, ARGS, inline_override, __VA_ARGS__) \ - inline auto BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, __VA_ARGS__)::fn ARGS \ + ID, PARAMETERS, inline_override, __VA_ARGS__) \ + inline auto BOOST_OPENMETHOD_OVERRIDER( \ + ID, PARAMETERS, __VA_ARGS__)::fn PARAMETERS \ -> boost::mp11::mp_back> +//! Register classes. +//! +//! Registers classes in a registry. +//! +//! This macro is a wrapper around @ref boost::openmethod::use_classes; see its +//! documentation for more details. +//! +//! @note The default registry is the value of +//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when `` is +//! included. Subsequently changing it has no retroactive effect. +//! +//! @par Examples +//! +//! A class and its direct bases must appear together in one call. Take `Cat` +//! and `Dog`, both derived from `Animal`, and `Bulldog`, derived from `Dog`. +//! A single call listing all of them describes the hierarchy: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, Bulldog); +//! @endcode +//! +//! Several calls do just as well, as long as every class appears alongside its +//! direct bases. `Dog` is listed twice here, and that is what attaches +//! `Bulldog` to it: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); +//! BOOST_OPENMETHOD_CLASSES(Dog, Bulldog); +//! @endcode +//! +//! Registering the classes one per call describes no inheritance at all, and +//! @ref boost::openmethod::initialize reports a +//! @ref boost::openmethod::missing_base error: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal); +//! BOOST_OPENMETHOD_CLASSES(Cat); +//! BOOST_OPENMETHOD_CLASSES(Dog); // initialize reports missing_base +//! @endcode +//! +//! Listing a class with an ancestor in place of its direct base is the more +//! dangerous mistake, because nothing reports it. Below, `Bulldog` is recorded +//! as derived from `Animal`; an overrider for `Dog` no longer applies to it, so +//! a call passing a `Bulldog` quietly selects the overrider for `Animal`: +//! +//! @code +//! BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); +//! BOOST_OPENMETHOD_CLASSES(Animal, Bulldog); +//! // OpenMethod believes that Bulldog derives from Animal, not Dog +//! @endcode +//! +//! @param ... The classes to register, optionally followed by the registry. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>) -// Share a registry's state across module boundaries. All of a registry's -// mutable state lives in one variable, registry_state::st -// (see registry_state in preamble.hpp); these macros emit the explicit -// instantiations that make it a single shared symbol: -// -// BOOST_OPENMETHOD_IMPORT_REGISTRY - header; every TU of a CLIENT module -// BOOST_OPENMETHOD_EXPORT_REGISTRY - header; every TU of the OWNING module -// BOOST_OPENMETHOD_INSTANTIATE_REGISTRY - exactly one .cpp of the owning module -// -// The owning module uses both: EXPORT in the header its translation units -// share, and INSTANTIATE in exactly one of them. Use them at namespace scope, -// after the registry's definition, with a trailing `;`. REGISTRY may be any -// registry, predefined or user-defined; everything emitted is fully qualified, -// so there is no need to be inside, or to open, namespace boost::openmethod. -// -// The macros exist because no single spelling is portable - the two ABIs want -// opposite things: +// The three macros below share a registry's state - the single variable +// registry_state::st, see registry_state in preamble.hpp - +// across module boundaries, by emitting the explicit instantiations that make +// it one shared symbol. See their documentation comments for how they are +// meant to be used. They exist because no single spelling is portable: the two +// ABIs want opposite things. // // * declspec platforms (Windows, Cygwin, MinGW): MSVC rejects `extern` together // with __declspec(dllexport) on an explicit instantiation outright ("warning @@ -210,27 +603,117 @@ inline constexpr bool method_not_found = false; // instantiates the state implicitly, and under -fvisibility=hidden that copy // is module-local; since ELF merges COMDATs at the most restrictive // visibility, the whole symbol then becomes local and clients fail to link. + +//! Import a registry's state from the module that owns it. +//! +//! All of a registry's mutable state lives in a single variable (see +//! @ref boost::openmethod::registry_state). Sharing a registry across modules +//! means sharing that one symbol, which takes three macros: the owning module +//! uses @ref BOOST_OPENMETHOD_EXPORT_REGISTRY in the header its translation +//! units share, and @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY in exactly one +//! of them; every client module uses `BOOST_OPENMETHOD_IMPORT_REGISTRY`. +//! +//! They exist to hide a platform incompatibility: on Windows, Cygwin and +//! MinGW, `__declspec(dllexport)` and `extern` are incompatible on an explicit +//! instantiation, while on ELF and Mach-O the visibility attribute must be on +//! the declaration and must not be repeated on the definition. +//! +//! Use at namespace scope, after the registry's definition, in every +//! translation unit of every module that uses the registry without owning it. +//! Being a declaration it may be repeated, so it belongs in the header those +//! modules share. Everything it emits is fully qualified, so there is no need +//! to be inside, or to open, namespace `boost::openmethod`. +//! +//! It emits an `extern template` declaration decorated with +//! `BOOST_SYMBOL_IMPORT` (`__declspec(dllimport)` on Windows, nothing on ELF). +//! The declaration suppresses the client's own instantiation, so it references +//! the owner's symbol instead of creating a private copy. +//! +//! The client module must be linked so the reference resolves: on Windows and +//! macOS by linking against the owning module; on ELF a dynamically loaded +//! library may also leave it for the dynamic linker to resolve at load time. +//! +//! @param REGISTRY The registry to import. May be any registry, predefined or +//! user-defined. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full +//! discussion, including the required link setup. #define BOOST_OPENMETHOD_IMPORT_REGISTRY(REGISTRY) \ extern template struct BOOST_SYMBOL_IMPORT ::boost::openmethod:: \ registry_state #ifdef BOOST_HAS_DECLSPEC -#define BOOST_OPENMETHOD_EXPORT_REGISTRY(REGISTRY) static_assert(true) +#define BOOST_OPENMETHOD_DETAIL_EXPORT_REGISTRY(REGISTRY) static_assert(true) -#define BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(REGISTRY) \ +#define BOOST_OPENMETHOD_DETAIL_INSTANTIATE_REGISTRY(REGISTRY) \ template struct BOOST_SYMBOL_EXPORT ::boost::openmethod::registry_state< \ REGISTRY::registry_type> #else -#define BOOST_OPENMETHOD_EXPORT_REGISTRY(REGISTRY) \ +#define BOOST_OPENMETHOD_DETAIL_EXPORT_REGISTRY(REGISTRY) \ extern template struct BOOST_SYMBOL_EXPORT ::boost::openmethod:: \ registry_state -#define BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(REGISTRY) \ +#define BOOST_OPENMETHOD_DETAIL_INSTANTIATE_REGISTRY(REGISTRY) \ template struct ::boost::openmethod::registry_state #endif +//! Declare a registry's state exported, in every translation unit of the +//! owning module. +//! +//! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing +//! macros fit together. +//! +//! Use at namespace scope, after the registry's definition, in *every* +//! translation unit of the module that owns the registry. Being a declaration +//! it may be repeated, so it belongs in the header those translation units +//! share. +//! +//! On ELF it emits an exported explicit instantiation *declaration*, which +//! both suppresses implicit instantiation and pins the symbol to default +//! visibility. On declspec platforms it expands to nothing, because there the +//! export belongs on the instantiation instead. +//! +//! @warning On ELF this macro is not decoration. A translation unit of the +//! owning module that uses neither it nor +//! @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY instantiates the state +//! implicitly, and under `-fvisibility=hidden` that copy is module-local. Since +//! ELF merges COMDATs at the *most restrictive* visibility, the merged symbol +//! becomes local: the module builds, exports nothing, and clients fail to link +//! with an undefined reference to `registry_state<...>::st`. +//! +//! @param REGISTRY The registry to export. May be any registry, predefined or +//! user-defined. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full +//! discussion, including the required link setup. +#define BOOST_OPENMETHOD_EXPORT_REGISTRY(REGISTRY) \ + BOOST_OPENMETHOD_DETAIL_EXPORT_REGISTRY(REGISTRY) + +//! Instantiate a registry's state, in exactly one translation unit of the +//! owning module. +//! +//! See @ref BOOST_OPENMETHOD_IMPORT_REGISTRY for how the three registry-sharing +//! macros fit together. +//! +//! Use at namespace scope, after the registry's definition, in *exactly one* +//! translation unit of the module that owns the registry. It belongs in a +//! `.cpp` file, never in a header. +//! +//! It emits the explicit instantiation *definition* of the registry state, of +//! which a program may contain only one. On declspec platforms the definition +//! carries the `dllexport`; on ELF and Mach-O it carries no attribute, that +//! having been supplied by @ref BOOST_OPENMETHOD_EXPORT_REGISTRY in the header. +//! +//! @param REGISTRY The registry to instantiate. May be any registry, predefined +//! or user-defined. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) for the full +//! discussion, including the required link setup. +#define BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(REGISTRY) \ + BOOST_OPENMETHOD_DETAIL_INSTANTIATE_REGISTRY(REGISTRY) + #endif diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index 02de74fe..2aca30b7 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -32,6 +32,11 @@ namespace policies { //! handler with a function that throws an exception, possibly preventing //! program termination. The @ref throw_error_handler policy can also be used to //! enable exception throwing on a registry basis. +//! +//! @par Example +//! include:policies.cpp#default_error_handler_registry;default_error_handler_set +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct default_error_handler : error_handler { //! A ErrorHandlerFn metafunction. diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index 331dc112..a5d8be56 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -60,6 +60,11 @@ namespace policies { //! corresponds to a value in the domain, or even that the codomain is a dense //! range of integers. In other words, a lot of space may be wasted in presence //! of large sets of type_ids. +//! +//! @par Example +//! include:policies.cpp#fast_perfect_hash +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct fast_perfect_hash : type_hash { //! Cannot find hash factors diff --git a/include/boost/openmethod/policies/static_rtti.hpp b/include/boost/openmethod/policies/static_rtti.hpp index a197a36f..9cb452b0 100644 --- a/include/boost/openmethod/policies/static_rtti.hpp +++ b/include/boost/openmethod/policies/static_rtti.hpp @@ -20,8 +20,21 @@ namespace boost::openmethod::policies { //! its equivalents for smart pointers). //! //! @par Example -//! TODO -//! include::example$static_rtti.cpp[tag=all] +//! +//! Selecting the policy, which has to happen before `` +//! is included: +//! +//! include:static_rtti.cpp#registry +//! +//! The classes and the method need no RTTI, and need not be polymorphic: +//! +//! include:static_rtti.cpp#classes +//! +//! Every `virtual_ptr` has to be created where the exact class is known: +//! +//! include:static_rtti.cpp#dispatch +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct static_rtti : rtti { //! A RttiFn metafunction. //! diff --git a/include/boost/openmethod/policies/std_rtti.hpp b/include/boost/openmethod/policies/std_rtti.hpp index 88857c56..7426263e 100644 --- a/include/boost/openmethod/policies/std_rtti.hpp +++ b/include/boost/openmethod/policies/std_rtti.hpp @@ -20,6 +20,11 @@ namespace boost::openmethod::policies { //! //! `std_rtti` implements the `rtti` policy using the standard C++ RTTI system. //! It is the default RTTI policy. +//! +//! @par Example +//! include:policies.cpp#std_rtti;std_rtti_dispatch +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct std_rtti : rtti { //! A RttiFn metafunction. //! diff --git a/include/boost/openmethod/policies/stderr_output.hpp b/include/boost/openmethod/policies/stderr_output.hpp index 595de98a..0258c182 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -13,9 +13,14 @@ namespace boost::openmethod { namespace policies { -//! @ref Writes to the C standard error stream. +//! Writes to the C standard error stream. //! //! `stderr_output` writes to standard error using the C API. +//! +//! @par Example +//! include:policies.cpp#stderr_output +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct stderr_output : output { //! An OutputFn metafunction. template diff --git a/include/boost/openmethod/policies/throw_error_handler.hpp b/include/boost/openmethod/policies/throw_error_handler.hpp index 066f17d4..08d1fa24 100644 --- a/include/boost/openmethod/policies/throw_error_handler.hpp +++ b/include/boost/openmethod/policies/throw_error_handler.hpp @@ -16,6 +16,11 @@ namespace boost::openmethod::policies { //! Throws error as an exception. //! +//! +//! @par Example +//! include:policies.cpp#throw_error_handler_registry;throw_error_handler_catch +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct throw_error_handler : error_handler { //! A ErrorHandlerFn metafunction. //! diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index 54be921c..ef5d2c09 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -24,6 +24,11 @@ namespace policies { //! //! @tparam MapFn A mp11 quoted metafunction that takes a key type and a //! value type, and returns an @ref AssociativeContainer. +//! +//! @par Example +//! include:policies.cpp#vptr_map +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template> class vptr_map : public vptr { public: @@ -78,7 +83,7 @@ class vptr_map : public vptr { st().vptrs.swap(new_vptrs); } - //! Returns a reference to a v-table pointer for an object. + //! Returns a *reference* to a v-table pointer for an object. //! //! Acquires the dynamic @ref type_id of `arg`, using the registry's //! @ref rtti policy. @@ -91,10 +96,23 @@ class vptr_map : public vptr { //! //! @tparam Class A registered class. //! @param arg A reference to a const object of type `Class`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { - auto type = Registry::rtti::dynamic_type(arg); + return vptr(Registry::rtti::dynamic_type(arg)); + } + + //! Returns a *reference* to a v-table pointer for a type. + //! + //! If the registry contains the @ref runtime_checks policy, checks that + //! the map contains the type id. If it does not, and if the registry + //! contains a @ref error_handler policy, calls its + //! @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param type A `type_id`. + //! @return A reference to the v-table pointer for `type`. + static auto vptr(type_id type) -> const vptr_type& { auto iter = st().vptrs.find(type); if constexpr (Registry::has_runtime_checks) { diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index ae3f7ac5..304d2ab9 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -24,6 +24,11 @@ namespace policies { //! //! If the registry contains the @ref indirect_vptr policy, stores pointers to //! pointers to v-tables in the vector. +//! +//! @par Example +//! include:policies.cpp#vptr_vector +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct vptr_vector : vptr { public: //! A VptrFn metafunction. @@ -147,15 +152,31 @@ struct vptr_vector : vptr { //! //! @tparam Class A registered class. //! @param arg A reference to a const object of type `Class`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { - auto dynamic_type = Registry::rtti::dynamic_type(arg); + return vptr(Registry::rtti::dynamic_type(arg)); + }; + + //! Returns a *reference* to a v-table pointer for a type. + //! + //! If the registry has a @ref type_hash policy, uses it to convert the + //! type id to an index; otherwise, uses the type_id as the index. + //! + //! If the registry contains the @ref runtime_checks policy, verifies + //! that the index falls within the limits of the vector. If it does + //! not, and if the registry contains a @ref error_handler policy, calls + //! its @ref error function with a @ref missing_class value, then + //! terminates the program with @ref abort. + //! + //! @param type A `type_id`. + //! @return A reference to the v-table pointer for `type`. + static auto vptr(type_id type) -> const vptr_type& { std::size_t index; if constexpr (has_type_hash) { - index = type_hash::hash(dynamic_type); + index = type_hash::hash(type); } else { - index = std::size_t(dynamic_type); + index = std::size_t(type); if constexpr (Registry::has_runtime_checks) { std::size_t max_index = st().vptrs.size(); @@ -163,7 +184,7 @@ struct vptr_vector : vptr { if (index >= max_index) { if constexpr (Registry::has_error_handler) { missing_class error; - error.type = dynamic_type; + error.type = type; Registry::error_handler::error(error); } diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 5dddbe50..0d53ee71 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -80,6 +80,8 @@ using type_id = const void*; //! - @ref virtual_traits must be specialized for `T`. //! //! @tparam T A class. +//! +//! @see [Virtual Pointer Alternatives](xref:ROOT:virtual_ptr_alt.adoc) template struct virtual_; @@ -90,13 +92,17 @@ struct virtual_traits; // Error handling //! Base class for all OpenMethod errors. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct openmethod_error {}; //! One Definition Rule violation. //! //! This error is raised if the definition of @ref default_registry is //! inconsistent across translation units, due to misuse of -//! {{BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS}}. +//! @ref BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct odr_violation : openmethod_error { //! Write a description of the error to a stream. //! @tparam Registry The registry containing this policy. @@ -132,6 +138,8 @@ std::size_t odr_check::inc = count++; } // namespace detail //! Registry not initialized +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct not_initialized : openmethod_error { //! Write a short description to an output stream //! @param os The output stream @@ -150,42 +158,24 @@ struct not_initialized : openmethod_error { //! //! @par Examples //! -//! Missing registration of a class used as a virtual parameter in a method: -//! @code -//! struct Animal { virtual ~Animal() {} }; -//! struct Dog : Animal {}; -//! -//! BOOST_OPENMETHOD_CLASSES(Animal); +//! @note The error goes to the registry's +//! @ref boost::openmethod::policies::error_handler policy, which writes the +//! description shown in the comments; the program is then terminated. A +//! handler may throw instead, to keep the program running. //! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); +//! Missing registration of a class used as a virtual parameter in a method: //! -//! initialize(); // throws missing_class; -//! @endcode +//! include:errors_missing_class_method.cpp#classes;init //! //! Missing registration of a class used as a virtual parameter in an overrider: -//! @code -//! BOOST_OPENMETHOD_CLASSES(Animal); //! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ } -//! -//! initialize(); // throws missing_class; -//! @endcode +//! include:errors_missing_class_overrider.cpp#classes;init //! //! Missing registration of a class used as a virtual parameter in a call: -//! @code -//! struct Bulldog : Dog {}; -//! -//! BOOST_OPENMETHOD_CLASSES(Animal, Dog); //! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); +//! include:errors_missing_class_call.cpp#classes;use //! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ } -//! -//! Bulldog hector; -//! poke(hector); // throws missing_class; -//! @endcode +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_class : openmethod_error { //! The type_id of the unknown class. type_id type; @@ -205,26 +195,23 @@ struct missing_class : openmethod_error { //! parameter list. //! //! @par Example +//! +//! @note The error goes to the registry's +//! @ref boost::openmethod::policies::error_handler policy, which writes the +//! description shown in the comments; the program is then terminated. A +//! handler may throw instead, to keep the program running. +//! //! In the following code, OpenMethod cannot infer that `Dog` is derived from //! `Animal`, because they are not registered in a same call to @ref //! BOOST_OPENMETHOD_CLASSES. //! -//! @code -//! BOOST_OPENMETHOD_CLASSES(Animal); -//! BOOST_OPENMETHOD_CLASSES(Dog); -//! -//! BOOST_OPENMETHOD(poke, (virtual_ptr), void); -//! -//! BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { /* ... */ } -//! -//! initialize(); // throws missing_base; -//! @endcode +//! include:errors_missing_base.cpp#classes;init //! //! Fix: //! -//! @code -//! BOOST_OPENMETHOD_CLASSES(Animal, Dog); -//! @endcode +//! include:errors_missing_class_call.cpp#fix +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_base : openmethod_error { //! The type_id of the base class. type_id base; @@ -240,6 +227,8 @@ struct missing_base : openmethod_error { }; //! No valid overrider +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct bad_call : openmethod_error { //! The type_id of method that was called type_id method; @@ -253,7 +242,10 @@ struct bad_call : openmethod_error { //! No overrider for virtual tuple //! -//! @see @ref bad_call for data members. +//! The data members are documented on @ref bad_call. +//! +//! @see @ref bad_call +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct no_overrider : bad_call { //! Write a short description to an output stream //! @param os The output stream @@ -267,7 +259,10 @@ struct no_overrider : bad_call { //! Ambiguous call //! -//! @see @ref bad_call for data members. +//! The data members are documented on @ref bad_call. +//! +//! @see @ref bad_call +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct ambiguous_call : bad_call { //! Write a short description to an output stream //! @param os The output stream @@ -287,6 +282,8 @@ struct ambiguous_call : bad_call { //! policy, its @ref error function is called with a `final_error` object, then //! the program is terminated with //! @ref abort. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct final_error : openmethod_error { type_id static_type, dynamic_type; @@ -483,7 +480,10 @@ inline trace trace::from_env() { //! implementing these blueprints must provide a `fn` metafunction //! that conforms to the blueprint's requirements. //! -//! @see @ref registry for a complete explanation of registries and policies. +//! @ref registry carries a complete explanation of registries and policies. +//! +//! @see @ref registry +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) namespace policies { @@ -600,6 +600,8 @@ struct RttiFn { //! @li derive from @c rtti. //! @li provide a @c fn metafunction that conforms to the @ref RttiFn //! blueprint. +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct rtti { // Policy category. using category = rtti; @@ -638,6 +640,8 @@ struct rtti { //! and overriders. This creates order-of-initialization issues. Deriving a @e //! rtti policy from this class - instead of just `rtti` - causes the collection //! of type ids to be deferred until the first call to @ref update. +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) struct deferred_static_rtti : rtti {}; // ----------------------------------------------------------------------------- @@ -667,6 +671,8 @@ struct ErrorHandlerFn { //! @li derive from @c error_handler. //! @li provide a @c fn metafunction that conforms to the @ref //! ErrorHandlerFn blueprint. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct error_handler { // Policy category. using category = error_handler; @@ -701,10 +707,29 @@ struct VptrFn { //! //! @tparam Class A registered class. //! @param arg A reference to a const object of type `Class`. - //! @return A reference to a the v-table pointer for `Class`. + //! @return A reference to the v-table pointer for `Class`. template static auto dynamic_vptr(const Class& arg) -> const vptr_type&; + // Added by the `std::any` interop, under the name `type_vptr`. An `any` + // knows the `type_id` of the value it contains, but has no object of that + // type to hand to `dynamic_vptr`. + + //! Return a *reference* to the v-table pointer for a type. + //! + //! Return a reference to the v-table pointer that `initialize` associated + //! to `type`. + //! + //! This function is optional. Implement it if the registry is to be used + //! with virtual parameters whose `virtual_traits` supply a `type_id` + //! themselves, instead of an object - see @ref VirtualTraits::vptr. Both + //! @ref vptr_vector and @ref vptr_map provide it, and implement + //! `dynamic_vptr` in terms of it. + //! + //! @param type A `type_id`. + //! @return A reference to the v-table pointer for `type`. + static auto vptr(type_id type) -> const vptr_type&; + //! Release the resources allocated by `initialize`. //! //! This function is optional. @@ -726,6 +751,8 @@ struct VptrFn { //! @li derive from @c vptr. //! @li provide a @c fn metafunction that conforms to the @ref //! VptrFn blueprint. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct vptr { // Policy category. using category = vptr; @@ -738,6 +765,8 @@ struct vptr { //! These indirect pointers remain valid after a call to @ref initialize, after //! dynamically loading a library that adds classes, methods and overriders to //! the registry. +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) struct indirect_vptr final { // Policy category. using category = indirect_vptr; @@ -802,6 +831,8 @@ struct TypeHashFn { //! @li derive from @c type_hash. //! @li provide a @c fn metafunction that conforms to the @ref //! TypeHashFn blueprint. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) struct type_hash { // Policy category. using category = type_hash; @@ -835,6 +866,8 @@ struct OutputFn { //! @li derive from @c output. //! @li provide a @c fn metafunction that conforms to the @ref //! OutputFn blueprint. +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct output { // Policy category. using category = output; @@ -849,6 +882,8 @@ struct output { //! @li Classes of virtual arguments have been registered. //! @li Dynamic and static types match in "final" constructs (@ref //! final_virtual_ptr and related functions). +//! +//! @see [Error Handling](xref:ROOT:error_handling.adoc) struct runtime_checks final { // Policy category. using category = runtime_checks; @@ -1053,8 +1088,8 @@ struct initialize_aux; //! whole and import via `extern template`. //! //! To share the state across modules, use -//! {{BOOST_OPENMETHOD_IMPORT_REGISTRY}}, {{BOOST_OPENMETHOD_EXPORT_REGISTRY}} -//! and {{BOOST_OPENMETHOD_INSTANTIATE_REGISTRY}}. They hide a platform +//! @ref BOOST_OPENMETHOD_IMPORT_REGISTRY, @ref BOOST_OPENMETHOD_EXPORT_REGISTRY +//! and @ref BOOST_OPENMETHOD_INSTANTIATE_REGISTRY. They hide a platform //! incompatibility: the export goes on the declaration on ELF and Mach-O, but //! on the instantiation on declspec platforms, where `extern` and //! `__declspec(dllexport)` cannot be combined. @@ -1066,6 +1101,8 @@ struct initialize_aux; //! // exactly one .cpp of the owning module: //! BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(boost::openmethod::default_registry); //! @endcode +//! +//! @see [Shared Libraries](xref:ROOT:shared_libraries.adoc) template struct registry_state { static detail::registry_state_type st; @@ -1113,6 +1150,19 @@ detail::registry_state_type registry_state::st; //! contains the `runtime_checks` policy. If an error is detected, it invokes //! the @ref error_handler policy if there is one. //! +//! A registry is identified by its policy list, not by the class that derives +//! from it. Everything a registry owns is keyed on the `registry` +//! specialization, which is what @ref registry_type aliases. Two classes built +//! from the same policies, in the same order, are therefore the same registry: +//! +//! include:../examples/registry_identity.cpp#shared +//! +//! This matters when a second registry exists to isolate a set of methods from +//! another, since it would naturally be given the same policies. Give each one +//! a policy of its own to keep them apart: +//! +//! include:../examples/registry_identity.cpp#distinct +//! //! @tparam Policy The policies used in the registry. //! //! @par Requirements @@ -1123,6 +1173,7 @@ detail::registry_state_type registry_state::st; //! @li @c Policy must contain a @c fn metafunction. //! //! @see @ref policies +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template class registry : public detail::registry_base { @@ -1160,9 +1211,11 @@ class registry : public detail::registry_base { //! `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: + //! which the registry's state is keyed. Two structs that derive from the + //! same specialization therefore share one state, and are one registry; + //! see @ref registry for how to keep two of them apart. It also 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; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f9f4524b..0edda63c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -155,6 +155,26 @@ 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") +# The constrained `cast` is removed from the overload set, so the diagnostic is +# the compiler's own overload resolution failure, whose wording varies: "no +# matching function for call to" on clang and gcc, "no matching overloaded +# function found" on MSVC. +openmethod_compile_fail_test( + compile_fail_boost_any_const_ref_to_mutable_ref "no matching") +openmethod_compile_fail_test( + compile_fail_boost_any_mutable_ref_to_rvalue_ref "no matching") +openmethod_compile_fail_test( + compile_fail_virtual_any_by_value "virtual_any must be passed by reference") +openmethod_compile_fail_test( + compile_fail_virtual_any_ref_by_ref + "virtual_any_ref is a cheap handle, pass it by value") +# "use of a deleted function" on gcc, "call to deleted function" on clang, +# "attempting to reference a deleted function" on MSVC. +openmethod_compile_fail_test( + compile_fail_final_virtual_ptr_std_any "deleted function") +openmethod_compile_fail_test( + compile_fail_virtual_ptr_inplace_vptr + "do not wrap an object that has a boost_openmethod_vptr overload") if (TARGET Boost::dll) add_subdirectory(dynamic_loading) diff --git a/test/Jamfile b/test/Jamfile index a1c69c4e..10ab8c56 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -20,6 +20,7 @@ project cxx17_structured_bindings ] /boost/openmethod//boost_openmethod + /boost/any//boost_any extra diff --git a/test/compile_fail_boost_any_const_ref_to_mutable_ref.cpp b/test/compile_fail_boost_any_const_ref_to_mutable_ref.cpp new file mode 100644 index 00000000..99058c55 --- /dev/null +++ b/test/compile_fail_boost_any_const_ref_to_mutable_ref.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_boost_any_types); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +// The `any` is const, so boost::any_cast cannot produce a mutable reference to +// the value it contains. Without the constraint on `cast`, this would fail +// inside Boost.Any instead of at the trait. +BOOST_OPENMETHOD_OVERRIDE(name, (Dog & dog), std::string) { + return dog.name; +} + +int main() { + return 0; +} diff --git a/test/compile_fail_boost_any_mutable_ref_to_rvalue_ref.cpp b/test/compile_fail_boost_any_mutable_ref_to_rvalue_ref.cpp new file mode 100644 index 00000000..d738e2d2 --- /dev/null +++ b/test/compile_fail_boost_any_mutable_ref_to_rvalue_ref.cpp @@ -0,0 +1,41 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_boost_any_types); + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +// Unlike std::any_cast, boost::any_cast binds an rvalue reference to the value +// stored in an lvalue `any`, which would let this overrider move the value out +// of an `any` the caller still owns. Moving the value out must go through a +// virtual_ parameter. +// +// The overrider is registered via method<...>::override because +// BOOST_OPENMETHOD_OVERRIDE cannot locate a method whose virtual parameter is +// a mutable lvalue reference to `any` - see test_dispatch_boost_any.cpp. +auto bump_dog(Dog&& dog) -> std::string { + return std::move(dog.name); +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +int main() { + return 0; +} diff --git a/test/compile_fail_final_virtual_ptr_std_any.cpp b/test/compile_fail_final_virtual_ptr_std_any.cpp new file mode 100644 index 00000000..60910da3 --- /dev/null +++ b/test/compile_fail_final_virtual_ptr_std_any.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +int main() { + // The primary final_virtual_ptr would use static_vptr - the + // v-table of the `any` root class, not of the contained value. The + // combination is deleted; use virtual_any instead. + std::any spot(Dog{"Spot"}); + final_virtual_ptr(spot); + return 0; +} diff --git a/test/compile_fail_virtual_any_by_value.cpp b/test/compile_fail_virtual_any_by_value.cpp new file mode 100644 index 00000000..fa30d78e --- /dev/null +++ b/test/compile_fail_virtual_any_by_value.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +// A virtual_any method parameter must be a reference: passing it by value +// would copy the `any` - and its payload - on every call. +BOOST_OPENMETHOD(name, (virtual_std_any), std::string); + +int main() { + virtual_std_any dog = Dog{"Snoopy"}; + return name(dog).size(); +} diff --git a/test/compile_fail_virtual_any_ref_by_ref.cpp b/test/compile_fail_virtual_any_ref_by_ref.cpp new file mode 100644 index 00000000..492e5996 --- /dev/null +++ b/test/compile_fail_virtual_any_ref_by_ref.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include + +using namespace boost::openmethod; + +struct Dog { + std::string name; +}; + +BOOST_OPENMETHOD_REGISTER(use_std_any_types); + +// A virtual_any_ref method parameter is passed by value: it is a cheap, +// two-word handle; a reference would add an indirection for nothing. +BOOST_OPENMETHOD(name, (const virtual_any_ref&), std::string); + +int main() { + std::any dog(Dog{"Snoopy"}); + return name(virtual_any_ref(dog)).size(); +} diff --git a/test/compile_fail_virtual_ptr_inplace_vptr.cpp b/test/compile_fail_virtual_ptr_inplace_vptr.cpp new file mode 100644 index 00000000..f0043950 --- /dev/null +++ b/test/compile_fail_virtual_ptr_inplace_vptr.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::openmethod; + +struct Animal : inplace_vptr_base { + virtual ~Animal() = default; +}; + +// An object with a boost_openmethod_vptr overload carries its own v-table +// pointer; wrapping it in a virtual_ptr is rejected at compile time. + +int main() { + Animal animal; + virtual_ptr p(animal); + return 0; +} diff --git a/test/test_core.cpp b/test/test_core.cpp index 5fd126b6..cc2d9695 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -283,20 +283,16 @@ namespace TEST_NS { using test_registry = test_registry_<__COUNTER__>; -const detail::word value; - struct Animal { - friend auto boost_openmethod_vptr(const Animal&, test_registry*) { - return &value; - } + friend auto + boost_openmethod_vptr(const Animal&, test_registry*) -> vptr_type; }; static_assert(detail::has_vptr_fn); static_assert(!detail::has_vptr_fn); -BOOST_AUTO_TEST_CASE(vptr_from_function) { - initialize(); - BOOST_TEST(detail::acquire_vptr(Animal{}) == &value); -} +// The hook serves dispatch (method::vptr), not virtual_ptr: acquire_vptr +// rejects classes with a boost_openmethod_vptr overload at compile time - +// see compile_fail_virtual_ptr_inplace_vptr.cpp. } // namespace TEST_NS diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp new file mode 100644 index 00000000..7142f9bc --- /dev/null +++ b/test/test_dispatch_boost_any.cpp @@ -0,0 +1,289 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE dispatch_boost_any +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const boost::any& (const ref) + +static_assert(detail::has_vptr< + virtual_traits, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(boost_any_by_const_ref) { + initialize(trace()); + + const boost::any spot(Dog{"Spot"}); + const boost::any felix(std::string{"Felix the cat"}); + const boost::any answer(42); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(felix) == "Felix the cat"); + BOOST_TEST(name(answer) == "42 the integer"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as boost::any& (mutable ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this. It locates the method by +// checking that the overrider's parameter types can be passed to the method's +// forwarder (see enable_forwarder and the guide function in macros.hpp), and +// `Dog&` does not convert to `boost::any&`. A temporary `boost::any` binds to +// `const boost::any&` and to `boost::any&&`, which is why the other two +// reference categories can use the macro; nothing binds to a mutable lvalue +// reference. Register directly via method<...>::override instead - the +// primitive the macro itself expands to. + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_string(std::string& name) -> std::string { + name += "!"; + return name; +} + +auto bump_int(int& value) -> std::string { + ++value; + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(boost_any_by_mutable_ref) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + boost::any felix(std::string{"Felix the cat"}); + boost::any answer(41); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(boost::any_cast(spot).name == "Spot Jr."); + + BOOST_TEST(bump(felix) == "Felix the cat!"); + BOOST_TEST(boost::any_cast(felix) == "Felix the cat!"); + + BOOST_TEST(bump(answer) == "42 the integer"); + BOOST_TEST(boost::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as boost::any&& (xvalue ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (int&& value), std::string) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(boost_any_by_xvalue_ref) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the `any` still owns the Dog + BOOST_TEST(!spot.empty()); + BOOST_TEST(boost::any_cast(spot).name == ""); + + boost::any felix(std::string{"Felix the cat"}); + BOOST_TEST(steal(std::move(felix)) == "Felix the cat"); + BOOST_TEST(!felix.empty()); + BOOST_TEST(boost::any_cast(felix) == ""); + + // moving an int copies it + boost::any answer(42); + BOOST_TEST(steal(std::move(answer)) == "42 the integer"); + BOOST_TEST(boost::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// catch-all overriders + +#define MAKE_CATCH_ALL_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +MAKE_CATCH_ALL_CLASSES(); + +// An overrider may take the `any` itself. Since every registered type +// derives from it, such an overrider is a catch-all, applying to any +// contained type that has no more specific overrider. + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const boost::any&), std::string) { + return "something else"; +} + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_any(boost::any&) -> std::string { + return "something else"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (boost::any&&), std::string) { + return "something else"; +} + +BOOST_AUTO_TEST_CASE(boost_any_catch_all) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + boost::any pi(3.14f); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(pi) == "something else"); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(bump(pi) == "something else"); + + BOOST_TEST(steal(boost::any(Dog{"Fido"})) == "Fido the dog"); + BOOST_TEST(steal(std::move(pi)) == "something else"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// `any` and ordinary virtual parameters mixed in one method + +MAKE_CATCH_ALL_CLASSES(); + +struct Animal { + virtual ~Animal() { + } +}; + +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat); + +BOOST_OPENMETHOD( + meet, (virtual_, virtual_ptr), + std::string); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const Dog& dog, virtual_ptr), std::string) { + return dog.name + " meets a cat"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const boost::any&, virtual_ptr), std::string) { + return "someone meets an animal"; +} + +BOOST_AUTO_TEST_CASE(boost_any_mixed_with_virtual_ptr) { + initialize(trace()); + + boost::any spot(Dog{"Spot"}); + boost::any pi(3.14f); + Cat felix; + + BOOST_TEST(meet(spot, felix) == "Spot meets a cat"); + BOOST_TEST(meet(pi, felix) == "someone meets an animal"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp new file mode 100644 index 00000000..61ee19f5 --- /dev/null +++ b/test/test_dispatch_std_any.cpp @@ -0,0 +1,288 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const std::any& (const ref) + +static_assert(detail::has_vptr< + virtual_traits, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(std_any_by_const_ref) { + initialize(trace()); + + const std::any spot(Dog{"Spot"}); + const std::any felix(std::string{"Felix the cat"}); + const std::any answer(42); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(felix) == "Felix the cat"); + BOOST_TEST(name(answer) == "42 the integer"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as std::any& (mutable ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this. It locates the method by +// checking that the overrider's parameter types can be passed to the method's +// forwarder (see enable_forwarder and the guide function in macros.hpp), and +// `Dog&` does not convert to `std::any&`. A temporary `std::any` binds to +// `const std::any&` and to `std::any&&`, which is why the other two reference +// categories can use the macro; nothing binds to a mutable lvalue reference. +// Register directly via method<...>::override instead - the primitive the +// macro itself expands to. + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_string(std::string& name) -> std::string { + name += "!"; + return name; +} + +auto bump_int(int& value) -> std::string { + ++value; + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(std_any_by_mutable_ref) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + std::any felix(std::string{"Felix the cat"}); + std::any answer(41); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(std::any_cast(spot).name == "Spot Jr."); + + BOOST_TEST(bump(felix) == "Felix the cat!"); + BOOST_TEST(std::any_cast(felix) == "Felix the cat!"); + + BOOST_TEST(bump(answer) == "42 the integer"); + BOOST_TEST(std::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as std::any&& (xvalue ref) + +static_assert( + detail::has_vptr, type_id>); + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (int&& value), std::string) { + std::ostringstream os; + os << value << " the integer"; + return os.str(); +} + +BOOST_AUTO_TEST_CASE(std_any_by_xvalue_ref) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the `any` still owns the Dog + BOOST_TEST(spot.has_value()); + BOOST_TEST(std::any_cast(spot).name == ""); + + std::any felix(std::string{"Felix the cat"}); + BOOST_TEST(steal(std::move(felix)) == "Felix the cat"); + BOOST_TEST(felix.has_value()); + BOOST_TEST(std::any_cast(felix) == ""); + + // moving an int copies it + std::any answer(42); + BOOST_TEST(steal(std::move(answer)) == "42 the integer"); + BOOST_TEST(std::any_cast(answer) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// catch-all overriders + +#define MAKE_CATCH_ALL_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +MAKE_CATCH_ALL_CLASSES(); + +// An overrider may take the `any` itself. Since every registered type +// derives from it, such an overrider is a catch-all, applying to any +// contained type that has no more specific overrider. + +BOOST_OPENMETHOD(name, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::any&), std::string) { + return "something else"; +} + +BOOST_OPENMETHOD(bump, (virtual_), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_any(std::any&) -> std::string { + return "something else"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_OPENMETHOD(steal, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::any&&), std::string) { + return "something else"; +} + +BOOST_AUTO_TEST_CASE(std_any_catch_all) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + std::any pi(3.14f); + + BOOST_TEST(name(spot) == "Spot the dog"); + BOOST_TEST(name(pi) == "something else"); + + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + BOOST_TEST(bump(pi) == "something else"); + + BOOST_TEST(steal(std::any(Dog{"Fido"})) == "Fido the dog"); + BOOST_TEST(steal(std::move(pi)) == "something else"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// `any` and ordinary virtual parameters mixed in one method + +MAKE_CATCH_ALL_CLASSES(); + +struct Animal { + virtual ~Animal() { + } +}; + +struct Cat : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat); + +BOOST_OPENMETHOD( + meet, (virtual_, virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const Dog& dog, virtual_ptr), std::string) { + return dog.name + " meets a cat"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (const std::any&, virtual_ptr), std::string) { + return "someone meets an animal"; +} + +BOOST_AUTO_TEST_CASE(std_any_mixed_with_virtual_ptr) { + initialize(trace()); + + std::any spot(Dog{"Spot"}); + std::any pi(3.14f); + Cat felix; + + BOOST_TEST(meet(spot, felix) == "Spot meets a cat"); + BOOST_TEST(meet(pi, felix) == "someone meets an animal"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp new file mode 100644 index 00000000..a9e87588 --- /dev/null +++ b/test/test_virtual_any_boost.cpp @@ -0,0 +1,244 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const virtual_boost_any& (const ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_boost_any&), std::string); + +// The overriders can use the macro: the value constructor of virtual_any +// makes the overrider's parameter convertible to the method's, so the +// method is located. + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; +} + +// A catch-all overrider may keep the wrapper. +BOOST_OPENMETHOD_OVERRIDE(name, (const virtual_boost_any& va), std::string) { + return !va.get().empty() ? "something" : "nothing"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_const_ref) { + initialize(trace()); + + // from an `any`: the v-table pointer is looked up from the dynamic + // type of the contained value + const boost::any spot_any(Dog{"Spot"}); + virtual_boost_any spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(name(spot) == "Spot the dog"); + + // from a value: the v-table pointer is set statically + virtual_boost_any rex = Dog{"Rex"}; + BOOST_TEST(rex.vptr() == default_registry::static_vptr); + BOOST_TEST(name(rex) == "Rex the dog"); + + virtual_boost_any felix = std::string("Felix the cat"); + BOOST_TEST(felix.vptr() == default_registry::static_vptr); + BOOST_TEST(name(felix) == "Felix the cat"); + + // a value converts to a (temporary) virtual_any at the call site + BOOST_TEST(name(Dog{"Fido"}) == "Fido the dog"); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `boost::any` root, applies + BOOST_TEST(name(42) == "something"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_boost_any& (mutable ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_boost_any&), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this: a temporary virtual_any +// binds to `const virtual_boost_any&` and to `virtual_boost_any&&`, but +// nothing binds to a mutable lvalue reference. Register directly via +// method<...>::override instead - the primitive the macro itself +// expands to. + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_boost_any&), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_int(int& value) -> std::string { + ++value; + return "bumped"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_by_mutable_ref) { + initialize(trace()); + + virtual_boost_any spot = Dog{"Spot"}; + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + // the mutation is visible through the virtual_any + BOOST_TEST(boost::any_cast(spot.get()).name == "Spot Jr."); + + virtual_boost_any answer = 41; + BOOST_TEST(bump(answer) == "bumped"); + BOOST_TEST(boost::any_cast(answer.get()) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_boost_any&& (xvalue ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_boost_any&&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_xvalue_ref) { + initialize(trace()); + + virtual_boost_any spot = Dog{"Spot"}; + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the virtual_any still owns the Dog + BOOST_TEST(!spot.get().empty()); + BOOST_TEST(boost::any_cast(spot.get()).name == ""); + + BOOST_TEST( + steal(virtual_boost_any(std::string("Felix the cat"))) == + "Felix the cat"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// value semantics + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_boost_any&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_value_semantics) { + initialize(trace()); + + virtual_boost_any empty; + BOOST_TEST(empty.get().empty()); + BOOST_TEST(empty.vptr() == nullptr); + + virtual_boost_any rex = Dog{"Rex"}; + + // copy: independent payloads, same v-table pointer + auto copy = rex; + BOOST_TEST(copy.vptr() == rex.vptr()); + BOOST_TEST(name(copy) == "Rex the dog"); + BOOST_TEST(name(rex) == "Rex the dog"); // original unaffected + + // move: the source's v-table pointer is nulled + auto moved = std::move(copy); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(copy.vptr() == nullptr); + BOOST_TEST(name(moved) == "Rex the dog"); + + // assignment from an `any` re-derives the v-table pointer + boost::any felix_any(std::string{"Felix"}); + moved = felix_any; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + + // assignment from a value sets it statically + moved = Dog{"Snoopy"}; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(name(moved) == "Snoopy the dog"); + + // emplace constructs in place and sets it statically + moved.emplace("Sylvester"); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(boost::any_cast(moved.get()) == "Sylvester"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// indirect vptrs + +struct Dog { + std::string name; +}; + +use_boost_any_types + BOOST_OPENMETHOD_GENSYM; + +using name_method = method< + struct name_id, + std::string(const virtual_any&), + indirect_registry>; + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_indirect_vptr) { + initialize(); + + boost::any spot_any(Dog{"Spot"}); + virtual_any spot = spot_any; + BOOST_TEST(spot.vptr() == indirect_registry::static_vptr); + BOOST_TEST(name_method::fn(spot) == "Spot the dog"); + + virtual_any rex = Dog{"Rex"}; + BOOST_TEST(name_method::fn(rex) == "Rex the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_any_ref.cpp b/test/test_virtual_any_ref.cpp new file mode 100644 index 00000000..70967ee2 --- /dev/null +++ b/test/test_virtual_any_ref.cpp @@ -0,0 +1,238 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// const handle: virtual_any_ref + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (virtual_any_ref), std::string); + +// A plain value does not convert to a virtual_any_ref, so +// BOOST_OPENMETHOD_OVERRIDE cannot locate the method for overriders that +// take the contained value. Register them with the core API instead - the +// primitive the macro itself expands to. + +using name_method = + BOOST_OPENMETHOD_TYPE(name, (virtual_any_ref), std::string); + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +auto name_string(const std::string& name) -> std::string { + return name; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); +BOOST_OPENMETHOD_REGISTER(name_method::override); + +// The catch-all overrider takes the handle itself, by value; the macro +// locates the method, since the conversion is the identity. +BOOST_OPENMETHOD_OVERRIDE( + name, (virtual_any_ref va), std::string) { + return va.get().has_value() ? "something" : "nothing"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_ref_const) { + initialize(trace()); + + // from an `any`: the v-table pointer is looked up from the dynamic + // type of the contained value + const std::any spot_any(Dog{"Spot"}); + virtual_any_ref spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(&spot.get() == &spot_any); + BOOST_TEST(name(spot) == "Spot the dog"); + + // an `any` lvalue converts to a (temporary) handle at the call site + std::any felix_any(std::string{"Felix the cat"}); + BOOST_TEST(name(felix_any) == "Felix the cat"); + + // from a virtual_any: the v-table pointer is copied - no lookup + const virtual_std_any rex = Dog{"Rex"}; + virtual_any_ref rex_ref = rex; + BOOST_TEST(rex_ref.vptr() == rex.vptr()); + BOOST_TEST(name(rex_ref) == "Rex the dog"); + + // a mutable handle converts to a const one + std::any answer_any(42); + virtual_any_ref answer = answer_any; + virtual_any_ref const_answer = answer; + BOOST_TEST(const_answer.vptr() == answer.vptr()); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `std::any` root, applies + BOOST_TEST(name(const_answer) == "something"); + + // copying a handle copies the two words; both refer to the same `any` + auto copy = spot; + BOOST_TEST(©.get() == &spot_any); + BOOST_TEST(copy.vptr() == spot.vptr()); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// mutable handle: virtual_any_ref + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_any_ref), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_any_ref), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_int(int& value) -> std::string { + ++value; + return "bumped"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_ref_mutable) { + initialize(trace()); + + // the handle borrows the `any`; mutations reach the referent + std::any spot_any(Dog{"Spot"}); + BOOST_TEST(bump(spot_any) == "Spot Jr. the dog"); + BOOST_TEST(std::any_cast(spot_any).name == "Spot Jr."); + + std::any answer_any(41); + virtual_any_ref answer = answer_any; + BOOST_TEST(bump(answer) == "bumped"); + BOOST_TEST(std::any_cast(answer_any) == 42); + + // borrowing from a virtual_any: mutations reach the owner's value + virtual_std_any rex = Dog{"Rex"}; + virtual_any_ref rex_ref = rex; + BOOST_TEST(rex_ref.vptr() == rex.vptr()); + BOOST_TEST(bump(rex_ref) == "Rex Jr. the dog"); + BOOST_TEST(std::any_cast(rex.get()).name == "Rex Jr."); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// boost::any: virtual_any_ref is generic over the `any` type + +struct Dog { + std::string name; +}; + +use_boost_any_types BOOST_OPENMETHOD_GENSYM; + +BOOST_OPENMETHOD(name, (virtual_any_ref), std::string); + +using name_method = BOOST_OPENMETHOD_TYPE( + name, (virtual_any_ref), std::string); + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_OPENMETHOD_OVERRIDE( + name, (virtual_any_ref va), std::string) { + return va.get().empty() ? "nothing" : "something"; +} + +BOOST_OPENMETHOD(bump, (virtual_any_ref), std::string); + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_any_ref), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_ref_boost_any) { + initialize(trace()); + + const boost::any spot_any(Dog{"Spot"}); + virtual_any_ref spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(name(spot) == "Spot the dog"); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `boost::any` root, applies + boost::any answer_any(42); + BOOST_TEST(name(answer_any) == "something"); + + // mutations through a mutable handle reach the referent + boost::any rex_any(Dog{"Rex"}); + BOOST_TEST(bump(rex_any) == "Rex Jr. the dog"); + BOOST_TEST(boost::any_cast(rex_any).name == "Rex Jr."); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// indirect vptrs + +struct Dog { + std::string name; +}; + +use_std_any_types + BOOST_OPENMETHOD_GENSYM; + +using name_method = method< + struct name_id, + std::string(virtual_any_ref), + indirect_registry>; + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_ref_indirect_vptr) { + initialize(); + + std::any spot_any(Dog{"Spot"}); + virtual_any_ref spot = spot_any; + BOOST_TEST(spot.vptr() == indirect_registry::static_vptr); + BOOST_TEST(name_method::fn(spot) == "Spot the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp new file mode 100644 index 00000000..bb41a1b7 --- /dev/null +++ b/test/test_virtual_any_std.cpp @@ -0,0 +1,244 @@ +// Copyright (c) 2018-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +#define MAKE_CLASSES() \ + struct Dog { \ + std::string name; \ + }; \ + \ + use_std_any_types BOOST_OPENMETHOD_GENSYM; + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as const virtual_std_any& (const ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_std_any&), std::string); + +// The overriders can use the macro: the value constructor of virtual_any +// makes the overrider's parameter convertible to the method's, so the +// method is located. + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) { + return name; +} + +// A catch-all overrider may keep the wrapper. +BOOST_OPENMETHOD_OVERRIDE(name, (const virtual_std_any& va), std::string) { + return va.get().has_value() ? "something" : "nothing"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_const_ref) { + initialize(trace()); + + // from an `any`: the v-table pointer is looked up from the dynamic + // type of the contained value + const std::any spot_any(Dog{"Spot"}); + virtual_std_any spot = spot_any; + BOOST_TEST(spot.vptr() == default_registry::static_vptr); + BOOST_TEST(name(spot) == "Spot the dog"); + + // from a value: the v-table pointer is set statically + virtual_std_any rex = Dog{"Rex"}; + BOOST_TEST(rex.vptr() == default_registry::static_vptr); + BOOST_TEST(name(rex) == "Rex the dog"); + + virtual_std_any felix = std::string("Felix the cat"); + BOOST_TEST(felix.vptr() == default_registry::static_vptr); + BOOST_TEST(name(felix) == "Felix the cat"); + + // a value converts to a (temporary) virtual_any at the call site + BOOST_TEST(name(Dog{"Fido"}) == "Fido the dog"); + + // `int` is registered, but has no specific overrider: the catch-all, + // registered for the `std::any` root, applies + BOOST_TEST(name(42) == "something"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_std_any& (mutable ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(bump, (virtual_std_any&), std::string); + +// BOOST_OPENMETHOD_OVERRIDE cannot express this: a temporary virtual_any +// binds to `const virtual_std_any&` and to `virtual_std_any&&`, but +// nothing binds to a mutable lvalue reference. Register directly via +// method<...>::override instead - the primitive the macro itself +// expands to. + +using bump_method = + BOOST_OPENMETHOD_TYPE(bump, (virtual_std_any&), std::string); + +auto bump_dog(Dog& dog) -> std::string { + dog.name += " Jr."; + return dog.name + " the dog"; +} + +auto bump_int(int& value) -> std::string { + ++value; + return "bumped"; +} + +BOOST_OPENMETHOD_REGISTER(bump_method::override); +BOOST_OPENMETHOD_REGISTER(bump_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_by_mutable_ref) { + initialize(trace()); + + virtual_std_any spot = Dog{"Spot"}; + BOOST_TEST(bump(spot) == "Spot Jr. the dog"); + // the mutation is visible through the virtual_any + BOOST_TEST(std::any_cast(spot.get()).name == "Spot Jr."); + + virtual_std_any answer = 41; + BOOST_TEST(bump(answer) == "bumped"); + BOOST_TEST(std::any_cast(answer.get()) == 42); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// pass virtual args as virtual_std_any&& (xvalue ref) + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(steal, (virtual_std_any&&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(steal, (Dog && dog), std::string) { + Dog stolen(std::move(dog)); + return stolen.name + " the dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(steal, (std::string && name), std::string) { + std::string stolen(std::move(name)); + return stolen; +} + +BOOST_AUTO_TEST_CASE(virtual_any_by_xvalue_ref) { + initialize(trace()); + + virtual_std_any spot = Dog{"Spot"}; + BOOST_TEST(steal(std::move(spot)) == "Spot the dog"); + // the overrider moved the name out; the virtual_any still owns the Dog + BOOST_TEST(spot.get().has_value()); + BOOST_TEST(std::any_cast(spot.get()).name == ""); + + BOOST_TEST( + steal(virtual_std_any(std::string("Felix the cat"))) == + "Felix the cat"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// value semantics + +MAKE_CLASSES(); + +BOOST_OPENMETHOD(name, (const virtual_std_any&), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.name + " the dog"; +} + +BOOST_AUTO_TEST_CASE(virtual_any_value_semantics) { + initialize(trace()); + + virtual_std_any empty; + BOOST_TEST(!empty.get().has_value()); + BOOST_TEST(empty.vptr() == nullptr); + + virtual_std_any rex = Dog{"Rex"}; + + // copy: independent payloads, same v-table pointer + auto copy = rex; + BOOST_TEST(copy.vptr() == rex.vptr()); + BOOST_TEST(name(copy) == "Rex the dog"); + BOOST_TEST(name(rex) == "Rex the dog"); // original unaffected + + // move: the source's v-table pointer is nulled + auto moved = std::move(copy); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(copy.vptr() == nullptr); + BOOST_TEST(name(moved) == "Rex the dog"); + + // assignment from an `any` re-derives the v-table pointer + std::any felix_any(std::string{"Felix"}); + moved = felix_any; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + + // assignment from a value sets it statically + moved = Dog{"Snoopy"}; + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(name(moved) == "Snoopy the dog"); + + // emplace constructs in place and sets it statically + moved.emplace("Sylvester"); + BOOST_TEST(moved.vptr() == default_registry::static_vptr); + BOOST_TEST(std::any_cast(moved.get()) == "Sylvester"); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +// ----------------------------------------------------------------------------- +// indirect vptrs + +struct Dog { + std::string name; +}; + +use_std_any_types + BOOST_OPENMETHOD_GENSYM; + +using name_method = method< + struct name_id, + std::string(const virtual_any&), + indirect_registry>; + +auto name_dog(const Dog& dog) -> std::string { + return dog.name + " the dog"; +} + +BOOST_OPENMETHOD_REGISTER(name_method::override); + +BOOST_AUTO_TEST_CASE(virtual_any_indirect_vptr) { + initialize(); + + std::any spot_any(Dog{"Spot"}); + virtual_any spot = spot_any; + BOOST_TEST(spot.vptr() == indirect_registry::static_vptr); + BOOST_TEST(name_method::fn(spot) == "Spot the dog"); + + virtual_any rex = Dog{"Rex"}; + BOOST_TEST(name_method::fn(rex) == "Rex the dog"); +} +} // namespace BOOST_OPENMETHOD_GENSYM diff --git a/test/test_virtual_ptr_doc.cpp b/test/test_virtual_ptr_doc.cpp deleted file mode 100644 index dbd95fb0..00000000 --- a/test/test_virtual_ptr_doc.cpp +++ /dev/null @@ -1,254 +0,0 @@ -// qright (c) 2018-2025 Jean-Louis Leroy -// Distributed under the Boost Software License, Version 1.0. -// See accompanying file LICENSE_1_0.txt -// or q at http://www.boost.org/LICENSE_1_0.txt) - -#include -#include -#include - -#define BOOST_TEST_MODULE openmethod -#include - -using namespace boost::openmethod; - -namespace polymorphic { - -struct Animal { - virtual ~Animal() { - } -}; -struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); -BOOST_OPENMETHOD(poke, (virtual_ptr), void); - -void instiantiate_poke(virtual_ptr snoopy) { - poke(snoopy); -} - -BOOST_AUTO_TEST_CASE(virtual_ptr_examples_polymorphic) { - { - initialize(trace()); - - { - virtual_ptr p{nullptr}; - - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - - { - Dog snoopy; - Animal& animal = snoopy; - - virtual_ptr p = animal; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - Dog snoopy; - Animal* animal = &snoopy; - - virtual_ptr p = animal; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr p{nullptr}; - Dog snoopy; - Animal* animal = &snoopy; - - p = animal; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - Dog snoopy; - virtual_ptr p = final_virtual_ptr(snoopy); - - p = nullptr; - - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - } -} - -BOOST_AUTO_TEST_CASE(smart_virtual_ptr_examples) { - initialize(); - - { - virtual_ptr> p; - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - - { - virtual_ptr> p{nullptr}; - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - } - - { - const std::shared_ptr snoopy = std::make_shared(); - virtual_ptr> p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - std::shared_ptr snoopy = std::make_shared(); - virtual_ptr> p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - std::shared_ptr snoopy = std::make_shared(); - Dog* moving = snoopy.get(); - - virtual_ptr> p = std::move(snoopy); - - // coverity[use_after_move] - BOOST_TEST(p.get() == moving); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.get() == nullptr); - } - { - const virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr> p = std::move(snoopy); - - // coverity[use_after_move] - BOOST_TEST(snoopy.get() != nullptr); - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr> snoopy = make_shared_virtual(); - Dog* moving = snoopy.get(); - - virtual_ptr> p = std::move(snoopy); - - BOOST_TEST(p.get() == moving); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.get() == nullptr); - BOOST_TEST(snoopy.vptr() == nullptr); - } - - { - virtual_ptr> p = make_shared_virtual(); - - p = nullptr; - - BOOST_TEST(p.get() == nullptr); - BOOST_TEST(p.vptr() == nullptr); - BOOST_TEST((p == virtual_ptr>())); - } - - { - const virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr> p; - - p = snoopy; - - BOOST_TEST(p.get() != nullptr); - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr> snoopy = make_shared_virtual(); - Dog* moving = snoopy.get(); - virtual_ptr> p; - - p = std::move(snoopy); - - BOOST_TEST(p.get() == moving); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - BOOST_TEST(snoopy.get() == nullptr); - BOOST_TEST(snoopy.vptr() == nullptr); - } -} -} // namespace polymorphic - -namespace non_polymorphic { - -struct Animal {}; // polymorphic not required -struct Dog : Animal {}; // polymorphic not required -BOOST_OPENMETHOD_CLASSES(Animal, Dog); - -// codecov:ignore:start -BOOST_OPENMETHOD(poke, (virtual_ptr), void); - -void instantiate_poke(virtual_ptr snoopy) { - poke(snoopy); -} -// codecov:ignore:end - -BOOST_AUTO_TEST_CASE(virtual_ptr_examples_non_polymorphic) { - { - initialize(); - - { - Dog snoopy; - virtual_ptr dog = final_virtual_ptr(snoopy); - - virtual_ptr p(dog); - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - Dog snoopy; - virtual_ptr dog = final_virtual_ptr(snoopy); - virtual_ptr p{nullptr}; - - p = dog; - - BOOST_TEST(p.get() == &snoopy); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - { - virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - static_assert( - std::is_constructible_v< - shared_virtual_ptr, virtual_ptr> == false); - - { - virtual_ptr> snoopy = - make_shared_virtual(); - virtual_ptr p; - - p = snoopy; - - BOOST_TEST(p.get() == snoopy.get()); - BOOST_TEST(p.vptr() == default_registry::static_vptr); - } - - static_assert( - std::is_assignable_v< - shared_virtual_ptr&, virtual_ptr> == false); - } -} -} // namespace non_polymorphic