diff --git a/.clang-format b/.clang-format index b26254bd..9e499faf 100644 --- a/.clang-format +++ b/.clang-format @@ -44,10 +44,10 @@ BraceWrapping: BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false -BreakInheritanceList: BeforeColon +BreakInheritanceList: AfterColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false -BreakConstructorInitializers: BeforeColon +BreakConstructorInitializers: AfterColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 80 @@ -102,7 +102,7 @@ PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left ReflowComments: false SortIncludes: false @@ -133,4 +133,6 @@ StatementMacros: TabWidth: 8 UseCRLF: false UseTab: Never +TemplateNames: + - register_classes ... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 874b7827..60987562 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,48 @@ jobs: COVERITY_SCAN_NOTIFICATION_EMAIL: ${{ secrets.COVERITY_SCAN_NOTIFICATION_EMAIL }} COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + reflection: + name: C++26 reflection + runs-on: ubuntu-24.04 + steps: + - name: Install GCC 16 + run: | + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install -y g++-16 cmake ninja-build + + - name: Clone Boost.OpenMethod + uses: actions/checkout@v4 + + - name: Clone Boost + uses: alandefreitas/cpp-actions/boost-clone@v1.8.8 + with: + # boost-clone excludes `test` and `tests` from the scan + # (modules-exclude-paths defaults to them), so it never sees the + # includes in our own test/ and would clone neither Boost.Test nor + # Boost.DLL - CMake then fails to generate on a missing + # Boost::unit_test_framework. `modules` is unioned with the scan, + # and their own dependencies are resolved afterwards. + modules: test dll + branch: ${{ (github.ref_name == 'master' && github.ref_name) || 'develop' }} + boost-dir: ../boost-source + scan-modules-dir: . + scan-modules-ignore: openmethod + + # The suite is the reflection test: BOOST_OPENMETHOD_TEST_CLASSES expands + # to nothing here, so every class has to be found by use_classes_in. + - name: Build and test + run: | + cmake -S . -B ../build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_COMPILER=g++-16 \ + -DBOOST_OPENMETHOD_ENABLE_REFLECTION=ON \ + -DBOOST_OPENMETHOD_BUILD_TESTS=ON \ + -DBOOST_OPENMETHOD_WARNINGS_AS_ERRORS=ON \ + -DBOOST_SRC_DIR="$(cd .. && pwd)/boost-source" + cmake --build ../build --target tests -j $(nproc) + ctest --test-dir ../build -j $(nproc) --output-on-failure + antora: name: Antora docs strategy: diff --git a/CMakeLists.txt b/CMakeLists.txt index 5224a251..9f119dfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,81 @@ option( BOOST_OPENMETHOD_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF) +option( + BOOST_OPENMETHOD_ENABLE_REFLECTION + "Build the tests and examples with C++26 reflection enabled" + OFF) + +# C++26 reflection (P2996). The library detects it on its own, from +# __cpp_impl_reflection; this only arranges for the tests to be built in a mode +# where the compiler provides it, which needs both C++26 and, on GCC, an opt-in +# flag. It is applied per target rather than through CMAKE_CXX_FLAGS, because +# CMake probes the compiler before CMAKE_CXX_STANDARD takes effect and GCC +# rejects -freflection under any other standard. +set(BOOST_OPENMETHOD_REFLECTION_OPTIONS "") + +if (BOOST_OPENMETHOD_ENABLE_REFLECTION) + include(CheckCXXSourceCompiles) + + set(BOOST_OPENMETHOD_REFLECTION_TEST_SOURCE [[ + #include + struct Base {}; + struct Derived : Base {}; + consteval auto count() -> int { + return static_cast( + std::meta::bases_of( + ^^Derived, std::meta::access_context::unchecked()).size()); + } + static_assert(count() == 1); + int main() {} + ]]) + + set(CMAKE_REQUIRED_QUIET ON) + + foreach(candidate "-std=c++26" "-std=c++26;-freflection") + string(REPLACE ";" " " candidate_flags "${candidate}") + set(CMAKE_REQUIRED_FLAGS "${candidate_flags}") + unset(BOOST_OPENMETHOD_HAS_REFLECTION CACHE) + check_cxx_source_compiles( + "${BOOST_OPENMETHOD_REFLECTION_TEST_SOURCE}" + BOOST_OPENMETHOD_HAS_REFLECTION) + + if (BOOST_OPENMETHOD_HAS_REFLECTION) + set(BOOST_OPENMETHOD_REFLECTION_OPTIONS ${candidate}) + break() + endif() + endforeach() + + unset(CMAKE_REQUIRED_FLAGS) + unset(CMAKE_REQUIRED_QUIET) + + if (NOT BOOST_OPENMETHOD_HAS_REFLECTION) + message( + FATAL_ERROR + "BOOST_OPENMETHOD_ENABLE_REFLECTION is ON but ${CMAKE_CXX_COMPILER_ID} " + "${CMAKE_CXX_COMPILER_VERSION} does not support C++26 reflection") + endif() + + message( + STATUS + "Boost.OpenMethod: C++26 reflection enabled" + " [${BOOST_OPENMETHOD_REFLECTION_OPTIONS}]") +endif() + +# Build `target` with C++26 reflection, if BOOST_OPENMETHOD_ENABLE_REFLECTION is +# ON. Does nothing otherwise, so callers need no condition of their own. +function(boost_openmethod_enable_reflection target) + if (NOT BOOST_OPENMETHOD_ENABLE_REFLECTION) + return() + endif() + + # The standard flag is passed here rather than through CXX_STANDARD: CMake + # learned the value 26 only in 3.30, and this project supports older ones. + # target_compile_options come after the flag CMake derives from the + # library's cxx_std_17 requirement, and the last -std wins. + target_compile_options( + ${target} PRIVATE ${BOOST_OPENMETHOD_REFLECTION_OPTIONS}) +endfunction() if (BOOST_OPENMETHOD_BUILD_EXAMPLES AND NOT BOOST_OPENMETHOD_BUILD_TESTS) message( diff --git a/doc/modules/ROOT/examples/CMakeLists.txt b/doc/modules/ROOT/examples/CMakeLists.txt index f922ab9e..2b0555f2 100644 --- a/doc/modules/ROOT/examples/CMakeLists.txt +++ b/doc/modules/ROOT/examples/CMakeLists.txt @@ -21,6 +21,7 @@ foreach (cpp ${cpp_files}) get_filename_component(stem ${cpp} NAME_WE) set(test_target "boost_openmethod-${stem}") add_executable(${test_target} ${cpp}) + boost_openmethod_enable_reflection(${test_target}) 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}) @@ -43,6 +44,7 @@ function(boost_openmethod_add_step_by_step dir) file(GLOB cpp_files "${subdir}/*.cpp") set(target "boost_openmethod-${dir}_${subex}") add_executable(${target} ${cpp_files}) + boost_openmethod_enable_reflection(${target}) target_link_libraries(${target} PRIVATE Boost::openmethod) set(output_dir openmethod/${dir}/${subex}) set_target_properties(${target} PROPERTIES diff --git a/doc/modules/ROOT/examples/accept_no_visitors.cpp b/doc/modules/ROOT/examples/accept_no_visitors.cpp index 4bcc656b..b63875eb 100644 --- a/doc/modules/ROOT/examples/accept_no_visitors.cpp +++ b/doc/modules/ROOT/examples/accept_no_visitors.cpp @@ -24,8 +24,8 @@ struct Node { struct Plus : Node { Plus( shared_virtual_ptr left, - shared_virtual_ptr right) - : left(std::move(left)), right(std::move(right)) { + shared_virtual_ptr right) : + left(std::move(left)), right(std::move(right)) { } shared_virtual_ptr left, right; @@ -34,8 +34,8 @@ struct Plus : Node { struct Times : Node { Times( shared_virtual_ptr left, - shared_virtual_ptr right) - : left(std::move(left)), right(std::move(right)) { + shared_virtual_ptr right) : + left(std::move(left)), right(std::move(right)) { } shared_virtual_ptr left, right; @@ -82,7 +82,8 @@ BOOST_OPENMETHOD_OVERRIDE(as_forth, (virtual_ptr node), string) { return as_forth(node->left) + " " + as_forth(node->right) + " *"; } -BOOST_OPENMETHOD_OVERRIDE(as_forth, (virtual_ptr node), string) { +BOOST_OPENMETHOD_OVERRIDE( + as_forth, (virtual_ptr node), string) { return std::to_string(node->value); } @@ -111,7 +112,8 @@ auto main() -> int { shared_virtual_ptr node = make_shared_virtual( make_shared_virtual(2), make_shared_virtual( - make_shared_virtual(3), make_shared_virtual(4))); + make_shared_virtual(3), + make_shared_virtual(4))); cout << as_forth(node) << " = " << as_lisp(node) << " = " << value(node) << "\n"; diff --git a/doc/modules/ROOT/examples/inplace_vptr.cpp b/doc/modules/ROOT/examples/inplace_vptr.cpp index ef07f99f..d23979e6 100644 --- a/doc/modules/ROOT/examples/inplace_vptr.cpp +++ b/doc/modules/ROOT/examples/inplace_vptr.cpp @@ -18,8 +18,7 @@ struct Cat : Animal, inplace_vptr_derived {}; struct Dog : Animal, inplace_vptr_derived {}; -BOOST_OPENMETHOD( - poke, (virtual_ animal, std::ostream& os), void); +BOOST_OPENMETHOD(poke, (virtual_ animal, std::ostream& os), void); BOOST_OPENMETHOD_OVERRIDE(poke, (Cat&, std::ostream& os), void) { os << "hiss\n"; diff --git a/doc/modules/ROOT/examples/matrix.cpp b/doc/modules/ROOT/examples/matrix.cpp index dbf8d6c9..c6f65664 100644 --- a/doc/modules/ROOT/examples/matrix.cpp +++ b/doc/modules/ROOT/examples/matrix.cpp @@ -22,8 +22,8 @@ struct abstract { int ref_count = 0; }; -struct registry - : boost::openmethod::registry {}; +struct registry : + boost::openmethod::registry {}; template using matrix_ptr = boost::openmethod::virtual_ptr; diff --git a/doc/modules/ROOT/examples/matrix_readme.cpp b/doc/modules/ROOT/examples/matrix_readme.cpp index 4cf4a74f..790db429 100644 --- a/doc/modules/ROOT/examples/matrix_readme.cpp +++ b/doc/modules/ROOT/examples/matrix_readme.cpp @@ -23,15 +23,18 @@ BOOST_OPENMETHOD_CLASSES(Matrix, SquareMatrix, SymmetricMatrix, DiagonalMatrix); BOOST_OPENMETHOD(to_json, (virtual_ptr, std::ostream& os), void); -BOOST_OPENMETHOD_OVERRIDE(to_json, (virtual_ptr, std::ostream& os), void) { +BOOST_OPENMETHOD_OVERRIDE( + to_json, (virtual_ptr, std::ostream& os), void) { os << "all the elements\n"; } -BOOST_OPENMETHOD_OVERRIDE(to_json, (virtual_ptr, std::ostream& os), void) { +BOOST_OPENMETHOD_OVERRIDE( + to_json, (virtual_ptr, std::ostream& os), void) { os << "elements above and including the diagonal\n"; } -BOOST_OPENMETHOD_OVERRIDE(to_json, (virtual_ptr, std::ostream& os), void) { +BOOST_OPENMETHOD_OVERRIDE( + to_json, (virtual_ptr, std::ostream& os), void) { os << "just the diagonal\n"; } diff --git a/doc/modules/ROOT/examples/rolex/1/main.cpp b/doc/modules/ROOT/examples/rolex/1/main.cpp index 55010bc6..576991cc 100644 --- a/doc/modules/ROOT/examples/rolex/1/main.cpp +++ b/doc/modules/ROOT/examples/rolex/1/main.cpp @@ -15,9 +15,10 @@ int main() { boost::openmethod::initialize(); Employee bill; - Salesman bob; bob.sales = 100'000.0; + Salesman bob; + bob.sales = 100'000.0; std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 - std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 } // end::content[] diff --git a/doc/modules/ROOT/examples/rolex/1/roles.hpp b/doc/modules/ROOT/examples/rolex/1/roles.hpp index ca280b66..4a75702f 100644 --- a/doc/modules/ROOT/examples/rolex/1/roles.hpp +++ b/doc/modules/ROOT/examples/rolex/1/roles.hpp @@ -11,7 +11,9 @@ #include -struct Employee { virtual ~Employee() = default; }; +struct Employee { + virtual ~Employee() = default; +}; struct Salesman : Employee { double sales = 0.0; diff --git a/doc/modules/ROOT/examples/rolex/2/main.cpp b/doc/modules/ROOT/examples/rolex/2/main.cpp index 55010bc6..576991cc 100644 --- a/doc/modules/ROOT/examples/rolex/2/main.cpp +++ b/doc/modules/ROOT/examples/rolex/2/main.cpp @@ -15,9 +15,10 @@ int main() { boost::openmethod::initialize(); Employee bill; - Salesman bob; bob.sales = 100'000.0; + Salesman bob; + bob.sales = 100'000.0; std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 - std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 } // end::content[] diff --git a/doc/modules/ROOT/examples/rolex/2/roles.hpp b/doc/modules/ROOT/examples/rolex/2/roles.hpp index 02e8b06f..74d9ccef 100644 --- a/doc/modules/ROOT/examples/rolex/2/roles.hpp +++ b/doc/modules/ROOT/examples/rolex/2/roles.hpp @@ -11,7 +11,9 @@ #include -struct Employee { virtual ~Employee() = default; }; +struct Employee { + virtual ~Employee() = default; +}; struct Salesman : Employee { double sales = 0.0; diff --git a/doc/modules/ROOT/examples/rolex/3/main.cpp b/doc/modules/ROOT/examples/rolex/3/main.cpp index 55010bc6..576991cc 100644 --- a/doc/modules/ROOT/examples/rolex/3/main.cpp +++ b/doc/modules/ROOT/examples/rolex/3/main.cpp @@ -15,9 +15,10 @@ int main() { boost::openmethod::initialize(); Employee bill; - Salesman bob; bob.sales = 100'000.0; + Salesman bob; + bob.sales = 100'000.0; std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 - std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 } // end::content[] diff --git a/doc/modules/ROOT/examples/rolex/3/roles.hpp b/doc/modules/ROOT/examples/rolex/3/roles.hpp index ae92c34d..32313969 100644 --- a/doc/modules/ROOT/examples/rolex/3/roles.hpp +++ b/doc/modules/ROOT/examples/rolex/3/roles.hpp @@ -11,7 +11,9 @@ #include -struct Employee { virtual ~Employee() = default; }; +struct Employee { + virtual ~Employee() = default; +}; struct Salesman : Employee { double sales = 0.0; diff --git a/doc/modules/ROOT/examples/rolex/4/main.cpp b/doc/modules/ROOT/examples/rolex/4/main.cpp index 5dea2ba0..1818021f 100644 --- a/doc/modules/ROOT/examples/rolex/4/main.cpp +++ b/doc/modules/ROOT/examples/rolex/4/main.cpp @@ -15,9 +15,10 @@ int main() { boost::openmethod::initialize(); employees::Employee bill; - sales::Salesman bob; bob.sales = 100'000.0; + sales::Salesman bob; + bob.sales = 100'000.0; std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 - std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 } // end::content[] diff --git a/doc/modules/ROOT/examples/rolex/4/roles.hpp b/doc/modules/ROOT/examples/rolex/4/roles.hpp index 500efeac..de41157a 100644 --- a/doc/modules/ROOT/examples/rolex/4/roles.hpp +++ b/doc/modules/ROOT/examples/rolex/4/roles.hpp @@ -24,7 +24,7 @@ BOOST_OPENMETHOD_INLINE_OVERRIDE( return 5000.0; } -} +} // namespace employees namespace sales { diff --git a/doc/modules/ROOT/examples/rolex/4/salesman.cpp b/doc/modules/ROOT/examples/rolex/4/salesman.cpp index 74c03ace..5234441f 100644 --- a/doc/modules/ROOT/examples/rolex/4/salesman.cpp +++ b/doc/modules/ROOT/examples/rolex/4/salesman.cpp @@ -12,7 +12,8 @@ namespace sales { BOOST_OPENMETHOD_OVERRIDE( pay, (boost::openmethod::virtual_ptr emp), double) { return employees::BOOST_OPENMETHOD_OVERRIDER( - pay, (boost::openmethod::virtual_ptr emp), + pay, + (boost::openmethod::virtual_ptr emp), double)::fn(emp) + emp->sales * 0.05; // base + commission } diff --git a/doc/modules/ROOT/examples/rolex/5/main.cpp b/doc/modules/ROOT/examples/rolex/5/main.cpp index 5adfe75f..c857352b 100644 --- a/doc/modules/ROOT/examples/rolex/5/main.cpp +++ b/doc/modules/ROOT/examples/rolex/5/main.cpp @@ -40,7 +40,8 @@ class Payroll { } friend BOOST_OPENMETHOD_OVERRIDER( - pay, (Payroll & payroll, boost::openmethod::virtual_ptr), + pay, + (Payroll & payroll, boost::openmethod::virtual_ptr), double); friend BOOST_OPENMETHOD_OVERRIDER( pay, @@ -83,8 +84,8 @@ int main() { Salesman bob; bob.sales = 100'000.0; - std::cout << "pay bill: $" << pay(payroll, bill) << "\n"; // $5000 - std::cout << "pay bob: $" << pay(payroll, bob) << "\n"; // 10000 + std::cout << "pay bill: $" << pay(payroll, bill) << "\n"; // $5000 + std::cout << "pay bob: $" << pay(payroll, bob) << "\n"; // 10000 std::cout << "remaining balance: $" << payroll.balance() << "\n"; // $985000 } // end::main[] diff --git a/doc/modules/ROOT/examples/throw_error_handler.cpp b/doc/modules/ROOT/examples/throw_error_handler.cpp index f1c520bd..2b94fa94 100644 --- a/doc/modules/ROOT/examples/throw_error_handler.cpp +++ b/doc/modules/ROOT/examples/throw_error_handler.cpp @@ -33,8 +33,8 @@ struct throw_if_not_implemented : bom::policies::error_handler { }; }; -struct custom_registry : bom::default_registry::with { -}; +struct custom_registry : + bom::default_registry::with {}; using boost::openmethod::virtual_ptr; diff --git a/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp b/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp index d8318db3..706cbad2 100644 --- a/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp +++ b/doc/modules/ROOT/examples/virtual_ptr_alt/1/virtual_ptr_alt.cpp @@ -11,21 +11,32 @@ struct Node { }; struct Variable : Node { - Variable(int value) : v(value) {} - int value() const override { return v; } + Variable(int value) : v(value) { + } + int value() const override { + return v; + } int v; }; struct Plus : Node { - Plus(const Node& left, const Node& right) : left(left), right(right) {} - int value() const override { return left.value() + right.value(); } - const Node& left; const Node& right; + Plus(const Node& left, const Node& right) : left(left), right(right) { + } + int value() const override { + return left.value() + right.value(); + } + const Node& left; + const Node& right; }; struct Times : Node { - Times(const Node& left, const Node& right) : left(left), right(right) {} - int value() const override { return left.value() * right.value(); } - const Node& left; const Node& right; + Times(const Node& left, const Node& right) : left(left), right(right) { + } + int value() const override { + return left.value() * right.value(); + } + const Node& left; + const Node& right; }; #include @@ -40,8 +51,7 @@ BOOST_OPENMETHOD_OVERRIDE( os << var.v; } -BOOST_OPENMETHOD_OVERRIDE( - postfix, (const Plus& plus, std::ostream& os), void) { +BOOST_OPENMETHOD_OVERRIDE(postfix, (const Plus& plus, std::ostream& os), void) { postfix(plus.left, os); os << ' '; postfix(plus.right, os); diff --git a/doc/modules/ROOT/examples/virtual_ptr_alt/3/virtual_ptr_alt.cpp b/doc/modules/ROOT/examples/virtual_ptr_alt/3/virtual_ptr_alt.cpp index c0039b82..f6619f34 100644 --- a/doc/modules/ROOT/examples/virtual_ptr_alt/3/virtual_ptr_alt.cpp +++ b/doc/modules/ROOT/examples/virtual_ptr_alt/3/virtual_ptr_alt.cpp @@ -8,24 +8,28 @@ #include // tag::content[] -struct Node : boost::openmethod::inplace_vptr_base { -}; +struct Node : boost::openmethod::inplace_vptr_base {}; -struct Variable : Node, boost::openmethod::inplace_vptr_derived { - Variable(int value) : v(value) {} +struct Variable : + Node, + boost::openmethod::inplace_vptr_derived { + Variable(int value) : v(value) { + } int v; }; struct Plus : Node, boost::openmethod::inplace_vptr_derived { - Plus(const Node& left, const Node& right) : left(left), right(right) {} + Plus(const Node& left, const Node& right) : left(left), right(right) { + } const Node& left; const Node& right; }; struct Times : Node, boost::openmethod::inplace_vptr_derived { - Times(const Node& left, const Node& right) : left(left), right(right) {} + Times(const Node& left, const Node& right) : left(left), right(right) { + } const Node& left; const Node& right; diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index 0d50f66d..95a9fbea 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -83,6 +83,13 @@ direct base of a class must appear together with it in at least one call to `BOOST_OPENMETHOD_CLASSES`. This enables the library to deduce the complete inheritance lattice. +[NOTE] +==== +With a compiler that supports C++26 reflection, the class list is unnecessary. +xref:reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc[BOOST_OPENMETHOD_REGISTER_CLASSES] +finds the classes on its own - see <>. +==== + The constructs used in this example require the classes to be polymorphic, in the standard C++ sense, i.e. they must have at least one virtual function. The library can also be used with non-polymorphic classes, with some restrictions. @@ -107,3 +114,102 @@ Putting it all together: ---- include::{examplesdir}/ast.cpp[tag=content] ---- + + +[#registering_classes_by_reflection] +## Registering Classes by Reflection + +When the compiler supports C++26 reflection (P2996), the library can work out +the class list for itself. One call to +xref:reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc[BOOST_OPENMETHOD_REGISTER_CLASSES] +replaces every +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] in the +file: + +[source,c++] +---- +struct Animal { virtual ~Animal() = default; }; +struct Cat : Animal {}; +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream& os, Dog&), void) { + os << "bark"; +} + +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); // registers all four classes +---- + +The macro scans the namespaces it is given - and the namespaces nested in them +- for the methods of the registry. It collects the classes those methods +dispatch on, then registers them, along with every class in the scanned +namespaces that derives from one of them. `Bulldog` above has no overrider of +its own and is named nowhere, and is registered all the same. + +The arguments come in four groups, each optional, in this order: reflections of +namespaces to scan; reflections of classes to register; one value combining +`register_classes_opts` options with `|`; and reflections of registries to register +the classes in - `boost::openmethod::default_registry` when none is named. A +listed class is registered whether a method dispatches on it or not, along with +the classes the scan finds derive from it. With classes but no namespace, +nothing is scanned: exactly the listed classes are registered, with the +inheritance relations between them read from reflection. With neither - +`BOOST_OPENMETHOD_REGISTER_CLASSES()` - the enclosing namespace is scanned. + +A base class that no method dispatches on, and that is not listed, is *not* +registered - it could never be selected on, and registering it would cost a +lattice node, a hash slot and dispatch table space for nothing. So a hierarchy +rooted in some general-purpose base contributes only the part of itself that +takes part in dispatch. As soon as another method does dispatch on that base, +it is registered, and the inheritance edges through it with it. + +A scan does not enter the `std` and `boost` namespaces, so `^^::` costs little +more than a narrower namespace would - a method cannot dispatch on a class the +program never heard of anyway. The `register_classes_opts::scan_std` and +`register_classes_opts::scan_boost` options bring them back in; a namespace *listed* +explicitly is always scanned. `register_classes_opts::no_recurse` restricts the scan +to the members declared directly in the listed namespaces. + +Reflection sees only what precedes it, so the macro must come *after* the +declarations it is meant to find. Putting it at the bottom of the file is the +simplest way to be sure. + +Virtual and multiple inheritance are supported. Unlike +`BOOST_OPENMETHOD_CLASSES`, which rejects it, repeated inheritance is not an +error here: an ambiguous base cannot take part in dispatch, so it is left out. + +Without reflection - in C++17, or in C++26 without the compiler flag that enables +it - the macro expands to nothing. A file that also calls +`BOOST_OPENMETHOD_CLASSES` therefore builds under either standard. + +### What Reflection Cannot Find + +A method is found through any declaration that names its `method` type: the +alias `BOOST_OPENMETHOD` declares alongside the method, a `using` declaration of +your own, or any of the method's registrar objects. None of those depends on the +method having an overrider, so a method declared with `BOOST_OPENMETHOD` is +always found. + +Three situations remain outside the scan's reach, and need a +`BOOST_OPENMETHOD_CLASSES` of their own: + +* a class in a namespace the macro does not scan; +* a core API method whose `method<...>` type is spelled out in full at every + use, with no `using` declaration of its own and no overrider - nothing names + it; +* a program that declares no method at all, and uses `virtual_ptr` on its own - + there is no virtual parameter for the scan to start from. + +### Turning It Off + +Adding the `policies::explicit_class_registration` policy to a registry stops +the library from registering anything on its own, in C++26 as in C++17: + +[source,c++] +---- +struct my_registry + : boost::openmethod::default_registry::with< + boost::openmethod::policies::explicit_class_registration> {}; +---- diff --git a/doc/modules/ROOT/pages/core_api.adoc b/doc/modules/ROOT/pages/core_api.adoc index 9d67461a..f1b3efc0 100644 --- a/doc/modules/ROOT/pages/core_api.adoc +++ b/doc/modules/ROOT/pages/core_api.adoc @@ -78,6 +78,23 @@ We register the classes with `use_classes`: include::{example}/core_api.cpp[tag=use_classes] ---- +With a compiler that supports C++26 reflection, `register_classes` replaces that +list. It scans one or more namespaces for the registry's methods, and registers +the classes they dispatch on along with everything in those namespaces that +derives from them: + +[source,c++] +---- +BOOST_OPENMETHOD_REGISTER(register_classes<^^::>); +---- + +A method declared the way `postfix` is above - an alias for a `method` +specialization - is found directly, and so is any of its `override` registrars. +Reflection sees only what precedes it, so this must come after the declarations +it is meant to find. See +xref:ROOT:basics.adoc#registering_classes_by_reflection[Registering Classes by +Reflection]. + Finally, we call the method via the static member of the method class `fn`: [source,c++] diff --git a/doc/modules/ROOT/pages/ref_macros.adoc b/doc/modules/ROOT/pages/ref_macros.adoc index c3250ee9..cf1513cd 100644 --- a/doc/modules/ROOT/pages/ref_macros.adoc +++ b/doc/modules/ROOT/pages/ref_macros.adoc @@ -14,6 +14,8 @@ uses of the library. | Adds an overrider to a method. | xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[*BOOST_OPENMETHOD_CLASSES*] | Registers classes. +| xref:reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc[*BOOST_OPENMETHOD_REGISTER_CLASSES*] +| Registers the classes of a namespace, by reflection. | xref:reference:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] | Adds an overrider to a method as an inline function. | xref:reference:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index e7e0002e..bfd16427 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -92,6 +92,15 @@ 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`. +The `explicit_class_registration` policy does the opposite of adding a +behaviour: it stops the library from registering classes by reflection, so a +registry that contains it knows only the classes named in +xref:reference:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] or +xref:reference:use_classes.adoc[use_classes]. It has no effect if the compiler +does not support C++26 reflection. See +xref:ROOT:basics.adoc#registering_classes_by_reflection[Registering Classes by +Reflection]. + The library provides another predefined registry: cpp:indirect_registry[]. It is useful when shared libraries are dynamically loaded at runtime, and add methods and overriders across program and shared library boundaries. See the section diff --git a/doc/modules/ROOT/snippets/CMakeLists.txt b/doc/modules/ROOT/snippets/CMakeLists.txt index 1b7b8c3e..de47749b 100644 --- a/doc/modules/ROOT/snippets/CMakeLists.txt +++ b/doc/modules/ROOT/snippets/CMakeLists.txt @@ -23,6 +23,7 @@ foreach (cpp ${cpp_files}) get_filename_component(stem ${cpp} NAME_WE) set(test_target "boost_openmethod-snippet_${stem}") add_executable(${test_target} ${cpp}) + boost_openmethod_enable_reflection(${test_target}) 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}) diff --git a/doc/modules/ROOT/snippets/errors_missing_base.cpp b/doc/modules/ROOT/snippets/errors_missing_base.cpp index fc557b5e..20288f4f 100644 --- a/doc/modules/ROOT/snippets/errors_missing_base.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_base.cpp @@ -3,6 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#include "explicit_registration.hpp" + #include #include diff --git a/doc/modules/ROOT/snippets/errors_missing_class_call.cpp b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp index 5f6a2bc1..9b5d42f3 100644 --- a/doc/modules/ROOT/snippets/errors_missing_class_call.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_class_call.cpp @@ -7,6 +7,8 @@ // which `default_registry` carries only when this symbol is defined. #define BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS +#include "explicit_registration.hpp" + #include #include diff --git a/doc/modules/ROOT/snippets/errors_missing_class_method.cpp b/doc/modules/ROOT/snippets/errors_missing_class_method.cpp index 4c4095c5..e47d28ba 100644 --- a/doc/modules/ROOT/snippets/errors_missing_class_method.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_class_method.cpp @@ -3,6 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#include "explicit_registration.hpp" + #include #include diff --git a/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp b/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp index 31ad6bbb..65652dfa 100644 --- a/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp +++ b/doc/modules/ROOT/snippets/errors_missing_class_overrider.cpp @@ -3,6 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#include "explicit_registration.hpp" + #include #include diff --git a/doc/modules/ROOT/snippets/explicit_registration.hpp b/doc/modules/ROOT/snippets/explicit_registration.hpp new file mode 100644 index 00000000..9dd08535 --- /dev/null +++ b/doc/modules/ROOT/snippets/explicit_registration.hpp @@ -0,0 +1,27 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Makes the default registry require explicit class registration, so that the +// error snippets keep reporting the error they illustrate when the compiler +// supports C++26 reflection. Include *before* . Like +// error_harness.hpp, never part of a tagged region: the pages show the mistake +// and the operation that reports it, and nothing else. +// +// The errors themselves do not go away in C++26 -- a class the library cannot +// reach from a method signature, an overrider, or a virtual_ptr still has to be +// registered by hand -- but these particular examples are all within its reach. + +#ifndef BOOST_OPENMETHOD_SNIPPETS_EXPLICIT_REGISTRATION_HPP +#define BOOST_OPENMETHOD_SNIPPETS_EXPLICIT_REGISTRATION_HPP + +#include + +struct snippet_registry : + boost::openmethod::default_registry::with< + boost::openmethod::policies::explicit_class_registration> {}; + +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY snippet_registry + +#endif diff --git a/doc/modules/ROOT/snippets/policies.cpp b/doc/modules/ROOT/snippets/policies.cpp index efbfde23..3a92eb4d 100644 --- a/doc/modules/ROOT/snippets/policies.cpp +++ b/doc/modules/ROOT/snippets/policies.cpp @@ -31,9 +31,10 @@ struct Dog : Animal {}; namespace std_rtti_demo { // tag::std_rtti[] -struct dynamic_registry : registry< - policies::std_rtti, policies::fast_perfect_hash, - policies::vptr_vector> {}; +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); @@ -54,9 +55,10 @@ 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> {}; +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); @@ -97,9 +99,10 @@ namespace fast_perfect_hash_demo { // 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> {}; +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); @@ -118,11 +121,10 @@ BOOST_OPENMETHOD_OVERRIDE( 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> {}; +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); @@ -140,11 +142,10 @@ BOOST_OPENMETHOD_OVERRIDE( 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> {}; +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); @@ -163,10 +164,10 @@ BOOST_OPENMETHOD_OVERRIDE( 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> {}; +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); diff --git a/doc/modules/ROOT/snippets/static_rtti.cpp b/doc/modules/ROOT/snippets/static_rtti.cpp index 31f6809c..6d447b6c 100644 --- a/doc/modules/ROOT/snippets/static_rtti.cpp +++ b/doc/modules/ROOT/snippets/static_rtti.cpp @@ -13,8 +13,8 @@ struct static_registry; #include #include -struct static_registry - : boost::openmethod::registry {}; +struct static_registry : + boost::openmethod::registry {}; // end::registry[] #include diff --git a/doc/modules/ROOT/snippets/type_erasure_concept.cpp b/doc/modules/ROOT/snippets/type_erasure_concept.cpp index aa886412..8052673d 100644 --- a/doc/modules/ROOT/snippets/type_erasure_concept.cpp +++ b/doc/modules/ROOT/snippets/type_erasure_concept.cpp @@ -29,9 +29,10 @@ struct Dog { // The concept must name the Concept it is part of, so the Concept is // defined as a struct. -struct Dispatchable : boost::mpl::vector< - te::copy_constructible<>, te::relaxed, - openmethod_vptr> {}; +struct Dispatchable : + boost::mpl::vector< + te::copy_constructible<>, te::relaxed, openmethod_vptr> { +}; using erased = te::any; diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 0ffdafc1..455e94c8 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -22,6 +22,7 @@ #include #include +#include #ifndef BOOST_OPENMETHOD_DEFAULT_REGISTRY //! Default value for `Registry`. @@ -210,8 +211,8 @@ struct requires_dynamic_cast_ref_aux : std::true_type {}; template struct requires_dynamic_cast_ref_aux< - B, D, std::void_t(std::declval()))>> - : std::false_type {}; + B, D, std::void_t(std::declval()))>> : + std::false_type {}; template constexpr bool requires_dynamic_cast = @@ -424,10 +425,10 @@ template struct use_class_aux; template -struct use_class_aux> - : std::conditional_t< - Registry::has_deferred_static_rtti, detail::deferred_class_info, - detail::class_info> { +struct use_class_aux> : + std::conditional_t< + Registry::has_deferred_static_rtti, detail::deferred_class_info, + detail::class_info> { static type_id bases[sizeof...(Bases)]; use_class_aux() { this->first_base = bases; @@ -500,6 +501,33 @@ class use_classes { detail::use_classes_tuple_type tuple; }; +// ----------------------------------------------------------------------------- +// reflection-based class registration + +namespace detail { + +#if BOOST_OPENMETHOD_HAS_REFLECTION + +// One registrar per entry, for the whole program - not per translation unit, as +// `BOOST_OPENMETHOD_CLASSES` produces. Same mechanism as +// `inplace_vptr_use_classes`: an `inline` variable template, instantiated by +// odr-use. Keyed on the entry rather than on the class, because a class' base +// list depends on what else was registered alongside it. +template +inline use_class_aux reflected_class_registrar; + +// Register every class the scan selected, each with its direct bases, as +// `reflected_registered_classes` computed them. +template +BOOST_FORCEINLINE auto use_reflected_classes(mp11::mp_list*) + -> void { + (..., (void)&reflected_class_registrar); +} + +#endif + +} // namespace detail + // ============================================================================= // virtual_ptr @@ -550,8 +578,8 @@ template struct is_smart_ptr_aux< Class, Registry, std::void_t< - typename virtual_traits::template rebind>> - : std::true_type {}; + typename virtual_traits::template rebind>> : + std::true_type {}; template struct same_smart_ptr_aux : std::false_type {}; @@ -560,11 +588,11 @@ template struct same_smart_ptr_aux< Class, Other, Registry, std::void_t::template rebind< - typename Other::element_type>>> - : std::is_same< - Other, - typename virtual_traits::template rebind< - typename Other::element_type>> {}; + typename Other::element_type>>> : + std::is_same< + Other, + typename virtual_traits::template rebind< + typename Other::element_type>> {}; } // namespace detail @@ -622,19 +650,19 @@ template struct is_virtual&> : std::true_type {}; template -struct is_virtual&> : std::true_type { -}; +struct is_virtual&> : + std::true_type {}; template struct is_virtual_ptr_aux : std::false_type {}; template -struct is_virtual_ptr_aux> : std::true_type { -}; +struct is_virtual_ptr_aux> : + std::true_type {}; template -struct is_virtual_ptr_aux&> - : std::true_type {}; +struct is_virtual_ptr_aux&> : + std::true_type {}; template constexpr bool is_virtual_ptr = detail::is_virtual_ptr_aux::value; @@ -660,9 +688,8 @@ decltype(auto) acquire_vptr(const ArgType& arg) { Registry::require_initialized(); - if constexpr (has_vptr< - virtual_traits, - const ArgType&>) { + if constexpr ( + has_vptr, const ArgType&>) { return virtual_traits::vptr(arg); } else { return Registry::template policy::dynamic_vptr(arg); @@ -872,9 +899,9 @@ class virtual_ptr { //! include:virtual_ptr.cpp#ctor_nullptr //! //! @param value A `nullptr`. - explicit virtual_ptr(std::nullptr_t) - : vp(detail::box_vptr(detail::null_vptr)), - obj(nullptr) { + explicit virtual_ptr(std::nullptr_t) : + vp(detail::box_vptr(detail::null_vptr)), + obj(nullptr) { } //! Construct a `virtual_ptr` from a reference to an object @@ -908,10 +935,10 @@ class virtual_ptr { BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) IsPolymorphic && std::is_constructible_v>> - virtual_ptr(Other& other) - : vp(detail::box_vptr( - detail::acquire_vptr(other))), - obj(&other) { + virtual_ptr(Other& other) : + vp(detail::box_vptr( + detail::acquire_vptr(other))), + obj(&other) { } //! Construct a `virtual_ptr` from a pointer to an object @@ -947,10 +974,10 @@ class virtual_ptr { BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) IsPolymorphic && std::is_constructible_v>> - virtual_ptr(Other* other) - : vp(detail::box_vptr( - detail::acquire_vptr(*other))), - obj(other) { + virtual_ptr(Other* other) : + vp(detail::box_vptr( + detail::acquire_vptr(*other))), + obj(other) { } //! Construct a `virtual_ptr` from another `virtual_ptr` @@ -981,8 +1008,8 @@ class virtual_ptr { class Other, typename = std::enable_if_t::element_type*>>> - virtual_ptr(const virtual_ptr& other) - : vp(other.vp), obj(other.get()) { + virtual_ptr(const virtual_ptr& other) : + vp(other.vp), obj(other.get()) { } //! Assign a `virtual_ptr` from a reference to an object @@ -1213,8 +1240,8 @@ class virtual_ptr< } template - virtual_ptr(Arg&& obj, decltype(vp) vp) - : vp(vp), obj(std::forward(obj)) { + virtual_ptr(Arg&& obj, decltype(vp) vp) : + vp(vp), obj(std::forward(obj)) { } public: @@ -1228,8 +1255,8 @@ class virtual_ptr< //! //! @par Example //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_default - virtual_ptr() - : vp(detail::box_vptr(detail::null_vptr)) { + virtual_ptr() : + vp(detail::box_vptr(detail::null_vptr)) { } //! Construct from `nullptr` @@ -1241,17 +1268,16 @@ class virtual_ptr< //! include:virtual_ptr.cpp#non_polymorphic_classes;shared_ctor_nullptr //! //! @param value A `nullptr`. - explicit virtual_ptr(std::nullptr_t) - : vp(detail::box_vptr(detail::null_vptr)) { + explicit virtual_ptr(std::nullptr_t) : + vp(detail::box_vptr(detail::null_vptr)) { } virtual_ptr(const virtual_ptr& other) = default; - virtual_ptr(virtual_ptr&& other) - : vp(std::exchange( - other.vp, - detail::box_vptr(detail::null_vptr))), - obj(std::move(other.obj)) { + virtual_ptr(virtual_ptr&& other) : + vp(std::exchange( + other.vp, detail::box_vptr(detail::null_vptr))), + obj(std::move(other.obj)) { } //! Construct from a (const) smart pointer to a derived class @@ -1284,11 +1310,11 @@ class virtual_ptr< std::is_constructible_v>, typename = std::enable_if_t>> - virtual_ptr(const Other& other) - : vp(detail::box_vptr( - other ? detail::acquire_vptr(*other) - : detail::null_vptr)), - obj(other) { + virtual_ptr(const Other& other) : + vp(detail::box_vptr( + other ? detail::acquire_vptr(*other) + : detail::null_vptr)), + obj(other) { } //! Construct from a smart pointer to a derived class @@ -1313,11 +1339,11 @@ class virtual_ptr< std::is_constructible_v>, typename = std::enable_if_t>> - virtual_ptr(Other& other) - : vp(detail::box_vptr( - other ? detail::acquire_vptr(*other) - : detail::null_vptr)), - obj(other) { + virtual_ptr(Other& other) : + vp(detail::box_vptr( + other ? detail::acquire_vptr(*other) + : detail::null_vptr)), + obj(other) { } //! Move-construct from a smart pointer to a derived class @@ -1349,11 +1375,11 @@ class virtual_ptr< std::is_constructible_v>, typename = std::enable_if_t>> - virtual_ptr(Other&& other) - : vp(detail::box_vptr( - other ? detail::acquire_vptr(*other) - : detail::null_vptr)), - obj(std::move(other)) { + virtual_ptr(Other&& other) : + vp(detail::box_vptr( + other ? detail::acquire_vptr(*other) + : detail::null_vptr)), + obj(std::move(other)) { } //! Construct from a smart virtual (const) pointer to a derived class @@ -1377,8 +1403,8 @@ class virtual_ptr< BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && std::is_constructible_v>> - virtual_ptr(const virtual_ptr& other) - : vp(other.vp), obj(other.obj) { + virtual_ptr(const virtual_ptr& other) : + vp(other.vp), obj(other.obj) { } //! Construct-move from a virtual pointer to a derived class @@ -1410,11 +1436,10 @@ class virtual_ptr< BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && std::is_constructible_v>> - virtual_ptr(virtual_ptr&& other) - : vp(std::exchange( - other.vp, - detail::box_vptr(detail::null_vptr))), - obj(std::move(other.obj)) { + virtual_ptr(virtual_ptr&& other) : + vp(std::exchange( + other.vp, detail::box_vptr(detail::null_vptr))), + obj(std::move(other.obj)) { } //! Assign from `nullptr` @@ -1766,8 +1791,8 @@ struct virtual_traits, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto - cast(const virtual_ptr& ptr) -> decltype(auto) { + static auto cast(const virtual_ptr& ptr) + -> decltype(auto) { return ptr.template cast(); } @@ -1812,8 +1837,8 @@ struct virtual_traits&, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto - cast(const virtual_ptr& ptr) -> decltype(auto) { + static auto cast(const virtual_ptr& ptr) + -> decltype(auto) { return ptr.template cast< typename std::remove_reference_t::element_type>(); } @@ -1913,12 +1938,12 @@ template struct parameter_traits, Registry> : virtual_traits {}; template -struct parameter_traits, Registry> - : virtual_traits, Registry> {}; +struct parameter_traits, Registry> : + virtual_traits, Registry> {}; template -struct parameter_traits&, Registry> - : virtual_traits&, Registry> {}; +struct parameter_traits&, Registry> : + virtual_traits&, Registry> {}; template constexpr bool false_t = false; // workaround before CWG2518/P2593R1 @@ -1934,10 +1959,10 @@ struct validate_method_parameter, Registry, U> : std::false_type { template struct validate_method_parameter< virtual_, Registry, - std::void_t::virtual_type>> - : std::bool_constant< - has_vptr_fn, Registry> || - Registry::rtti::template is_polymorphic>> { + std::void_t::virtual_type>> : + std::bool_constant< + has_vptr_fn, Registry> || + Registry::rtti::template is_polymorphic>> { static_assert( validate_method_parameter::value, "virtual_<> parameter is not a polymorphic class and no " @@ -1945,8 +1970,8 @@ struct validate_method_parameter< }; template -struct validate_method_parameter, Registry, void> - : std::true_type {}; +struct validate_method_parameter, Registry, void> : + std::true_type {}; template struct validate_method_parameter< @@ -2060,8 +2085,8 @@ class method; //! @tparam Registry The registry of the method template< typename Id, typename... Parameters, typename ReturnType, class Registry> -class method - : public detail::method_base { +class method : + public detail::method_base { // Deliberately no default for Inline: giving it a default on only one of // this template's two forward declarations in this file (the other is // below, next to override_impl) is accepted by gcc but rejected by clang @@ -2239,8 +2264,8 @@ class method template auto resolve_multi_first( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word; + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word; template< std::size_t VirtualArg, typename MethodArgList, typename ArgType, @@ -2267,25 +2292,25 @@ class method static BOOST_NORETURN auto fn_not_implemented( detail::remove_virtual_... args) -> ReturnType; - static BOOST_NORETURN auto - fn_ambiguous(detail::remove_virtual_... args) -> ReturnType; + static BOOST_NORETURN auto fn_ambiguous( + detail::remove_virtual_... args) -> ReturnType; template< auto Overrider, typename OverriderReturn, typename... OverriderParameters> struct thunk { - static auto - fn(detail::remove_virtual_... arg) -> ReturnType; + static auto fn(detail::remove_virtual_... arg) + -> ReturnType; using OverriderVirtualParameters = detail::overrider_virtual_types< DeclaredParameters, mp11::mp_list, Registry>; }; template - struct override_impl - : std::conditional_t< - Registry::has_deferred_static_rtti, - detail::deferred_overrider_info, detail::overrider_info> { + struct override_impl : + std::conditional_t< + Registry::has_deferred_static_rtti, detail::deferred_overrider_info, + detail::overrider_info> { explicit override_impl(FunctionPointer* next = nullptr); void resolve_type_ids(); @@ -2386,8 +2411,9 @@ method::operator()( using namespace detail; auto pf = resolve(args...); - return pf(std::forward::type>( - args)...); + return pf( + std::forward::type>( + args)...); } template< @@ -2426,9 +2452,9 @@ BOOST_FORCEINLINE auto method::vptr( if constexpr (detail::has_vptr_fn) { return boost_openmethod_vptr(obj, static_cast(nullptr)); - } else if constexpr (detail::has_vptr< - virtual_traits, - decltype(obj)>) { + } else if constexpr ( + detail::has_vptr< + virtual_traits, decltype(obj)>) { return virtual_traits::vptr(obj); } else { return Registry::template policy::dynamic_vptr(obj); @@ -2441,8 +2467,8 @@ template< template BOOST_FORCEINLINE auto method::resolve_uni( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word { using namespace detail; using namespace policies; @@ -2461,8 +2487,8 @@ template< template BOOST_FORCEINLINE auto method::resolve_multi_first( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word { using namespace detail; using namespace boost::mp11; @@ -2519,8 +2545,8 @@ method::resolve_multi_next( template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -inline auto -method::has_next() -> bool { +inline auto method::has_next() + -> bool { if (next == fn_not_implemented) { return false; } @@ -2598,8 +2624,8 @@ struct validate_overrider_parameter< template struct validate_overrider_parameter< - T1, T2, std::enable_if_t && !is_virtual_ptr>> - : std::false_type { + T1, T2, std::enable_if_t && !is_virtual_ptr>> : + std::false_type { static_assert( false_t, "virtual_ptr<> is required in overrider in same position as in " @@ -2613,14 +2639,14 @@ template struct validate_overrider_parameter, T2, void> : std::true_type {}; template -struct validate_overrider_parameter, virtual_, void> - : std::false_type { +struct validate_overrider_parameter, virtual_, void> : + std::false_type { static_assert(false_t, "virtual_<> is not allowed in overriders"); }; template -struct validate_overrider_parameter, virtual_ptr, void> - : std::true_type {}; +struct validate_overrider_parameter< + virtual_ptr, virtual_ptr, void> : std::true_type {}; template struct validate_overrider_parameter< @@ -2637,8 +2663,8 @@ struct validate_overrider_parameter< template struct validate_overrider_parameter< - const virtual_ptr&, const virtual_ptr&, void> - : std::true_type { + const virtual_ptr&, const virtual_ptr&, void> : + std::true_type { static_assert(std::is_same_v, "registry mismatch"); using C1 = virtual_type&, R>; using C2 = virtual_type&, R>; @@ -2758,6 +2784,463 @@ void method::override_impl< this->vp_type_ids); } +// ============================================================================= +// register_classes + +namespace detail { + +#if BOOST_OPENMETHOD_HAS_REFLECTION + +template +struct method_traits_aux; + +template< + typename Id, typename ReturnType, typename... Parameters, class Registry> +struct method_traits_aux> { + // The classes the method dispatches on, plus its return type, which is + // registered too when it is covariant. Same expression as + // `method::resolve_type_ids`. + using type = mp11::mp_push_back< + mp11::mp_transform_q< + mp11::mp_bind_back, + virtual_types>>, + virtual_type>; +}; + +// Read from reflection, by `substitute`-ing a method into it and taking the +// template arguments of the result. +template +using method_classes = typename method_traits_aux::type; + +// How `register_classes` interprets one of its template arguments. +enum class register_classes_arg : unsigned char { + invalid, + scope, // a scope_marker - the use site's enclosing scope + namespace_, // a namespace to scan + class_, // a class to register + options, // a register_classes_opts value + registry, // a registry to add the classes to +}; + +consteval auto is_registry_type(std::meta::info type) -> bool { + return std::meta::extract(std::meta::substitute( + ^^is_registry, + { + type})); +} + +template +consteval auto classify_register_classes_arg() -> register_classes_arg { + using Type = decltype(Arg); + + if constexpr (std::is_same_v) { + return register_classes_arg::scope; + } else if constexpr (std::is_same_v) { + return register_classes_arg::options; + } else if constexpr (std::is_same_v) { + auto entity = std::meta::dealias(Arg); + + if (std::meta::is_namespace(entity)) { + return register_classes_arg::namespace_; + } + + if (std::meta::is_class_type(entity)) { + // A registry is always complete where classes are registered in + // it, so an incomplete class cannot be one. + if (std::meta::is_complete_type(entity) && + is_registry_type(entity)) { + return register_classes_arg::registry; + } + + return register_classes_arg::class_; + } + + return register_classes_arg::invalid; + } else { + return register_classes_arg::invalid; + } +} + +template +consteval auto register_classes_args_are_valid() -> bool { + return ( + ... && + (classify_register_classes_arg() != + register_classes_arg::invalid)); +} + +// The groups must come in the order the `register_classes_arg` enumerators are +// declared in: the scope marker, namespaces, classes, options, registries. +template +consteval auto register_classes_args_are_ordered() -> bool { + register_classes_arg kinds[] = {classify_register_classes_arg()...}; + + for (auto index = 1u; index != sizeof...(Args); ++index) { + if (kinds[index] < kinds[index - 1]) { + return false; + } + } + + return true; +} + +template +consteval auto register_classes_option_count() -> unsigned { + return ( + 0u + ... + + (classify_register_classes_arg() == + register_classes_arg::options)); +} + +template +consteval auto register_classes_has_scan_source() -> bool { + return ( + ... || + (classify_register_classes_arg() <= + register_classes_arg::class_)); +} + +// `mp_list` if `Arg` is a reflection of a registry, `mp_list<>` +// otherwise. +template< + auto Arg, + bool = + classify_register_classes_arg() == register_classes_arg::registry> +struct register_classes_registry { + using type = mp11::mp_list<>; +}; + +template +struct register_classes_registry { + // clang-format off: the formatter predates P2996 and eats the spaces + // around the splice, leaving `typename[:...:]`. + using type = mp11::mp_list; + // clang-format on +}; + +template +using register_classes_registries = mp11::mp_append< + mp11::mp_list<>, typename register_classes_registry::type...>; + +// The classes to register for the arguments `Args`, each with its direct +// bases: the classes the methods of `Registry` dispatch on, the classes listed +// in `Args`, and the ones a scan of the listed namespaces found that derive +// from them. Returns `mp_list, ...>` - the +// shape `use_class_aux` expects, with the class repeated as its own improper +// base, as `inheritance_map` produces. +// +// The work is done here, in reflection, and not with `mp11` over the lists the +// scan produces. A scan of the global namespace reaches every class in the +// program that is not in `std` or `boost`, and instantiating a trait once per +// pair of them costs far more than walking their base classes does. +template +consteval auto reflected_registered_classes_info() -> std::meta::info { + // Partition the arguments. Registries take no part here: the caller calls + // this function once per registry. + std::vector namespaces; + std::vector virtual_classes; + auto opts = register_classes_opts::opts{}; + auto fallback_scope = std::meta::info(); + + (..., [&] { + constexpr auto kind = classify_register_classes_arg(); + + if constexpr (kind == register_classes_arg::scope) { + fallback_scope = Args.scope; + } else if constexpr (kind == register_classes_arg::namespace_) { + push_unique(namespaces, std::meta::dealias(Args)); + } else if constexpr (kind == register_classes_arg::class_) { + push_unique( + virtual_classes, + std::meta::remove_cv(std::meta::dealias(Args))); + } else if constexpr (kind == register_classes_arg::options) { + opts = Args; + } + }()); + + // With neither a namespace nor a class to start from, scan the namespace + // enclosing the use site, which the default template argument - or + // BOOST_OPENMETHOD_REGISTER_CLASSES - captured in the scope marker. If classes + // are listed but no namespace is, nothing is scanned: exactly the listed + // classes are registered. + if (namespaces.empty() && virtual_classes.empty()) { + auto scope = fallback_scope; + + while (!std::meta::is_namespace(scope)) { + scope = std::meta::parent_of(scope); + } + + namespaces.push_back(scope); + } + + std::vector methods, classes; + + for (auto ns : namespaces) { + scan_namespace(ns, ^^method, methods, classes, opts); + } + + // Add the classes the methods dispatch on. + + for (auto found : methods) { + // A method's third template argument is its registry. + if (std::meta::template_arguments_of(found)[2] != ^^Registry) { + continue; + } + + auto list = std::meta::dealias( + std::meta::substitute( + ^^method_classes, + { + found})); + + for (auto type : std::meta::template_arguments_of(list)) { + // A method's return type is `void` unless it is covariant, and a + // virtual parameter may be a smart pointer rather than a class. + if (std::meta::is_class_type(type)) { + push_unique(virtual_classes, std::meta::remove_cv(type)); + } + } + } + + // Those, plus every class the scan found that derives from one of them. A + // base class no method dispatches on is left out: no overrider could ever + // be selected on it, and it would cost a lattice node, a hash slot and + // dispatch table space. + auto registered = virtual_classes; + + for (auto found : classes) { + std::vector bases; + collect_reflected_bases(found, bases); + + for (auto base : bases) { + if (contains(virtual_classes, base)) { + push_unique(registered, found); + break; + } + } + } + + // Which registered class inherits from which, as a square matrix indexed + // by position in `registered`. Walking the base classes once per class and + // answering from the matrix afterwards keeps this within the compiler's + // budget for constant evaluation: the alternative, re-searching a class' + // bases for every pair, is cubic in the number of classes times the depth + // of the hierarchy, and exceeds GCC's default -fconstexpr-ops-limit on a + // chain of a few dozen. + auto count = registered.size(); + std::vector inherits(count * count, char(0)); + + for (auto index = 0u; index != count; ++index) { + std::vector bases; + collect_reflected_bases(registered[index], bases); + + for (auto base : bases) { + if (base == registered[index]) { + continue; + } + + for (auto other = 0u; other != count; ++other) { + if (registered[other] == base) { + inherits[index * count + other] = char(1); + break; + } + } + } + } + + std::vector entries; + + for (auto index = 0u; index != count; ++index) { + std::vector entry; + entry.push_back(registered[index]); + // The class as its own improper base, as `inheritance_map` does. + // `initialize` discards it, and `use_class_aux` cannot hold an empty + // base array. + entry.push_back(registered[index]); + + for (auto base = 0u; base != count; ++base) { + if (!inherits[index * count + base]) { + continue; + } + + // Keep only the nearest ancestors - the direct bases of this class + // in the lattice the registry will hold. One that another ancestor + // also inherits from is reached through that one, and recording it + // as well would make `initialize` see an edge that is not there. + // Unregistered classes in between are skipped over, which is what + // flattens the lattice down to the classes that dispatch. + bool hidden = false; + + for (auto between = 0u; between != count; ++between) { + if (between != base && inherits[index * count + between] && + inherits[between * count + base]) { + hidden = true; + break; + } + } + + if (!hidden) { + entry.push_back(registered[base]); + } + } + + entries.push_back(std::meta::substitute(^^mp11::mp_list, entry)); + } + + return std::meta::substitute(^^mp11::mp_list, entries); +} + +// `mp_list, ...>`, ready for `use_class_aux`. +// +// clang-format off: the formatter predates P2996 and eats the spaces around the +// splice, leaving `typename[:...:]`. +template +using reflected_registered_classes = + typename [: reflected_registered_classes_info() :]; +// clang-format on + +// Register the classes selected by `Args` in one registry - unless it opted +// out of reflection-based registration, in which case the scan does not even +// run. +template +BOOST_FORCEINLINE auto use_reflected_classes_in() -> void { + if constexpr (Registry::has_reflected_class_registration) { + using registered = reflected_registered_classes; + use_reflected_classes(static_cast(nullptr)); + } +} + +#endif + +} // namespace detail + +#if BOOST_OPENMETHOD_HAS_REFLECTION + +//! Get the current namespace. +//! +//! Returns a reflection of the namespace enclosing the point of the call. Use +//! it to pass the current namespace to @ref register_classes when the argument +//! list contains no namespace to scan, e.g. +//! `register_classes`. Do not pass an +//! argument: the default captures the caller's context. +//! +//! This function is available only if the compiler supports C++26 reflection, +//! i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. +consteval auto current_namespace( + std::meta::access_context ctx = std::meta::access_context::current()) + -> std::meta::info { + auto scope = ctx.scope(); + + while (!std::meta::is_namespace(scope)) { + scope = std::meta::parent_of(scope); + } + + return scope; +} + +//! Find the classes taking part in dispatch by reflection, and register them +//! +//! `register_classes` is a registrar class that finds the classes taking part in +//! dispatch by reflection, and adds them to one or more registries. It makes +//! @ref use_classes unnecessary in most cases. +//! +//! The arguments are non-type template arguments, in four groups, each +//! optional, in this order: +//! +//! @li **Namespaces** to scan, as reflections: `^^app`, `^^::`. +//! @li **Classes** to register, as reflections: `^^Animal`. They are +//! registered whether a method dispatches on them or not, along with the +//! classes the scan finds that derive from them. +//! @li **One @ref register_classes_opts value**, combining options with `|`. +//! @li **Registries** to register the classes in, as reflections: +//! `^^my_registry`. Each one receives the registration. The default is +//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY`. +//! +//! The scan covers the listed namespaces and, unless +//! @ref register_classes_opts::no_recurse is passed, the namespaces nested in them +//! - except `std` and `boost`, which are skipped unless +//! @ref register_classes_opts::scan_std or @ref register_classes_opts::scan_boost say +//! otherwise; a namespace *listed* explicitly is always scanned. The scan +//! finds the methods of each target registry, collects the classes they +//! dispatch on, and registers those, the listed classes, and every class in +//! the scanned namespaces that derives from one of them. A base class that no +//! method dispatches on, and that is not listed, is not registered: it could +//! never be selected on. +//! +//! If no namespace and no class is given, the scanned namespace is the one +//! enclosing the registrar. This works for `register_classes<>` and for every +//! form of @ref BOOST_OPENMETHOD_REGISTER_CLASSES; with any other bare +//! `register_classes` argument list, the enclosing namespace cannot be captured +//! - pass it explicitly with @ref current_namespace, e.g. +//! `register_classes`. +//! +//! If classes are listed but no namespace is, nothing is scanned: exactly the +//! listed classes are registered, with the inheritance relations between them +//! read from reflection. A base that is not listed is not registered. +//! +//! Reflection sees only what precedes it, so `register_classes` must come +//! **after** the declarations it is meant to find - at the bottom of the file. +//! +//! A method is found through any namespace member that names its `method` +//! specialization: the alias @ref BOOST_OPENMETHOD declares for it, a `using` +//! declaration written by hand, or any of its registrar objects - the object +//! @ref BOOST_OPENMETHOD_OVERRIDE creates, or one written by hand. None of +//! those requires the method to have an overrider. A core interface method +//! whose `method<...>` type is spelled out in full at every use, with neither a +//! `using` declaration nor an overrider, is named by nothing and is not found; +//! its classes must be registered with @ref use_classes. +//! +//! Virtual and multiple inheritance are supported. Unlike @ref use_classes, +//! which rejects it, repeated inheritance is not an error here: an ambiguous +//! base cannot take part in dispatch, so it is left out. +//! +//! This class template is available only if the compiler supports C++26 +//! reflection, i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. +//! +//! @tparam First,Rest Reflections of namespaces, classes and registries, and +//! at most one @ref register_classes_opts value, in that order. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) +template< + auto First = + detail::scope_marker{std::meta::access_context::current().scope()}, + auto... Rest> +class register_classes { + static_assert( + detail::register_classes_args_are_valid(), + "arguments must be reflections of namespaces, classes or registries, " + "or one register_classes_opts value"); + static_assert( + detail::register_classes_args_are_ordered(), + "order the arguments as namespaces, classes, options, registries"); + static_assert( + detail::register_classes_option_count() <= 1u, + "combine the options into a single register_classes_opts argument with " + "|"); + static_assert( + detail::register_classes_has_scan_source(), + "the enclosing namespace cannot be captured here; pass a namespace, " + "or current_namespace(), or use BOOST_OPENMETHOD_REGISTER_CLASSES"); + + using found_registries = + detail::register_classes_registries; + using registries = mp11::mp_if< + mp11::mp_empty, + mp11::mp_list, found_registries>; + + template + static auto use(mp11::mp_list*) -> void { + (..., detail::use_reflected_classes_in()); + } + + public: + register_classes() { + use(static_cast(nullptr)); + } +}; + +#endif + //! Aliases for the most frequently used types in the library. namespace aliases { diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index ca8926ec..0c991fcc 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -47,16 +47,15 @@ namespace boost::openmethod { //! // exactly one .cpp of the owning module: //! BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(boost::openmethod::default_registry); //! @endcode -struct default_registry - : registry< - policies::std_rtti, policies::fast_perfect_hash, - policies::vptr_vector, policies::default_error_handler, - policies::stderr_output +struct default_registry : + registry< + policies::std_rtti, policies::fast_perfect_hash, policies::vptr_vector, + policies::default_error_handler, policies::stderr_output #ifdef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS - , - policies::runtime_checks + , + policies::runtime_checks #endif - > { + > { }; namespace detail { diff --git a/include/boost/openmethod/detail/ostdstream.hpp b/include/boost/openmethod/detail/ostdstream.hpp index c63b381e..79c2ad1b 100644 --- a/include/boost/openmethod/detail/ostdstream.hpp +++ b/include/boost/openmethod/detail/ostdstream.hpp @@ -51,8 +51,8 @@ inline auto operator<<(ostdstream& os, const char* str) -> ostdstream& { return os; } -inline auto -operator<<(ostdstream& os, const std::string_view& view) -> ostdstream& { +inline auto operator<<(ostdstream& os, const std::string_view& view) + -> ostdstream& { if (os.stream) { fwrite(view.data(), sizeof(*view.data()), view.length(), os.stream); } diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp new file mode 100644 index 00000000..d745b32e --- /dev/null +++ b/include/boost/openmethod/detail/reflection.hpp @@ -0,0 +1,262 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_DETAIL_REFLECTION_HPP +#define BOOST_OPENMETHOD_DETAIL_REFLECTION_HPP + +// Detect C++26 reflection (P2996). Both the language feature (the `^^` +// operator) and the library (`std::meta`) are required. +#if !defined(BOOST_OPENMETHOD_HAS_REFLECTION) +#if defined(__cpp_impl_reflection) && __has_include() +#include +#if defined(__cpp_lib_reflection) +#define BOOST_OPENMETHOD_HAS_REFLECTION 1 +#endif +#endif +#endif + +#if !defined(BOOST_OPENMETHOD_HAS_REFLECTION) +#define BOOST_OPENMETHOD_HAS_REFLECTION 0 +#endif + +#if BOOST_OPENMETHOD_HAS_REFLECTION + +#include + +#include + +namespace boost::openmethod { + +//! Options for reflection-based class registration. +//! +//! The values alter how @ref register_classes and @ref +//! BOOST_OPENMETHOD_REGISTER_CLASSES scan namespaces. Combine them with `|`. +//! A namespace rather than an enum class, so a `using namespace` directive can +//! make the terse spellings available. +//! +//! This namespace is available only if the compiler supports C++26 reflection, +//! i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. +namespace register_classes_opts { + +//! The type of the option values. +enum opts : unsigned { + //! Scan only the members declared directly in the listed namespaces; do + //! not descend into the namespaces nested in them. + no_recurse = 1, + //! Enter the `std` namespace when a scan reaches it. By default it is + //! skipped. + scan_std = 2, + //! Enter the `boost` namespace when a scan reaches it. By default it is + //! skipped. + scan_boost = 4, +}; + +//! Combine two sets of options. +constexpr auto operator|(opts a, opts b) -> opts { + return opts(unsigned(a) | unsigned(b)); +} + +} // namespace register_classes_opts + +} // namespace boost::openmethod + +namespace boost::openmethod::detail { + +consteval auto has_opt( + register_classes_opts::opts opts, register_classes_opts::opts opt) -> bool { + return (unsigned(opts) & unsigned(opt)) != 0; +} + +// The scope enclosing a use of `BOOST_OPENMETHOD_REGISTER_CLASSES`, or the +// instantiation of `register_classes<>` with an empty argument list. It is a +// fallback: a scan uses it only when its argument list names no namespace and +// no class. +struct scope_marker { + std::meta::info scope; +}; + +// ============================================================================= +// base classes + +// Append `type` and all the base classes transitively reachable from it to +// `types`, skipping the ones already present. Only public base specifiers are +// followed: a class reached solely through a private or protected base cannot +// take part in dispatch, because the conversion is not available to the +// library. +consteval void collect_reflected_bases( + std::meta::info type, std::vector& types) { + for (auto seen : types) { + if (seen == type) { + return; + } + } + + types.push_back(type); + + // The range is held in a named local instead of being left to the + // range-for to lifetime-extend, to work around GCC PR124645/PR124646. + // r16-8235 marks a lifetime-extended temporary of consteval-only type - + // which `vector` is - `DECL_EXTERNAL` unconditionally, and the + // constant evaluator then hands every frame of a recursive call the same + // object: the inner call destroys the vector the outer call is still + // walking, and the loop fails with "accessing '' outside its + // lifetime". A plain automatic variable is not an extended-ref temporary, + // so each frame gets its own. `scan_namespace` below recurses too, and + // does the same. + // + // Fixed upstream in r16-8430. Drop this once no supported toolchain sits + // in between - Ubuntu 26.04, which Boost.CI uses for the C++26 leg, ships + // 16-20260322 (r16-8246) and does. + auto bases = + std::meta::bases_of(type, std::meta::access_context::unchecked()); + + for (auto base : bases) { + if (std::meta::is_public(base)) { + collect_reflected_bases(std::meta::type_of(base), types); + } + } +} + +template +consteval auto reflected_bases_info() -> std::meta::info { + std::vector types; + collect_reflected_bases(std::meta::dealias(^^Class), types); + + return std::meta::substitute(^^mp11::mp_list, types); +} + +// `mp11::mp_list`, where `Bases` are all the base classes +// transitively reachable from `Class` through public inheritance, in +// unspecified order. `Class` itself is the first element. +template +// clang-format off: the formatter predates P2996 and eats the spaces around +// the splice, leaving `typename[:...:]`. +using reflected_bases = typename [: reflected_bases_info() :]; +// clang-format on + +// ============================================================================= +// namespace scan + +// True if `member` is a namespace that a recursive scan does not enter: `std` +// and `boost`, unless the options say otherwise. Walking them would cost a +// great deal and find nothing: a method cannot be declared on a class the +// program has never heard of. The nested namespaces - `std::chrono`, +// `boost::mp11`, the inline versioning ones - are reached only through their +// parent, so they are left out with it. The exclusion applies only to +// recursion: a namespace listed explicitly is always scanned. +consteval auto is_excluded_namespace( + std::meta::info member, register_classes_opts::opts opts) -> bool { + auto ns = std::meta::dealias(member); + + if (ns == ^^::std) { + return !has_opt(opts, register_classes_opts::scan_std); + } + + if (ns == ^^::boost) { + return !has_opt(opts, register_classes_opts::scan_boost); + } + + return false; +} + +consteval auto contains( + const std::vector& types, std::meta::info type) -> bool { + for (auto seen : types) { + if (seen == type) { + return true; + } + } + + return false; +} + +consteval void push_unique( + std::vector& types, std::meta::info type) { + if (!contains(types, type)) { + types.push_back(type); + } +} + +// The class template specialization that `member` names: `member` itself, if it +// is a type - or an alias for one - that is a specialization; or the class that +// encloses `member`'s type, if `member` is a variable of a nested type. This is +// how a `method` is found: the core interface names it in an alias, and a +// registrar - the one `BOOST_OPENMETHOD_OVERRIDE` creates, or one written by +// hand - is a variable of type `method<...>::override<...>`. Returns an invalid +// reflection if `member` names no specialization. +consteval auto specialization_named_by(std::meta::info member) + -> std::meta::info { + if (std::meta::is_type(member)) { + auto type = std::meta::dealias(member); + + if (std::meta::has_template_arguments(type)) { + return type; + } + + return std::meta::info(); + } + + if (std::meta::is_variable(member)) { + auto enclosing = std::meta::type_of(member); + + if (std::meta::has_parent(enclosing)) { + auto parent = std::meta::parent_of(enclosing); + + if (std::meta::is_type(parent) && + std::meta::has_template_arguments(parent)) { + return parent; + } + } + } + + return std::meta::info(); +} + +// Walk `ns` and, unless `opts` says `no_recurse`, the namespaces nested in it, +// collecting the specializations of `Template` that its members name, and the +// complete class types they declare. Nothing else is retained: the scan of a +// large namespace must not build a list of everything in it. +consteval void scan_namespace( + std::meta::info ns, std::meta::info Template, + std::vector& specializations, + std::vector& classes, register_classes_opts::opts opts) { + // A named local, for the reason given in `collect_reflected_bases`. + auto members = + std::meta::members_of(ns, std::meta::access_context::unchecked()); + + for (auto member : members) { + if (std::meta::is_namespace(member)) { + if (!has_opt(opts, register_classes_opts::no_recurse) && + !is_excluded_namespace(member, opts)) { + scan_namespace( + member, Template, specializations, classes, opts); + } + + continue; + } + + auto specialization = specialization_named_by(member); + + if (specialization != std::meta::info() && + std::meta::template_of(specialization) == Template) { + push_unique(specializations, specialization); + } + + if (std::meta::is_type(member)) { + auto type = std::meta::dealias(member); + + if (std::meta::is_class_type(type) && + std::meta::is_complete_type(type)) { + push_unique(classes, type); + } + } + } +} + +} // namespace boost::openmethod::detail + +#endif + +#endif diff --git a/include/boost/openmethod/detail/static_list.hpp b/include/boost/openmethod/detail/static_list.hpp index d135cab2..27c86407 100644 --- a/include/boost/openmethod/detail/static_list.hpp +++ b/include/boost/openmethod/detail/static_list.hpp @@ -177,13 +177,13 @@ class static_list { return tmp; } - friend auto - operator==(const const_iterator& a, const const_iterator& b) -> bool { + friend auto operator==(const const_iterator& a, const const_iterator& b) + -> bool { return a.ptr == b.ptr; } - friend auto - operator!=(const const_iterator& a, const const_iterator& b) -> bool { + friend auto operator!=(const const_iterator& a, const const_iterator& b) + -> bool { return a.ptr != b.ptr; } diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 13756852..869a3ab9 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -158,6 +158,9 @@ struct generic_compiler { struct class_ { std::vector ci; + // What the records declared, before the closure is computed; emptied by + // calculate_transitive_bases. + std::vector declared_bases; std::vector transitive_bases; std::vector direct_bases; std::vector direct_derived; @@ -167,6 +170,7 @@ struct generic_compiler { boost::dynamic_bitset<> reserved_slots; std::size_t first_slot = 0; std::size_t mark = 0; // temporary mark to detect cycles + bool transitive_bases_done = false; std::vector vtbl; auto is_base_of(class_* other) const -> bool { @@ -258,8 +262,8 @@ struct generic_compiler { const_class_iterator( std::deque::const_iterator class_iter, - std::deque::const_iterator class_end) - : class_iter_(class_iter), class_end_(class_end) { + std::deque::const_iterator class_end) : + class_iter_(class_iter), class_end_(class_end) { if (class_iter_ != class_end_) { ci_iter_ = class_iter_->ci.begin(); advance_to_valid(); @@ -348,8 +352,8 @@ struct trace_stream { trace_stream& trace; int by; - explicit indent(trace_stream& trace, int by = 2) - : trace(trace), by(by) { + explicit indent(trace_stream& trace, int by = 2) : + trace(trace), by(by) { trace.indentation_level += by; } @@ -362,8 +366,8 @@ struct trace_stream { struct rflush { std::size_t width; std::size_t value; - explicit rflush(std::size_t width, std::size_t value) - : width(width), value(value) { + explicit rflush(std::size_t width, std::size_t value) : + width(width), value(value) { } }; @@ -405,8 +409,8 @@ auto operator<<( struct spec_name { spec_name( const detail::generic_compiler::method& method, - const detail::generic_compiler::overrider* def) - : method(method), def(def) { + const detail::generic_compiler::overrider* def) : + method(method), def(def) { } const detail::generic_compiler::method& method; const detail::generic_compiler::overrider* def; @@ -551,7 +555,7 @@ struct registry::compiler : detail::generic_compiler { void install_global_tables(); void augment_classes(); - void collect_transitive_bases(class_* cls, class_* base); + void calculate_transitive_bases(class_& cls); void calculate_transitive_derived(class_& cls); void augment_methods(); void assign_slots(); @@ -568,8 +572,8 @@ struct registry::compiler : detail::generic_compiler { static void select_dominant_overriders( std::vector& dominants, std::size_t& pick, std::size_t& remaining); - static auto - is_more_specific(const overrider* a, const overrider* b) -> bool; + static auto is_more_specific(const overrider* a, const overrider* b) + -> bool; static auto is_base(const overrider* a, const overrider* b) -> bool; std::tuple options; @@ -652,8 +656,8 @@ struct msvc_tuple_get { template template -registry::compiler::compiler(Options... opts) - : options(opts...) { +registry::compiler::compiler(Options... opts) : + options(opts...) { if constexpr (has_trace) { #ifdef _MSC_VER tr.on = detail::msvc_tuple_get::fn(options).on; @@ -666,18 +670,41 @@ registry::compiler::compiler(Options... opts) template template -void registry::compiler::collect_transitive_bases( - class_* cls, class_* base) { - if (base->mark == class_mark) { +void registry::compiler::calculate_transitive_bases( + class_& cls) { + if (cls.transitive_bases_done) { return; } - cls->transitive_bases.push_back(base); - base->mark = class_mark; + // Set before recursing. Inheritance cannot cycle, so this only guards + // against a malformed set of records. + cls.transitive_bases_done = true; + + // Complete every declared base first, so that merging them below sees + // their full closure. Recursing bumps `class_mark`, hence the fresh mark + // afterwards. + for (auto base : cls.declared_bases) { + calculate_transitive_bases(*base); + } + + auto mark = ++class_mark; - for (auto base_base : base->transitive_bases) { - collect_transitive_bases(cls, base_base); + for (auto base : cls.declared_bases) { + if (base->mark != mark) { + base->mark = mark; + cls.transitive_bases.push_back(base); + } + + for (auto base_base : base->transitive_bases) { + if (base_base->mark != mark) { + base_base->mark = mark; + cls.transitive_bases.push_back(base_base); + } + } } + + cls.declared_bases.clear(); + cls.declared_bases.shrink_to_fit(); } template @@ -760,29 +787,21 @@ void registry::compiler::augment_classes() { if (rtc != rtb) { // At compile time we collected the class as its own // improper base, as per std::is_base_of. Eliminate that. - ++class_mark; - collect_transitive_bases(rtc, rtb); + rtc->declared_bases.push_back(rtb); } } } - // At this point bases may contain duplicates, and also indirect - // bases. Clean that up. - - std::size_t mark = ++class_mark; + // `declared_bases` now holds whatever the records said - direct bases, + // ancestors, or a mixture, with duplicates. Turn that into the transitive + // closure. This is done here rather than while reading the records because + // a record's bases are only as informative as the records already seen: a + // class registered before its own bases would otherwise be left with a + // short list. That matters beyond `transitive_bases` itself, because the + // direct-base derivation below sorts on its size. for (auto& rtc : classes) { - decltype(rtc.transitive_bases) bases; - mark = ++class_mark; - - for (auto rtb : rtc.transitive_bases) { - if (rtb->mark != mark) { - bases.push_back(rtb); - rtb->mark = mark; - } - } - - rtc.transitive_bases.swap(bases); + calculate_transitive_bases(rtc); } for (auto& rtc : classes) { @@ -793,7 +812,7 @@ void registry::compiler::augment_classes() { [](auto a, auto b) { return a->transitive_bases.size() > b->transitive_bases.size(); }); - mark = ++class_mark; + auto mark = ++class_mark; // Collect the direct base classes. The first base is certainly a // direct one. Remove *its* bases from the candidates, by marking @@ -1757,10 +1776,9 @@ void registry::compiler::select_dominant_overriders( if (candidates[i]->covariant_return_type->is_base_of( candidates[j]->covariant_return_type)) { candidates[i] = nullptr; - } else if (candidates[j] - ->covariant_return_type->is_base_of( - candidates[i] - ->covariant_return_type)) { + } else if ( + candidates[j]->covariant_return_type->is_base_of( + candidates[i]->covariant_return_type)) { candidates[j] = nullptr; } } diff --git a/include/boost/openmethod/inplace_vptr.hpp b/include/boost/openmethod/inplace_vptr.hpp index cf1328b0..44867b5d 100644 --- a/include/boost/openmethod/inplace_vptr.hpp +++ b/include/boost/openmethod/inplace_vptr.hpp @@ -105,8 +105,8 @@ class inplace_vptr_base : protected detail::inplace_vptr_base_tag { std::conditional_t boost_openmethod_vptr = nullptr; - friend auto - boost_openmethod_vptr(const Class& obj, Registry*) noexcept -> vptr_type { + friend auto boost_openmethod_vptr(const Class& obj, Registry*) noexcept + -> vptr_type { if constexpr (Registry::has_indirect_vptr) { return *obj.boost_openmethod_vptr; } else { @@ -216,13 +216,13 @@ class inplace_vptr_derived { (!detail::is_registry && ...), "registry can be specified only for root classes"); - friend auto - boost_openmethod_registry(Class*) -> detail::inplace_vptr_registry; - friend auto - boost_openmethod_bases(Class*) -> mp11::mp_list; + friend auto boost_openmethod_registry(Class*) + -> detail::inplace_vptr_registry; + friend auto boost_openmethod_bases(Class*) + -> mp11::mp_list; friend auto boost_openmethod_vptr( - const Class& obj, - detail::inplace_vptr_registry* registry) -> vptr_type { + const Class& obj, detail::inplace_vptr_registry* registry) + -> vptr_type { return boost_openmethod_vptr(static_cast(obj), registry); } diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index d955e47f..e8f14306 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -21,16 +21,16 @@ namespace boost::openmethod { namespace detail { template -struct validate_method_parameter, Registry, void> - : std::true_type {}; +struct validate_method_parameter, Registry, void> : + std::true_type {}; template -struct validate_method_parameter, Registry, void> - : std::true_type {}; +struct validate_method_parameter, Registry, void> : + std::true_type {}; template -struct validate_method_parameter, Registry, void> - : std::true_type {}; +struct validate_method_parameter, Registry, void> : + std::true_type {}; // `boost::any::type()` yields a `std::type_info`, which is a valid `type_id` // only for an rtti policy that identifies classes by `&typeid(T)`. Under any @@ -119,9 +119,9 @@ struct virtual_traits { !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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, boost::any>) { return (arg); } else { (void)&detail::use_any_classes< @@ -202,9 +202,9 @@ struct virtual_traits { 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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, boost::any>) { return (arg); } else { (void)&detail::use_any_classes< @@ -285,9 +285,9 @@ struct virtual_traits { !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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, boost::any>) { return std::move(arg); } else { (void)&detail::use_any_classes< diff --git a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp index ec23346b..1cb36db2 100644 --- a/include/boost/openmethod/interop/boost_intrusive_ptr.hpp +++ b/include/boost/openmethod/interop/boost_intrusive_ptr.hpp @@ -102,15 +102,15 @@ struct virtual_traits&, Registry> { //! @return A `boost::intrusive_ptr` _value_. template static decltype(auto) cast(const boost::intrusive_ptr& obj) { - if constexpr (std::is_same_v< - OverriderType, const boost::intrusive_ptr&>) { + if constexpr ( + std::is_same_v&>) { return obj; } else { using element_type = typename std::remove_reference_t::element_type; - if constexpr (detail::requires_dynamic_cast< - Class*, element_type*>) { + if constexpr ( + detail::requires_dynamic_cast) { // make it work with custom RTTI return std::remove_const_t< std::remove_reference_t>( diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index 0a4beabf..de09075d 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -96,28 +96,28 @@ constexpr bool te_pass_through = template struct validate_method_parameter< - virtual_&>, Registry, void> - : std::true_type {}; + virtual_&>, Registry, void> : + std::true_type {}; template struct validate_method_parameter< - virtual_&>, Registry, void> - : std::true_type {}; + virtual_&>, Registry, void> : + std::true_type {}; template struct validate_method_parameter< - virtual_&&>, Registry, void> - : std::true_type {}; + virtual_&&>, Registry, void> : + std::true_type {}; template struct validate_method_parameter< - virtual_>, Registry, void> - : std::true_type {}; + virtual_>, Registry, void> : + std::true_type {}; template struct validate_method_parameter< - virtual_>, Registry, void> - : std::true_type {}; + virtual_>, Registry, void> : + std::true_type {}; template struct validate_method_parameter< @@ -182,8 +182,8 @@ struct virtual_traits&, Registry> { //! //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the bound value. - static auto - vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + static auto vptr(const boost::type_erasure::any& arg) + -> const vptr_type& { detail::assert_std_rtti_type_erasure(); (void)&detail::use_any_classes>; return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); @@ -208,10 +208,10 @@ struct virtual_traits&, Registry> { typename = std::enable_if_t< !std::is_rvalue_reference_v && (!detail::te_mutable_target || detail::te_mutable_bound)>> - static auto - cast(const boost::type_erasure::any& arg) -> decltype(auto) { - if constexpr (detail::te_pass_through< - U, boost::type_erasure::any>) { + static auto cast(const boost::type_erasure::any& arg) + -> decltype(auto) { + if constexpr ( + detail::te_pass_through>) { return (arg); } else { (void)&detail::use_any_classes< @@ -257,8 +257,8 @@ struct virtual_traits&, Registry> { //! //! @param arg A reference to an `any`. //! @return A reference to the v-table pointer for the bound value. - static auto - vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + static auto vptr(const boost::type_erasure::any& arg) + -> const vptr_type& { detail::assert_std_rtti_type_erasure(); (void)&detail::use_any_classes>; return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); @@ -285,8 +285,8 @@ struct virtual_traits&, Registry> { (!detail::te_mutable_target || detail::te_owning || detail::te_mutable_bound)>> static auto cast(boost::type_erasure::any& arg) -> decltype(auto) { - if constexpr (detail::te_pass_through< - U, boost::type_erasure::any>) { + if constexpr ( + detail::te_pass_through>) { return (arg); } else { (void)&detail::use_any_classes< @@ -332,8 +332,8 @@ struct virtual_traits&&, Registry> { //! //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the bound value. - static auto - vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + static auto vptr(const boost::type_erasure::any& arg) + -> const vptr_type& { detail::assert_std_rtti_type_erasure(); (void)&detail::use_any_classes>; return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); @@ -361,8 +361,8 @@ struct virtual_traits&&, Registry> { (!detail::te_mutable_target || detail::te_owning || detail::te_mutable_bound)>> static auto cast(boost::type_erasure::any&& arg) -> decltype(auto) { - if constexpr (detail::te_pass_through< - U, boost::type_erasure::any>) { + if constexpr ( + detail::te_pass_through>) { return std::move(arg); } else { (void)&detail::use_any_classes< @@ -421,8 +421,8 @@ struct virtual_traits, Registry> { //! //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the bound value. - static auto - vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + static auto vptr(const boost::type_erasure::any& arg) + -> const vptr_type& { detail::assert_std_rtti_type_erasure(); (void)&detail::use_any_classes>; return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); @@ -444,8 +444,8 @@ struct virtual_traits, Registry> { template< typename U, typename = std::enable_if_t>> static auto cast(boost::type_erasure::any arg) -> decltype(auto) { - if constexpr (detail::te_pass_through< - U, boost::type_erasure::any>) { + if constexpr ( + detail::te_pass_through>) { // by value: a reference would dangle when this function's // parameter goes out of scope return arg; @@ -497,8 +497,8 @@ struct virtual_traits, Registry> { //! //! @param arg A reference to a const `any`. //! @return A reference to the v-table pointer for the bound value. - static auto - vptr(const boost::type_erasure::any& arg) -> const vptr_type& { + static auto vptr(const boost::type_erasure::any& arg) + -> const vptr_type& { detail::assert_std_rtti_type_erasure(); (void)&detail::use_any_classes>; return Registry::vptr::vptr(&boost::type_erasure::typeid_of(arg)); @@ -519,10 +519,10 @@ struct virtual_traits, Registry> { typename U, typename = std::enable_if_t< !std::is_rvalue_reference_v && !detail::te_mutable_target>> - static auto - cast(boost::type_erasure::any arg) -> decltype(auto) { - if constexpr (detail::te_pass_through< - U, boost::type_erasure::any>) { + static auto cast(boost::type_erasure::any arg) + -> decltype(auto) { + if constexpr ( + detail::te_pass_through>) { // by value: a reference would dangle when this function's // parameter goes out of scope return arg; diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index c02bc7be..f8bdd78a 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -21,16 +21,16 @@ namespace boost::openmethod { namespace detail { template -struct validate_method_parameter, Registry, void> - : std::true_type {}; +struct validate_method_parameter, Registry, void> : + std::true_type {}; template -struct validate_method_parameter, Registry, void> - : std::true_type {}; +struct validate_method_parameter, Registry, void> : + std::true_type {}; template -struct validate_method_parameter, Registry, void> - : std::true_type {}; +struct validate_method_parameter, Registry, void> : + std::true_type {}; // `std::any::type()` yields a `std::type_info`, which is a valid `type_id` // only for an rtti policy that identifies classes by `&typeid(T)`. Under any @@ -109,9 +109,9 @@ struct virtual_traits { //! @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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, std::any>) { return (arg); } else { (void)&detail::use_any_classes>; @@ -182,9 +182,9 @@ struct virtual_traits { //! @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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, std::any>) { return (arg); } else { (void)&detail::use_any_classes>; @@ -252,9 +252,9 @@ struct virtual_traits { //! @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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, std::any>) { return std::move(arg); } else { (void)&detail::use_any_classes>; diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index 4ea7a531..49f69a84 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -33,8 +33,8 @@ struct shared_ptr_cast_traits&&> { }; template -struct validate_method_parameter&, Registry, void> - : std::false_type { +struct validate_method_parameter&, Registry, void> : + std::false_type { static_assert( false_t, "std::shared_ptr cannot be passed by non-const lvalue reference"); @@ -42,8 +42,8 @@ struct validate_method_parameter&, Registry, void> template struct validate_method_parameter< - virtual_ptr, Registry>&, Registry, void> - : std::false_type { + virtual_ptr, Registry>&, Registry, void> : + std::false_type { static_assert( false_t, "std::shared_ptr cannot be passed by non-const lvalue reference"); @@ -91,8 +91,8 @@ struct virtual_traits, Registry> { static auto cast(const std::shared_ptr& obj) -> decltype(auto) { using namespace boost::openmethod::detail; - if constexpr (requires_dynamic_cast< - Class*, typename Derived::element_type*>) { + if constexpr ( + requires_dynamic_cast) { return std::dynamic_pointer_cast< typename shared_ptr_cast_traits::virtual_type>(obj); } else { @@ -120,8 +120,9 @@ struct virtual_traits, Registry> { static auto cast(std::shared_ptr&& obj) -> decltype(auto) { using namespace boost::openmethod::detail; - if constexpr (requires_dynamic_cast< - Class*, decltype(std::declval().get())>) { + if constexpr ( + requires_dynamic_cast< + Class*, decltype(std::declval().get())>) { return std::dynamic_pointer_cast< typename shared_ptr_cast_traits::virtual_type>( std::move(obj)); diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 4053c997..af05d0a6 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -135,8 +135,8 @@ class virtual_any { //! 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)) { + virtual_any() : + obj(), vp(detail::box_vptr(detail::null_vptr)) { } //! Construct from an `any` (copy). @@ -148,9 +148,9 @@ class virtual_any { //! //! @par Example //! include:virtual_any.cpp#from_any - virtual_any(const Any& other) - : obj(other), vp(detail::box_vptr( - detail::acquire_vptr(obj))) { + virtual_any(const Any& other) : + obj(other), vp(detail::box_vptr( + detail::acquire_vptr(obj))) { } //! Construct from an `any` (move). @@ -159,9 +159,9 @@ class virtual_any { //! 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))) { + virtual_any(Any&& other) : + obj(std::move(other)), vp(detail::box_vptr( + detail::acquire_vptr(obj))) { } //! Construct from a value. @@ -185,10 +185,10 @@ class virtual_any { typename = std::enable_if_t< !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>)) { + virtual_any(T&& value) : + obj(std::forward(value)), + vp(detail::box_vptr( + Registry::template static_vptr>)) { (void)&detail::use_any_classes>; Registry::require_initialized(); BOOST_ASSERT(detail::unbox_vptr(vp) != nullptr); @@ -336,8 +336,8 @@ struct virtual_traits&, Registry> { //! //! @param arg A reference to a const `virtual_any`. //! @return A reference to the v-table pointer for the stored value. - static auto - vptr(const virtual_any& arg) -> const vptr_type& { + static auto vptr(const virtual_any& arg) + -> const vptr_type& { (void)&detail::use_any_classes; return arg.vptr_ref(); } @@ -356,9 +356,10 @@ struct virtual_traits&, Registry> { //! @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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, + virtual_any>) { return (arg); } else { (void)&detail::use_any_classes>; @@ -395,8 +396,8 @@ struct virtual_traits&, Registry> { //! //! @param arg A reference to a `virtual_any`. //! @return A reference to the v-table pointer for the stored value. - static auto - vptr(const virtual_any& arg) -> const vptr_type& { + static auto vptr(const virtual_any& arg) + -> const vptr_type& { (void)&detail::use_any_classes; return arg.vptr_ref(); } @@ -417,9 +418,10 @@ struct virtual_traits&, Registry> { //! @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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, + virtual_any>) { return (arg); } else { (void)&detail::use_any_classes>; @@ -455,8 +457,8 @@ struct virtual_traits&&, Registry> { //! //! @param arg A reference to a `virtual_any`. //! @return A reference to the v-table pointer for the stored value. - static auto - vptr(const virtual_any& arg) -> const vptr_type& { + static auto vptr(const virtual_any& arg) + -> const vptr_type& { (void)&detail::use_any_classes; return arg.vptr_ref(); } @@ -475,9 +477,10 @@ struct virtual_traits&&, Registry> { //! @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>) { + if constexpr ( + std::is_same_v< + std::remove_cv_t>, + virtual_any>) { return std::move(arg); } else { (void)&detail::use_any_classes>; @@ -523,16 +526,16 @@ template struct is_virtual&&> : std::true_type {}; template -struct parameter_traits&, Registry> - : virtual_traits&, Registry> {}; +struct parameter_traits&, Registry> : + virtual_traits&, Registry> {}; template -struct parameter_traits&, Registry> - : virtual_traits&, Registry> {}; +struct parameter_traits&, Registry> : + virtual_traits&, Registry> {}; template -struct parameter_traits&&, Registry> - : virtual_traits&&, Registry> {}; +struct parameter_traits&&, Registry> : + virtual_traits&&, Registry> {}; template struct validate_method_parameter< @@ -543,24 +546,24 @@ struct validate_method_parameter< template struct validate_method_parameter< - virtual_any&, MethodRegistry, void> - : std::bool_constant> { + 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> { + 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> { + virtual_any&&, MethodRegistry, void> : + std::bool_constant> { static_assert( std::is_same_v, "registry mismatch"); }; @@ -573,31 +576,31 @@ struct validate_method_parameter< // less specialized than . template -struct validate_overrider_parameter&, T2, void> - : std::true_type {}; +struct validate_overrider_parameter&, T2, void> : + std::true_type {}; template struct validate_overrider_parameter< - virtual_any&, virtual_any&, void> - : std::true_type {}; + virtual_any&, virtual_any&, void> : + std::true_type {}; template -struct validate_overrider_parameter&, T2, void> - : std::true_type {}; +struct validate_overrider_parameter< + const virtual_any&, T2, void> : std::true_type {}; template struct validate_overrider_parameter< - const virtual_any&, const virtual_any&, void> - : std::true_type {}; + const virtual_any&, const virtual_any&, + void> : std::true_type {}; template -struct validate_overrider_parameter&&, T2, void> - : std::true_type {}; +struct validate_overrider_parameter&&, T2, void> : + std::true_type {}; template struct validate_overrider_parameter< - virtual_any&&, virtual_any&&, void> - : std::true_type {}; + virtual_any&&, virtual_any&&, void> : + std::true_type {}; template struct select_overrider_virtual_type_aux< diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 4e895841..3566ffce 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -219,6 +219,8 @@ inline constexpr bool method_not_found = false; //! @see [Header and Implementation Files](xref:ROOT:headers.adoc) #define BOOST_OPENMETHOD(ID, PARAMETERS, ...) \ struct BOOST_OPENMETHOD_ID(ID); \ + using BOOST_OPENMETHOD_GENSYM = \ + BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__); \ template \ typename ::boost::openmethod::detail::enable_forwarder< \ void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ @@ -587,6 +589,54 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>) +//! Find the classes taking part in dispatch by reflection, and register them. +//! +//! It makes @ref BOOST_OPENMETHOD_CLASSES unnecessary in most cases. +//! +//! This macro is a wrapper around @ref boost::openmethod::register_classes; see +//! its documentation for the meaning of the arguments, which are passed +//! through verbatim: reflections of namespaces to scan, of classes to +//! register, at most one @ref boost::openmethod::register_classes_opts value, and +//! reflections of registries - each group optional, in that order. With no +//! argument at all - or none that names a namespace or a class - the +//! enclosing namespace is scanned. +//! +//! Reflection sees only what precedes it, so this macro must come **after** the +//! declarations it is meant to find - at the bottom of the file: +//! +//! @code +//! struct Animal { virtual ~Animal() = default; }; +//! struct Cat : Animal {}; +//! struct Dog : Animal {}; +//! struct Bulldog : Dog {}; +//! +//! BOOST_OPENMETHOD(poke, (std::ostream&, virtual_), void); +//! +//! BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream& os, Dog&), void) { +//! os << "bark"; +//! } +//! +//! BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); // registers all four classes +//! @endcode +//! +//! Without reflection - in C++17, or in C++26 without the compiler flag that +//! enables it - this macro expands to nothing, so a file that also calls +//! @ref BOOST_OPENMETHOD_CLASSES builds under either standard. +//! +//! @param ... Namespaces, classes, options, registries - see above. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +#if BOOST_OPENMETHOD_HAS_REFLECTION +#define BOOST_OPENMETHOD_REGISTER_CLASSES(...) \ + BOOST_OPENMETHOD_REGISTER( \ + ::boost::openmethod::register_classes< \ + ::boost::openmethod::detail::scope_marker{ \ + ::std::meta::access_context::current().scope() } __VA_OPT__( \ + , ) __VA_ARGS__>) +#else +#define BOOST_OPENMETHOD_REGISTER_CLASSES(...) static_assert(true) +#endif + // 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 diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index 0a365667..38cdff0f 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -50,20 +50,21 @@ struct default_error_handler : error_handler { template< typename T, class... Errors, class Policy, class... MorePolicies> struct error_variant_aux< - T, std::variant, mp11::mp_list> - : error_variant_aux< - void, std::variant, - mp11::mp_list> {}; + T, std::variant, + mp11::mp_list> : + error_variant_aux< + void, std::variant, mp11::mp_list> { + }; template struct error_variant_aux< std::void_t, std::variant, - mp11::mp_list> - : error_variant_aux< - void, - mp11::mp_append< - std::variant, typename Policy::errors>, - mp11::mp_list> {}; + mp11::mp_list> : + error_variant_aux< + void, + mp11::mp_append< + std::variant, typename Policy::errors>, + mp11::mp_list> {}; template struct error_variant_aux< diff --git a/include/boost/openmethod/policies/throw_error_handler.hpp b/include/boost/openmethod/policies/throw_error_handler.hpp index bea2bcc8..8fc83e30 100644 --- a/include/boost/openmethod/policies/throw_error_handler.hpp +++ b/include/boost/openmethod/policies/throw_error_handler.hpp @@ -38,8 +38,8 @@ struct throw_error_handler : error_handler { template [[noreturn]] static auto error(const Error& error) -> void { struct wrapper : Error, std::runtime_error { - wrapper(const Error& error, std::string&& description) - : Error(error), std::runtime_error(description) { + wrapper(const Error& error, std::string&& description) : + Error(error), std::runtime_error(description) { } }; diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index f2f020b2..257956bf 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -63,8 +63,8 @@ class vptr_map : public vptr { //! @param ctx A Context object. //! @param options A tuple of option objects. template - static void - initialize(const Context& ctx, const std::tuple&) { + static void initialize( + const Context& ctx, const std::tuple&) { decltype(st().vptrs) new_vptrs; for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 0d53ee71..9eb30dc2 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -1,6 +1,7 @@ #ifndef BOOST_OPENMETHOD_REGISTRY_HPP #define BOOST_OPENMETHOD_REGISTRY_HPP +#include #include #include @@ -175,6 +176,13 @@ struct not_initialized : openmethod_error { //! //! include:errors_missing_class_call.cpp#classes;use //! +//! @note With a compiler that supports C++26 reflection, @ref +//! BOOST_OPENMETHOD_REGISTER_CLASSES registers these classes on its own, and the +//! examples above no longer report anything. The error remains reachable - for +//! a class in a namespace the scan does not cover, or in a registry with an +//! @ref boost::openmethod::policies::explicit_class_registration policy, which +//! is what the examples use. +//! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_class : openmethod_error { //! The type_id of the unknown class. @@ -211,6 +219,13 @@ struct missing_class : openmethod_error { //! //! include:errors_missing_class_call.cpp#fix //! +//! @note With a compiler that supports C++26 reflection, @ref +//! BOOST_OPENMETHOD_REGISTER_CLASSES registers these classes on its own, and the +//! examples above no longer report anything. The error remains reachable - for +//! a class in a namespace the scan does not cover, or in a registry with an +//! @ref boost::openmethod::policies::explicit_class_registration policy, which +//! is what the examples use. +//! //! @see [Error Handling](xref:ROOT:error_handling.adoc) struct missing_base : openmethod_error { //! The type_id of the base class. @@ -891,6 +906,27 @@ struct runtime_checks final { struct fn {}; }; +// ----------------------------------------------------------------------------- +// explicit_class_registration + +//! Policy to disable reflection-based class registration. +//! +//! When the compiler supports C++26 reflection, the library registers the +//! classes of virtual parameters, and their base classes, on its own; see @ref +//! use_classes. If this policy is present, it does not: every class must be +//! registered with @ref use_classes or @ref BOOST_OPENMETHOD_CLASSES, exactly +//! as in C++17. +//! +//! The policy has no effect if the compiler does not support reflection. +//! +//! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) +struct explicit_class_registration final { + // Policy category. + using category = explicit_class_registration; + template + struct fn {}; +}; + } // namespace policies // ----------------------------------------------------------------------------- @@ -1059,8 +1095,8 @@ struct initialize_aux; struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux)) : std::false_type {}; \ template \ struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))< \ - std::void_t()...))>, T, Args...> \ - : std::true_type {}; \ + std::void_t()...))>, T, Args...> : \ + std::true_type {}; \ template \ constexpr bool BOOST_PP_CAT(has_, FN) = \ BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))::value @@ -1337,6 +1373,14 @@ class registry : public detail::registry_base { //! `true` if the registry has an indirect_vptr policy. static constexpr auto has_indirect_vptr = !std::is_same_v, void>; + + //! `true` if the library registers classes by reflection. + //! + //! `true` if the compiler supports C++26 reflection and the registry does + //! not have an @ref policies::explicit_class_registration policy. + static constexpr auto has_reflected_class_registration = + BOOST_OPENMETHOD_HAS_REFLECTION && + std::is_same_v, void>; }; template diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c404d5c1..6452506f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -94,6 +94,7 @@ foreach(test_cpp ${test_cpp_files}) set(test_target "boost_openmethod-${test}") add_executable(${test_target} EXCLUDE_FROM_ALL ${test_cpp}) target_link_libraries(${test_target} PRIVATE Boost::openmethod Boost::unit_test_framework) + boost_openmethod_enable_reflection(${test_target}) boost_openmethod_add_test(${test_target}) add_dependencies(tests ${test_target}) @@ -119,6 +120,7 @@ endforeach() add_executable(boost_openmethod-test_mix_release_debug EXCLUDE_FROM_ALL mix_release_debug/main.cpp mix_release_debug/lib.cpp) target_link_libraries(boost_openmethod-test_mix_release_debug PRIVATE Boost::openmethod Boost::unit_test_framework) +boost_openmethod_enable_reflection(boost_openmethod-test_mix_release_debug) boost_openmethod_add_test(boost_openmethod-test_mix_release_debug) add_dependencies(tests boost_openmethod-test_mix_release_debug) @@ -150,6 +152,7 @@ set_property( function(openmethod_compile_fail_test testname fail_regex) set(test_target "boost_openmethod-${testname}") add_library(${test_target} STATIC EXCLUDE_FROM_ALL "${testname}.cpp") + boost_openmethod_enable_reflection(${test_target}) target_link_libraries(${test_target} PRIVATE Boost::openmethod) add_test( NAME "${test_target}" diff --git a/test/Jamfile b/test/Jamfile index 92001cde..5a588b62 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -46,6 +46,13 @@ project ../../type_erasure/include BOOST_TYPE_ERASURE_NO_LIB=1 + # C++26 reflection (P2996). GCC provides it only behind a flag, and accepts + # that flag only alongside a C++26 standard flag - so this is conditioned on + # cxxstd=26, not on cxxstd=2c, which spells the standard -std=c++2c. The + # library needs nothing else: it detects reflection from + # __cpp_impl_reflection, and falls back to explicit class registration. + gcc,26:-freflection + extra clang:on diff --git a/test/compile_fail_reflection_arg_order.cpp b/test/compile_fail_reflection_arg_order.cpp new file mode 100644 index 00000000..bff8b3a3 --- /dev/null +++ b/test/compile_fail_reflection_arg_order.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: order the arguments as namespaces, classes, options, registries + +#include + +#if !BOOST_OPENMETHOD_HAS_REFLECTION + +// Without reflection there is nothing to check; produce the expected +// diagnostic so the test passes under any configuration. +#error order the arguments as namespaces, classes, options, registries + +#else + +namespace app { + +struct my_registry : boost::openmethod::default_registry::with<> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +// The registry must come last. +BOOST_OPENMETHOD_REGISTER( + boost::openmethod::register_classes<^^app::my_registry, ^^app::Animal>); + +} // namespace app + +#endif + +int main() { + return 0; +} diff --git a/test/compile_fail_reflection_no_scan_source.cpp b/test/compile_fail_reflection_no_scan_source.cpp new file mode 100644 index 00000000..14ba74f7 --- /dev/null +++ b/test/compile_fail_reflection_no_scan_source.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: the enclosing namespace cannot be captured here + +#include + +#if !BOOST_OPENMETHOD_HAS_REFLECTION + +// Without reflection there is nothing to check; produce the expected +// diagnostic so the test passes under any configuration. +#error the enclosing namespace cannot be captured here + +#else + +namespace app { + +struct my_registry : boost::openmethod::default_registry::with<> {}; + +// Only the default template argument of the bare class template - or +// BOOST_OPENMETHOD_REGISTER_CLASSES - can capture the enclosing namespace. With a +// registry as the only argument, there is nothing to scan. +BOOST_OPENMETHOD_REGISTER( + boost::openmethod::register_classes<^^app::my_registry>); + +} // namespace app + +#endif + +int main() { + return 0; +} diff --git a/test/compile_fail_std_any_custom_rtti.cpp b/test/compile_fail_std_any_custom_rtti.cpp index c222b5d9..6a6e224b 100644 --- a/test/compile_fail_std_any_custom_rtti.cpp +++ b/test/compile_fail_std_any_custom_rtti.cpp @@ -40,8 +40,8 @@ struct custom_rtti : policies::rtti { }; }; -struct custom_rtti_registry - : default_registry::with::without {}; +struct custom_rtti_registry : + default_registry::with::without {}; struct Dog { std::string name; diff --git a/test/compile_fail_type_erasure_custom_rtti.cpp b/test/compile_fail_type_erasure_custom_rtti.cpp index 5182c710..e0ba45ab 100644 --- a/test/compile_fail_type_erasure_custom_rtti.cpp +++ b/test/compile_fail_type_erasure_custom_rtti.cpp @@ -43,8 +43,8 @@ struct custom_rtti : policies::rtti { }; }; -struct custom_rtti_registry - : default_registry::with::without {}; +struct custom_rtti_registry : + default_registry::with::without {}; using Concept = boost::mpl::vector, te::typeid_<>, te::relaxed>; diff --git a/test/compile_fail_virtual_ptr_different_registries.cpp b/test/compile_fail_virtual_ptr_different_registries.cpp index de782329..728f9e0b 100644 --- a/test/compile_fail_virtual_ptr_different_registries.cpp +++ b/test/compile_fail_virtual_ptr_different_registries.cpp @@ -15,8 +15,9 @@ struct Cat { } }; -struct other_registry : default_registry::without::with< - policies::runtime_checks> {}; +struct other_registry : + default_registry::without::with< + policies::runtime_checks> {}; BOOST_OPENMETHOD(poke, (virtual_ptr), void, other_registry); diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 0a5fa6a3..6ca51418 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -31,8 +31,8 @@ using state_id_fn = const void*(); BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); -boost::filesystem::path -find_lib(const boost::filesystem::path& dir, const char* name_fragment) { +boost::filesystem::path find_lib( + const boost::filesystem::path& dir, const char* name_fragment) { for (auto entry : boost::filesystem::directory_iterator(dir)) { auto fname = entry.path().filename().string(); if (fname.find(name_fragment) != std::string::npos) { diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 567048d3..350e081d 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -46,8 +46,8 @@ BOOST_SYMBOL_EXPORT void method_make_dog(unique_virtual_ptr& p) { p = make_dog(); } -BOOST_SYMBOL_EXPORT const char* -method_call_speak(boost::openmethod::virtual_ptr animal) { +BOOST_SYMBOL_EXPORT const char* method_call_speak( + boost::openmethod::virtual_ptr animal) { return speak(animal); } diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 6e7a7172..15a9419f 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -41,8 +41,8 @@ BOOST_SYMBOL_EXPORT void overrider_make_dog(unique_virtual_ptr& p) { p = make_dog(); } -BOOST_SYMBOL_EXPORT const char* -overrider_call_speak(boost::openmethod::virtual_ptr animal) { +BOOST_SYMBOL_EXPORT const char* overrider_call_speak( + boost::openmethod::virtual_ptr animal) { return speak(animal); } diff --git a/test/implicit_shared_libraries/custom_registry/registry.hpp b/test/implicit_shared_libraries/custom_registry/registry.hpp index dc4b56aa..1a3bcd9d 100644 --- a/test/implicit_shared_libraries/custom_registry/registry.hpp +++ b/test/implicit_shared_libraries/custom_registry/registry.hpp @@ -24,9 +24,10 @@ struct custom_registry; // directly. The resulting policy list is: // // std_rtti, vptr_map<>, default_error_handler, stderr_output -struct custom_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::vptr_map<>>:: - without {}; +struct custom_registry : + boost::openmethod::default_registry:: + with>::without< + boost::openmethod::policies::type_hash> {}; // Both removals above happen implicitly, by category, so assert them. static_assert(boost::mp11::mp_contains< diff --git a/test/test_capture_errors.hpp b/test/test_capture_errors.hpp index ddfaabee..e1999a2e 100644 --- a/test/test_capture_errors.hpp +++ b/test/test_capture_errors.hpp @@ -11,6 +11,13 @@ // definition, the library include, and `test_registry` itself. Including it // first - before anything that pulls in core.hpp - is all a test has to do, // and there is no ordering left for a caller to get wrong. +// +// A test that is *about* a class the library must not find on its own - one +// that expects `missing_class` or `missing_base` - defines +// BOOST_OPENMETHOD_TEST_EXPLICIT_CLASS_REGISTRATION before including this +// header. The `explicit_class_registration` policy then leaves every +// registration to the test, exactly as in C++17, instead of letting reflection +// supply the class the test is withholding. struct test_registry; #define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry @@ -29,8 +36,15 @@ struct capture_output : boost::openmethod::policies::output { }; }; -struct test_registry - : boost::openmethod::default_registry::with {}; +#ifdef BOOST_OPENMETHOD_TEST_EXPLICIT_CLASS_REGISTRATION +struct test_registry : + boost::openmethod::default_registry::with< + capture_output, + boost::openmethod::policies::explicit_class_registration> {}; +#else +struct test_registry : + boost::openmethod::default_registry::with {}; +#endif template struct capture_errors { diff --git a/test/test_checked_registry.hpp b/test/test_checked_registry.hpp index b9a31c10..657e5bd1 100644 --- a/test/test_checked_registry.hpp +++ b/test/test_checked_registry.hpp @@ -15,14 +15,30 @@ // // `runtime_checks` catches what initialize() cannot; `throw_error_handler` // turns the diagnosis into an exception the test can catch. +// +// A test that is *about* a class the library must not find on its own - one +// that expects `missing_class` or `missing_base` - defines +// BOOST_OPENMETHOD_TEST_EXPLICIT_CLASS_REGISTRATION before including this +// header. The `explicit_class_registration` policy then leaves every +// registration to the test, exactly as in C++17, instead of letting reflection +// supply the class the test is withholding. struct test_registry; #define BOOST_OPENMETHOD_DEFAULT_REGISTRY test_registry #include #include -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; +#ifdef BOOST_OPENMETHOD_TEST_EXPLICIT_CLASS_REGISTRATION +struct test_registry : + boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks, + boost::openmethod::policies::throw_error_handler, + boost::openmethod::policies::explicit_class_registration> {}; +#else +struct test_registry : + boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks, + boost::openmethod::policies::throw_error_handler> {}; +#endif #endif diff --git a/test/test_class_registration_missing_base_class.cpp b/test/test_class_registration_missing_base_class.cpp index e1f395b9..af4280f0 100644 --- a/test/test_class_registration_missing_base_class.cpp +++ b/test/test_class_registration_missing_base_class.cpp @@ -3,6 +3,10 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +// This test is *about* a class that is not registered, so the library must not +// register it by reflection. +#define BOOST_OPENMETHOD_TEST_EXPLICIT_CLASS_REGISTRATION + #include "test_checked_registry.hpp" #include diff --git a/test/test_class_registration_unknown_class_overrider.cpp b/test/test_class_registration_unknown_class_overrider.cpp index 0781b49f..61d868a3 100644 --- a/test/test_class_registration_unknown_class_overrider.cpp +++ b/test/test_class_registration_unknown_class_overrider.cpp @@ -3,6 +3,10 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +// This test is *about* a class that is not registered, so the library must not +// register it by reflection. +#define BOOST_OPENMETHOD_TEST_EXPLICIT_CLASS_REGISTRATION + #include "test_checked_registry.hpp" #include diff --git a/test/test_classes.hpp b/test/test_classes.hpp new file mode 100644 index 00000000..8947c3c4 --- /dev/null +++ b/test/test_classes.hpp @@ -0,0 +1,30 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_TEST_CLASSES_HPP +#define BOOST_OPENMETHOD_TEST_CLASSES_HPP + +#include + +//! Register classes, unless the library can find them by reflection. +//! +//! Expands to @ref BOOST_OPENMETHOD_CLASSES in C++17, and to nothing when the +//! compiler supports C++26 reflection. Tests that are not *about* class +//! registration use this instead of `BOOST_OPENMETHOD_CLASSES`, so that a +//! C++26 run exercises reflection-based registration over the whole suite: the +//! classes go unregistered, and every test still has to pass. +//! +//! Tests that check what happens when a class is *not* registered keep +//! `BOOST_OPENMETHOD_CLASSES`, and put +//! `boost::openmethod::policies::explicit_class_registration` in their +//! registry so that the library leaves the registration to them. + +#if BOOST_OPENMETHOD_HAS_REFLECTION +#define BOOST_OPENMETHOD_TEST_CLASSES(...) +#else +#define BOOST_OPENMETHOD_TEST_CLASSES(...) BOOST_OPENMETHOD_CLASSES(__VA_ARGS__) +#endif + +#endif diff --git a/test/test_compiler.cpp b/test/test_compiler.cpp index b4def394..95ea47b8 100644 --- a/test/test_compiler.cpp +++ b/test/test_compiler.cpp @@ -147,6 +147,54 @@ BOOST_AUTO_TEST_CASE(test_use_classes_linear) { BOOST_CHECK_EQUAL(sstr(d4->transitive_derived), sstr(d4, d5)); } +// The lattice must not depend on the order in which classes are registered. +// Every record below carries exactly one, genuinely direct, base; only the order +// of the calls differs from `test_use_classes_linear`, with D3's own ancestry +// registered last. +BOOST_AUTO_TEST_CASE(test_use_classes_derived_before_base) { + struct Base { + virtual ~Base() = default; + }; + + struct D1 : Base {}; + struct D2 : D1 {}; + struct D3 : D2 {}; + struct D4 : D3 {}; + struct D5 : D4 {}; + + struct registry : test_registry_<__COUNTER__> {}; + + BOOST_OPENMETHOD_CLASSES(D3, D4, registry); + BOOST_OPENMETHOD_CLASSES(D4, D5, registry); + BOOST_OPENMETHOD_CLASSES(Base, D1, D2, D3, registry); + + auto comp = initialize(); + + auto base = get_class(comp); + auto d1 = get_class(comp); + auto d2 = get_class(comp); + auto d3 = get_class(comp); + auto d4 = get_class(comp); + auto d5 = get_class(comp); + + BOOST_CHECK_EQUAL(sstr(base->direct_bases), empty); + BOOST_CHECK_EQUAL(sstr(d1->direct_bases), sstr(base)); + BOOST_CHECK_EQUAL(sstr(d2->direct_bases), sstr(d1)); + BOOST_CHECK_EQUAL(sstr(d3->direct_bases), sstr(d2)); + BOOST_CHECK_EQUAL(sstr(d4->direct_bases), sstr(d3)); + // D3 is an *indirect* base of D5, and must not appear here. + BOOST_CHECK_EQUAL(sstr(d5->direct_bases), sstr(d4)); + + BOOST_CHECK_EQUAL(sstr(d5->transitive_bases), sstr(base, d1, d2, d3, d4)); + BOOST_CHECK_EQUAL(sstr(d4->transitive_bases), sstr(base, d1, d2, d3)); + + BOOST_CHECK_EQUAL(sstr(base->direct_derived), sstr(d1)); + BOOST_CHECK_EQUAL(sstr(d3->direct_derived), sstr(d4)); + BOOST_CHECK_EQUAL(sstr(d4->direct_derived), sstr(d5)); + BOOST_CHECK_EQUAL( + sstr(base->transitive_derived), sstr(base, d1, d2, d3, d4, d5)); +} + BOOST_AUTO_TEST_CASE(test_use_classes_diamond) { using test_registry = test_registry_<__COUNTER__>; BOOST_OPENMETHOD_REGISTER(use_classes); diff --git a/test/test_core.cpp b/test/test_core.cpp index 13ee4473..7f6c8408 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -282,8 +282,8 @@ namespace TEST_NS { using test_registry = test_registry_<__COUNTER__>; struct Animal { - friend auto - boost_openmethod_vptr(const Animal&, test_registry*) -> vptr_type; + friend auto boost_openmethod_vptr(const Animal&, test_registry*) + -> vptr_type; }; static_assert(detail::has_vptr_fn); diff --git a/test/test_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp index e56a2de2..61935c4e 100644 --- a/test/test_custom_rtti_deferred.cpp +++ b/test/test_custom_rtti_deferred.cpp @@ -9,6 +9,8 @@ struct test_registry; #include #include +#include "test_classes.hpp" + #include namespace { @@ -144,9 +146,9 @@ struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { }; }; -struct test_registry - : boost::openmethod::default_registry::with::without< - boost::openmethod::policies::type_hash> {}; +struct test_registry : + boost::openmethod::default_registry::with::without< + boost::openmethod::policies::type_hash> {}; #define BOOST_TEST_MODULE custom_rtti_deferred #include @@ -154,7 +156,7 @@ struct test_registry using namespace boost::openmethod; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, Bat, Owl); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat, Bat, Owl); BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); @@ -233,3 +235,7 @@ BOOST_AUTO_TEST_CASE(custom_rtti_deferred) { BOOST_TEST(os.str() == "The bat evades the owl."); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp index 98f70647..5c48f2a8 100644 --- a/test/test_custom_rtti_simple.cpp +++ b/test/test_custom_rtti_simple.cpp @@ -9,6 +9,8 @@ struct test_registry; #include #include +#include "test_classes.hpp" + #include namespace { @@ -90,9 +92,9 @@ struct custom_rtti : boost::openmethod::policies::rtti { }; }; -struct test_registry - : boost::openmethod::default_registry::with::without< - boost::openmethod::policies::type_hash> {}; +struct test_registry : + boost::openmethod::default_registry::with::without< + boost::openmethod::policies::type_hash> {}; #define BOOST_TEST_MODULE custom_rtti_simple #include @@ -100,7 +102,7 @@ struct test_registry using namespace boost::openmethod; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); @@ -166,3 +168,7 @@ void call_poke(vptr a, std::ostream& os) { } } // namespace using_vptr + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_custom_rtti_simple_projection.cpp b/test/test_custom_rtti_simple_projection.cpp index d2f06b3b..8cb911da 100644 --- a/test/test_custom_rtti_simple_projection.cpp +++ b/test/test_custom_rtti_simple_projection.cpp @@ -9,6 +9,8 @@ struct test_registry; #include #include +#include "test_classes.hpp" + namespace { template inline char non_polymorphic_static_type_storage = '\0'; @@ -77,8 +79,8 @@ struct custom_rtti : boost::openmethod::policies::rtti { }; }; -struct test_registry : boost::openmethod::default_registry::with { -}; +struct test_registry : + boost::openmethod::default_registry::with {}; #define BOOST_TEST_MODULE custom_rtti_simple_projection #include @@ -86,7 +88,7 @@ struct test_registry : boost::openmethod::default_registry::with { using namespace boost::openmethod; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); @@ -114,3 +116,7 @@ BOOST_AUTO_TEST_CASE(custom_rtti_simple_projection) { BOOST_TEST(os.str() == "Sylvester hisses."); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_custom_rtti_virtual_base.cpp b/test/test_custom_rtti_virtual_base.cpp index 0ccb51b9..e4c62363 100644 --- a/test/test_custom_rtti_virtual_base.cpp +++ b/test/test_custom_rtti_virtual_base.cpp @@ -9,6 +9,8 @@ struct test_registry; #include #include +#include "test_classes.hpp" + #include namespace { @@ -115,9 +117,9 @@ struct custom_rtti : boost::openmethod::policies::rtti { }; }; -struct test_registry - : boost::openmethod::default_registry::with::without< - boost::openmethod::policies::type_hash> {}; +struct test_registry : + boost::openmethod::default_registry::with::without< + boost::openmethod::policies::type_hash> {}; #define BOOST_TEST_MODULE custom_rtti_virtual_base #include @@ -125,7 +127,7 @@ struct test_registry using namespace boost::openmethod; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); @@ -191,3 +193,7 @@ void call_poke(vptr a, std::ostream& os) { } } // namespace using_vptr + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_across_namespaces.cpp b/test/test_dispatch_across_namespaces.cpp index ed1fb76d..aaf6dc51 100644 --- a/test/test_dispatch_across_namespaces.cpp +++ b/test/test_dispatch_across_namespaces.cpp @@ -8,6 +8,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE dispatch_across_namespaces #include @@ -29,7 +31,7 @@ namespace more_animals { class Dog : public animals::Animal {}; -BOOST_OPENMETHOD_CLASSES(Dog, animals::Animal); +BOOST_OPENMETHOD_TEST_CLASSES(Dog, animals::Animal); BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog&), std::string) { return "bark"; @@ -43,3 +45,7 @@ BOOST_AUTO_TEST_CASE(across_namespaces) { const animals::Animal& animal = more_animals::Dog(); BOOST_TEST("bark" == poke(animal)); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index b3bf13b0..8c575a5f 100644 --- a/test/test_dispatch_boost_any.cpp +++ b/test/test_dispatch_boost_any.cpp @@ -11,6 +11,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE dispatch_boost_any #include @@ -292,7 +294,7 @@ struct Animal { struct Cat : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Cat); BOOST_OPENMETHOD( meet, (virtual_, virtual_ptr), @@ -325,9 +327,9 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // a type that is never named statically anywhere is not registered -struct throw_registry - : default_registry::with< - policies::runtime_checks, policies::throw_error_handler> {}; +struct throw_registry : + default_registry::with< + policies::runtime_checks, policies::throw_error_handler> {}; struct Dog { std::string name; @@ -373,7 +375,7 @@ struct Dog : Animal { std::string name; }; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); @@ -399,3 +401,7 @@ BOOST_AUTO_TEST_CASE(boost_any_class_in_hierarchy) { BOOST_TEST(name(any_spot) == "Spot the dog"); } } // namespace BOOST_OPENMETHOD_GENSYM + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_comma_in_return_type.cpp b/test/test_dispatch_comma_in_return_type.cpp index 34be736f..4c8f7ba8 100644 --- a/test/test_dispatch_comma_in_return_type.cpp +++ b/test/test_dispatch_comma_in_return_type.cpp @@ -8,16 +8,18 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE dispatch_comma_in_return_type #include using namespace boost::openmethod; struct Test { - virtual ~Test(){}; + virtual ~Test() {}; }; -BOOST_OPENMETHOD_CLASSES(Test); +BOOST_OPENMETHOD_TEST_CLASSES(Test); BOOST_OPENMETHOD(foo, (virtual_), std::pair); @@ -32,3 +34,7 @@ BOOST_AUTO_TEST_CASE(comma_in_return_type) { BOOST_CHECK(foo(test) == std::pair(1, 2)); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_intrusive_ptr.cpp b/test/test_dispatch_intrusive_ptr.cpp index f22749b2..7ff45b3c 100644 --- a/test/test_dispatch_intrusive_ptr.cpp +++ b/test/test_dispatch_intrusive_ptr.cpp @@ -12,6 +12,8 @@ #include #include #include + +#include "test_classes.hpp" #include #include @@ -41,7 +43,7 @@ using namespace boost::openmethod; using Animal::Animal; \ }; \ \ - BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); namespace BOOST_OPENMETHOD_GENSYM { @@ -166,3 +168,7 @@ BOOST_AUTO_TEST_CASE(intrusive_virtual_ptr_by_const_ref) { BOOST_TEST(name(felix) == "Felix the cat"); } } // namespace BOOST_OPENMETHOD_GENSYM + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_lvalue_refs.cpp b/test/test_dispatch_lvalue_refs.cpp index 4068e840..1881aa63 100644 --- a/test/test_dispatch_lvalue_refs.cpp +++ b/test/test_dispatch_lvalue_refs.cpp @@ -16,7 +16,7 @@ using namespace boost::openmethod; using namespace animals; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(name, (virtual_), std::string); @@ -37,3 +37,7 @@ BOOST_AUTO_TEST_CASE(cast_args_lvalue_refs) { Cat felix("Felix"); BOOST_TEST(name(felix) == "Bill's cat Felix"); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_multi.cpp b/test/test_dispatch_multi.cpp index f1cb5a83..49bd773b 100644 --- a/test/test_dispatch_multi.cpp +++ b/test/test_dispatch_multi.cpp @@ -14,7 +14,12 @@ using namespace boost::openmethod; using namespace test_matrices; -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); +BOOST_OPENMETHOD_TEST_CLASSES(matrix, diagonal_matrix); + +// dense_matrix has no overrider of its own, and is not named in any method +// signature, so reflection has no way of finding it. It is registered by hand +// in C++26 too - along with its base, as use_classes requires. +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix); BOOST_OPENMETHOD( times, (virtual_, virtual_), string_pair); @@ -69,3 +74,7 @@ BOOST_AUTO_TEST_CASE(simple) { times(diag, 2) == string_pair(DIAGONAL_SCALAR, MATRIX_SCALAR)); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_next_fn.cpp b/test/test_dispatch_next_fn.cpp index fde927d6..2141248c 100644 --- a/test/test_dispatch_next_fn.cpp +++ b/test/test_dispatch_next_fn.cpp @@ -9,6 +9,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE dispatch_next_fn #include @@ -22,7 +24,7 @@ struct Animal { struct Dog : Animal {}; struct Bulldog : Dog {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Bulldog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Bulldog); struct BOOST_OPENMETHOD_ID(poke); using poke = @@ -49,3 +51,7 @@ BOOST_AUTO_TEST_CASE(test_next_fn) { std::unique_ptr hector = std::make_unique(); BOOST_TEST(poke::fn(*hector) == "bark and bite back"); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_pointer.cpp b/test/test_dispatch_pointer.cpp index 925f3da2..950fd110 100644 --- a/test/test_dispatch_pointer.cpp +++ b/test/test_dispatch_pointer.cpp @@ -16,7 +16,7 @@ using namespace boost::openmethod; using namespace animals; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(name, (virtual_), std::string); @@ -37,3 +37,7 @@ BOOST_AUTO_TEST_CASE(cast_args_pointer) { Cat felix("Felix"); BOOST_TEST(name(&felix) == "Bill's cat Felix"); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_rvalue_refs.cpp b/test/test_dispatch_rvalue_refs.cpp index d89f8c29..9172263d 100644 --- a/test/test_dispatch_rvalue_refs.cpp +++ b/test/test_dispatch_rvalue_refs.cpp @@ -16,7 +16,7 @@ using namespace boost::openmethod; using namespace animals; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(teleport, (virtual_), std::unique_ptr); @@ -45,3 +45,7 @@ BOOST_AUTO_TEST_CASE(cast_args_rvalue_refs) { BOOST_TEST(felix.name == ""); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_shared_ptr_by_ref.cpp b/test/test_dispatch_shared_ptr_by_ref.cpp index 6b5bb0ee..699e21c8 100644 --- a/test/test_dispatch_shared_ptr_by_ref.cpp +++ b/test/test_dispatch_shared_ptr_by_ref.cpp @@ -18,7 +18,7 @@ using namespace boost::openmethod; using namespace animals; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD( name, (virtual_&>), std::string); @@ -42,3 +42,7 @@ BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_ref) { auto felix = std::make_shared("Felix"); BOOST_TEST(name(felix) == "Bill's cat Felix"); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_shared_ptr_by_value.cpp b/test/test_dispatch_shared_ptr_by_value.cpp index 064d6a99..1bfab5fc 100644 --- a/test/test_dispatch_shared_ptr_by_value.cpp +++ b/test/test_dispatch_shared_ptr_by_value.cpp @@ -18,7 +18,7 @@ using namespace boost::openmethod; using namespace animals; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(name, (virtual_>), std::string); @@ -39,3 +39,7 @@ BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_value) { auto felix = std::make_shared("Felix"); BOOST_TEST(name(felix) == "Bill's cat Felix"); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 05b68ba4..e7a480ba 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -11,6 +11,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE openmethod #include @@ -290,7 +292,7 @@ struct Animal { struct Cat : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Cat); BOOST_OPENMETHOD( meet, (virtual_, virtual_ptr), std::string); @@ -322,9 +324,9 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // a type that is never named statically anywhere is not registered -struct throw_registry - : default_registry::with< - policies::runtime_checks, policies::throw_error_handler> {}; +struct throw_registry : + default_registry::with< + policies::runtime_checks, policies::throw_error_handler> {}; struct Dog { std::string name; @@ -370,7 +372,7 @@ struct Dog : Animal { std::string name; }; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (virtual_ptr), std::string); @@ -396,3 +398,7 @@ BOOST_AUTO_TEST_CASE(std_any_class_in_hierarchy) { BOOST_TEST(name(any_spot) == "Spot the dog"); } } // namespace BOOST_OPENMETHOD_GENSYM + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_type_erasure.cpp b/test/test_dispatch_type_erasure.cpp index 13fc4e2b..83d82755 100644 --- a/test/test_dispatch_type_erasure.cpp +++ b/test/test_dispatch_type_erasure.cpp @@ -282,9 +282,10 @@ struct Dog { // The concept must name the Concept it is part of: define the Concept as // a struct. -struct Dispatchable : boost::mpl::vector< - te::copy_constructible<>, te::relaxed, - openmethod_vptr> {}; +struct Dispatchable : + boost::mpl::vector< + te::copy_constructible<>, te::relaxed, openmethod_vptr> { +}; using dispatchable = te::any; using dispatchable_ref = te::any; @@ -346,9 +347,10 @@ struct Dog { std::string name; }; -struct Dispatchable : boost::mpl::vector< - te::copy_constructible<>, te::relaxed, - openmethod_vptr> {}; +struct Dispatchable : + boost::mpl::vector< + te::copy_constructible<>, te::relaxed, + openmethod_vptr> {}; using dispatchable = te::any; diff --git a/test/test_dispatch_unique_ptr.cpp b/test/test_dispatch_unique_ptr.cpp index 2fd4db28..c9a75737 100644 --- a/test/test_dispatch_unique_ptr.cpp +++ b/test/test_dispatch_unique_ptr.cpp @@ -18,7 +18,7 @@ using namespace boost::openmethod; using namespace animals; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(name, (virtual_>), std::string); @@ -41,3 +41,7 @@ BOOST_AUTO_TEST_CASE(cast_args_unique_ptr) { BOOST_TEST(name(std::move(felix)) == "Bill's cat Felix"); BOOST_TEST(felix.get() == nullptr); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_inplace_vptr.cpp b/test/test_inplace_vptr.cpp index 74360f91..d5dd37a5 100644 --- a/test/test_inplace_vptr.cpp +++ b/test/test_inplace_vptr.cpp @@ -15,8 +15,9 @@ struct test_registry; #include namespace bom = boost::openmethod; -struct test_registry : bom::default_registry::without< - bom::policies::vptr, bom::policies::type_hash> {}; +struct test_registry : + bom::default_registry::without< + bom::policies::vptr, bom::policies::type_hash> {}; #define BOOST_TEST_MODULE intrusive #include @@ -42,9 +43,10 @@ struct Pet : bom::inplace_vptr_base { std::ostream& os; }; -struct DomesticCat : Cat, - Pet, - bom::inplace_vptr_derived { +struct DomesticCat : + Cat, + Pet, + bom::inplace_vptr_derived { explicit DomesticCat(std::ostream& os); ~DomesticCat(); }; diff --git a/test/test_n2216_covariant_return_type.cpp b/test/test_n2216_covariant_return_type.cpp index 361c15e9..b198fdde 100644 --- a/test/test_n2216_covariant_return_type.cpp +++ b/test/test_n2216_covariant_return_type.cpp @@ -17,7 +17,7 @@ using namespace boost::openmethod; using namespace test_matrices; -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix); +BOOST_OPENMETHOD_TEST_CLASSES(matrix, dense_matrix); BOOST_OPENMETHOD( times, (virtual_, virtual_), @@ -47,3 +47,7 @@ BOOST_AUTO_TEST_CASE(covariant_return_type) { auto result = times(left, right); BOOST_TEST(result->type == DENSE_MATRIX); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_n2216_pick_any_ambiguous.cpp b/test/test_n2216_pick_any_ambiguous.cpp index dc351017..61eda1ff 100644 --- a/test/test_n2216_pick_any_ambiguous.cpp +++ b/test/test_n2216_pick_any_ambiguous.cpp @@ -14,7 +14,7 @@ using namespace boost::openmethod; using namespace test_matrices; -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix); +BOOST_OPENMETHOD_TEST_CLASSES(matrix, dense_matrix); BOOST_OPENMETHOD( times, (virtual_, virtual_), string_pair); @@ -43,3 +43,7 @@ BOOST_AUTO_TEST_CASE(pick_any_ambiguous) { BOOST_TEST(result.first == MATRIX_DENSE); BOOST_TEST(result.second == MATRIX_MATRIX); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_namespaces.cpp b/test/test_namespaces.cpp index 9273a293..8e9301bf 100644 --- a/test/test_namespaces.cpp +++ b/test/test_namespaces.cpp @@ -29,11 +29,17 @@ class Dolphin : public interfaces::Animal {}; #include #include +#include "test_classes.hpp" + using boost::openmethod::virtual_; -BOOST_OPENMETHOD_CLASSES( - interfaces::Animal, canis::Dog, canis::Bulldog, felis::Cat, - delphinus::Dolphin); +BOOST_OPENMETHOD_TEST_CLASSES( + interfaces::Animal, canis::Dog, canis::Bulldog, felis::Cat); + +// Dolphin has no overrider of its own, and is not named in any method +// signature, so reflection has no way of finding it. It is registered by hand +// in C++26 too - along with its base, as use_classes requires. +BOOST_OPENMETHOD_CLASSES(interfaces::Animal, delphinus::Dolphin); // open method with single virtual argument <=> virtual function "from outside" BOOST_OPENMETHOD(poke, (virtual_), std::string); @@ -102,3 +108,7 @@ auto main() -> int { std::cout << "hector meets flipper: " << meet(*hector, *flipper) << "\n"; // ignore } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_pointer_to_method.cpp b/test/test_pointer_to_method.cpp index 92138988..adc2e6cb 100644 --- a/test/test_pointer_to_method.cpp +++ b/test/test_pointer_to_method.cpp @@ -6,6 +6,8 @@ #include #include +#include "test_classes.hpp" + #include #define BOOST_TEST_MODULE openmethod @@ -21,7 +23,7 @@ class Animal { class Dog : public Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Animal); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Animal); BOOST_OPENMETHOD(poke, (virtual_), std::string); @@ -35,3 +37,7 @@ BOOST_AUTO_TEST_CASE(noadl) { Dog snoopy; BOOST_TEST(stimulus(snoopy) == "bark"); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_reflection.cpp b/test/test_reflection.cpp new file mode 100644 index 00000000..8efbc982 --- /dev/null +++ b/test/test_reflection.cpp @@ -0,0 +1,922 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Tests for reflection-based class registration - `register_classes` and +// `BOOST_OPENMETHOD_REGISTER_CLASSES`. Without a compiler that supports C++26 +// reflection there is nothing to test, and the whole file reduces to one test +// case that says so. + +#include +#include + +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE reflection +#include + +#if !BOOST_OPENMETHOD_HAS_REFLECTION + +BOOST_AUTO_TEST_CASE(reflection_not_supported) { + BOOST_TEST_MESSAGE("compiler does not support C++26 reflection"); +} + +#else + +#include + +#include +#include + +using namespace boost::openmethod; + +// True if `Class` was registered in `Registry`, as seen by the compiler object +// `initialize` returns. +template +auto registered(const Compiler& comp) -> bool { + return comp.class_map.find( + Registry::rtti::type_index( + Registry::rtti::template static_type())) != + comp.class_map.end(); +} + +// ============================================================================= +// The macro interface, and a leaf class no signature names + +namespace macro_interface { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; +struct Cat : Animal {}; + +// Never named in a method or an overrider, and never wrapped in a virtual_ptr. +// Only the namespace scan can find it. +struct Bulldog : Dog {}; + +// Not related to any virtual parameter: must be left alone. +struct Fence { + virtual ~Fence() = default; +}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat&), std::string) { + return "hiss"; +} + +} // namespace macro_interface + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^macro_interface, ^^macro_interface::test_registry>); + +BOOST_AUTO_TEST_CASE(macro_interface_dispatch) { + using namespace macro_interface; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + // Found by the scan, although nothing dispatches on it. + BOOST_TEST((registered(comp))); + // Unrelated to every virtual parameter. + BOOST_TEST((!registered(comp))); + + Animal animal; + Dog dog; + Cat cat; + Bulldog bulldog; + + BOOST_TEST(poke(animal) == "generic"); + BOOST_TEST(poke(dog) == "bark"); + BOOST_TEST(poke(cat) == "hiss"); + // Bulldog has no overrider of its own; it is registered as derived from + // Dog, so Dog's overrider applies. + BOOST_TEST(poke(bulldog) == "bark"); +} + +// ============================================================================= +// The core interface: a method named by an alias, overriders as free functions + +namespace core_interface { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Node { + virtual ~Node() = default; +}; + +struct Literal : Node {}; +struct Plus : Node {}; + +struct BOOST_OPENMETHOD_ID(value); + +using value = method< + BOOST_OPENMETHOD_ID(value), std::string(virtual_ptr), + test_registry>; + +auto value_literal(virtual_ptr) -> std::string { + return "literal"; +} + +auto value_plus(virtual_ptr) -> std::string { + return "plus"; +} + +BOOST_OPENMETHOD_REGISTER(value::override); + +} // namespace core_interface + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^core_interface, ^^core_interface::test_registry>); + +BOOST_AUTO_TEST_CASE(core_interface_dispatch) { + using namespace core_interface; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + Literal literal; + Plus plus; + + BOOST_TEST( + value::fn(virtual_ptr(literal)) == "literal"); + BOOST_TEST(value::fn(virtual_ptr(plus)) == "plus"); +} + +// ============================================================================= +// A method with no overrider at all + +namespace method_only { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +// No overrider, so no registrar object names the method. It is found through +// the alias BOOST_OPENMETHOD declares for it. +BOOST_OPENMETHOD(poke, (virtual_), void, test_registry); + +} // namespace method_only + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^method_only, ^^method_only::test_registry>); + +BOOST_AUTO_TEST_CASE(method_without_overrider) { + using namespace method_only; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); +} + +// ============================================================================= +// Inheritance: virtual, multiple, and inaccessible bases + +namespace inheritance { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Herbivore : virtual Animal {}; +struct Carnivore : virtual Animal {}; +struct Omnivore : Herbivore, Carnivore {}; + +// Reached only through a private base: the library cannot convert a Stowaway to +// an Animal, so it must not be registered as one. +struct Stowaway : private Animal {}; + +BOOST_OPENMETHOD( + meet, (virtual_, virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(meet, (Animal&, Animal&), std::string) { + return "ignore"; +} + +BOOST_OPENMETHOD_OVERRIDE(meet, (Carnivore&, Herbivore&), std::string) { + return "hunt"; +} + +} // namespace inheritance + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^inheritance, ^^inheritance::test_registry>); + +BOOST_AUTO_TEST_CASE(virtual_and_multiple_inheritance) { + using namespace inheritance; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((!registered(comp))); + + Herbivore herbivore; + Carnivore carnivore; + Omnivore omnivore; + + BOOST_TEST(meet(herbivore, herbivore) == "ignore"); + BOOST_TEST(meet(carnivore, herbivore) == "hunt"); + // Omnivore is both, and inherits the Carnivore/Herbivore overrider. + BOOST_TEST(meet(omnivore, omnivore) == "hunt"); +} + +// ============================================================================= +// Repeated inheritance is left out, not rejected +// +// `use_classes` rejects an ambiguous base at compile time, because naming one +// is a mistake in a hand-written list. Here the classes are collected +// mechanically, and a class that happens to have one must not break the build. + +namespace repeated_inheritance { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +struct Left : Animal {}; +struct Right : Animal {}; +// Animal is an ambiguous base: no conversion to it exists. +struct Repeated : Left, Right {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +} // namespace repeated_inheritance + +BOOST_OPENMETHOD_REGISTER( + register_classes< + ^^repeated_inheritance, ^^repeated_inheritance::test_registry>); + +BOOST_AUTO_TEST_CASE(repeated_inheritance_does_not_break_the_scan) { + using namespace repeated_inheritance; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + Dog dog; + Left left; + BOOST_TEST(poke(dog) == "bark"); + BOOST_TEST(poke(left) == "generic"); +} + +// ============================================================================= +// Nested namespaces, smart pointers, and covariant return types + +namespace nested { + +struct test_registry : test_registry_<__COUNTER__> {}; + +namespace shapes { + +struct Shape { + virtual ~Shape() = default; +}; + +namespace round { +struct Circle : Shape {}; +} // namespace round + +} // namespace shapes + +BOOST_OPENMETHOD( + name, (virtual_ptr, test_registry>), + std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE( + name, (virtual_ptr, test_registry>), + std::string) { + return "circle"; +} + +} // namespace nested + +BOOST_OPENMETHOD_REGISTER(register_classes<^^nested, ^^nested::test_registry>); + +BOOST_AUTO_TEST_CASE(nested_namespaces_and_smart_pointers) { + using namespace nested; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + std::shared_ptr circle = + std::make_shared(); + BOOST_TEST( + name( + virtual_ptr, test_registry>( + circle)) == "circle"); +} + +// ============================================================================= +// A base class nothing dispatches on is not registered +// +// Registering it would cost a class_info, a perfect-hash slot and dispatch table +// space, for a class no overrider can ever be selected on. + +namespace unused_bases { + +struct test_registry : test_registry_<__COUNTER__> {}; + +// Neither of these is a virtual parameter of any method below. +struct Serializable { + virtual ~Serializable() = default; +}; + +struct Named : Serializable {}; + +struct Animal : Named {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +} // namespace unused_bases + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^unused_bases, ^^unused_bases::test_registry>); + +BOOST_AUTO_TEST_CASE(bases_that_take_no_part_in_dispatch_are_left_out) { + using namespace unused_bases; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + // Above the only virtual parameter, so of no use to dispatch. + BOOST_TEST((!registered(comp))); + BOOST_TEST((!registered(comp))); + + Animal animal; + Dog dog; + BOOST_TEST(poke(animal) == "generic"); + BOOST_TEST(poke(dog) == "bark"); +} + +// ============================================================================= +// ... unless another method dispatches on it + +namespace shared_bases { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Serializable { + virtual ~Serializable() = default; +}; + +struct Named : Serializable {}; + +struct Animal : Named {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +// Named is a virtual parameter here, so it - and the lattice edges through it - +// must be registered after all. +BOOST_OPENMETHOD(label, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(label, (Named&), std::string) { + return "named"; +} + +BOOST_OPENMETHOD_OVERRIDE(label, (Dog&), std::string) { + return "dog"; +} + +} // namespace shared_bases + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^shared_bases, ^^shared_bases::test_registry>); + +BOOST_AUTO_TEST_CASE(a_base_another_method_dispatches_on_is_registered) { + using namespace shared_bases; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + // Still above every virtual parameter. + BOOST_TEST((!registered(comp))); + + Animal animal; + Dog dog; + BOOST_TEST(poke(dog) == "bark"); + // Dog reaches Named through Animal: the edges survive. + BOOST_TEST(label(dog) == "dog"); + BOOST_TEST(label(animal) == "named"); +} + +// ============================================================================= +// The recorded bases are the direct ones +// +// Reflection knows a class' direct bases, so the registry records those, not the +// whole ancestry. `initialize` derives the lattice from them either way; the +// point is to not ship, instantiate and store what it can work out for itself. + +namespace direct_bases { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct A { + virtual ~A() = default; +}; + +struct B : A {}; +struct C : B {}; +struct D : C {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (A&), std::string) { + return "A"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (C&), std::string) { + return "C"; +} + +} // namespace direct_bases + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^direct_bases, ^^direct_bases::test_registry>); + +BOOST_AUTO_TEST_CASE(recorded_bases_are_direct) { + using namespace direct_bases; + + auto comp = initialize(); + + // Each class_info names the class itself, as its own improper base, plus + // its direct bases - four entries for the whole chain, not ten. + std::size_t recorded = 0; + + for (auto iter = comp.classes_begin(); iter != comp.classes_end(); ++iter) { + recorded += iter->last_base - iter->first_base; + } + + BOOST_TEST(recorded == 7u); // A: 1, B/C/D: 2 each + + // The lattice initialize derives from them is still the full chain. + auto a = comp.class_map.at( + test_registry::rtti::type_index(test_registry::rtti::static_type())); + auto d = comp.class_map.at( + test_registry::rtti::type_index(test_registry::rtti::static_type())); + + BOOST_TEST(d->direct_bases.size() == 1u); + BOOST_TEST(d->transitive_bases.size() == 3u); + BOOST_TEST(a->transitive_derived.size() == 4u); + + A a_obj; + B b; + C c; + D d_obj; + BOOST_TEST(poke(a_obj) == "A"); + BOOST_TEST(poke(b) == "A"); + BOOST_TEST(poke(c) == "C"); + BOOST_TEST(poke(d_obj) == "C"); +} + +// ============================================================================= +// explicit_class_registration opts out + +namespace opted_out { + +struct test_registry : + test_registry_< + __COUNTER__, policies::explicit_class_registration, + policies::throw_error_handler> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), void, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { +} + +} // namespace opted_out + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^opted_out, ^^opted_out::test_registry>); + +BOOST_AUTO_TEST_CASE(explicit_class_registration_disables_the_scan) { + static_assert(!opted_out::test_registry::has_reflected_class_registration); + // Nothing was registered, so initialize cannot resolve the method's + // virtual parameter. + BOOST_CHECK_THROW(initialize(), missing_class); +} + +// ============================================================================= +// Several namespaces in one registration + +namespace two_namespaces { + +struct test_registry : test_registry_<__COUNTER__> {}; + +namespace zoo { + +struct Animal { + virtual ~Animal() = default; +}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +} // namespace zoo + +namespace pets { + +struct Dog : zoo::Animal {}; + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +} // namespace pets + +} // namespace two_namespaces + +BOOST_OPENMETHOD_REGISTER( + register_classes< + ^^two_namespaces::zoo, ^^two_namespaces::pets, + ^^two_namespaces::test_registry>); + +BOOST_AUTO_TEST_CASE(several_namespaces_in_one_registration) { + using namespace two_namespaces; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + pets::Dog dog; + BOOST_TEST(zoo::poke(dog) == "bark"); +} + +// ============================================================================= +// With no namespace and no class, the enclosing namespace is scanned + +namespace enclosing_macro { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +// Only the registry is named; the macro captures the enclosing namespace. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^enclosing_macro::test_registry); + +} // namespace enclosing_macro + +BOOST_AUTO_TEST_CASE(macro_scans_the_enclosing_namespace) { + using namespace enclosing_macro; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + Dog dog; + BOOST_TEST(poke(dog) == "bark"); +} + +namespace enclosing_helper { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +// The bare class template cannot capture the enclosing namespace on its own; +// `current_namespace()` passes it explicitly. +BOOST_OPENMETHOD_REGISTER( + register_classes); + +} // namespace enclosing_helper + +BOOST_AUTO_TEST_CASE(current_namespace_names_the_enclosing_namespace) { + using namespace enclosing_helper; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + Dog dog; + BOOST_TEST(poke(dog) == "bark"); +} + +// ============================================================================= +// Classes without a namespace: no scan, exactly the listed classes + +namespace classes_only { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +} // namespace classes_only + +// `Dog` is left out on purpose: only the listed classes are registered, and +// the inheritance lattice is flattened over the gap - `Bulldog`'s recorded +// base is `Animal`. +BOOST_OPENMETHOD_REGISTER( + register_classes< + ^^classes_only::Animal, ^^classes_only::Bulldog, + ^^classes_only::test_registry>); + +BOOST_AUTO_TEST_CASE(listed_classes_without_a_namespace_disable_the_scan) { + using namespace classes_only; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((!registered(comp))); + + // The flattened lattice records `Animal` as `Bulldog`'s direct base. + auto bulldog = comp.class_map.at( + test_registry::rtti::type_index( + test_registry::rtti::static_type())); + BOOST_TEST(bulldog->direct_bases.size() == 1u); + + // The edge is live: the overrider for `Animal` applies to `Bulldog`. + Bulldog snoopy; + BOOST_TEST(poke(snoopy) == "generic"); +} + +// ============================================================================= +// A listed class is a root for the scan + +namespace listed_root { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +// No method dispatches on `Tool`; only listing it registers it - along with +// `Hammer`, which the scan finds derives from it. +struct Tool { + virtual ~Tool() = default; +}; + +struct Hammer : Tool {}; + +// Unrelated to any root: must be left alone, as always. +struct Fence { + virtual ~Fence() = default; +}; + +} // namespace listed_root + +BOOST_OPENMETHOD_REGISTER( + register_classes< + ^^listed_root, ^^listed_root::Tool, ^^listed_root::test_registry>); + +BOOST_AUTO_TEST_CASE(a_listed_class_roots_the_scan) { + using namespace listed_root; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((!registered(comp))); +} + +// ============================================================================= +// no_recurse stops at the listed namespaces + +namespace no_recurse { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +namespace kennel { + +struct Bulldog : Dog {}; + +} // namespace kennel + +} // namespace no_recurse + +BOOST_OPENMETHOD_REGISTER( + register_classes< + ^^no_recurse, register_classes_opts::no_recurse, + ^^no_recurse::test_registry>); + +BOOST_AUTO_TEST_CASE(no_recurse_skips_nested_namespaces) { + using namespace no_recurse; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + BOOST_TEST((!registered(comp))); +} + +// ============================================================================= +// boost is skipped by default; scan_boost brings it back in + +namespace boost_gate { + +struct default_registry_ : test_registry_<__COUNTER__> {}; +struct scan_boost_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, default_registry_); +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +BOOST_OPENMETHOD(poke2, (virtual_), std::string, scan_boost_registry); +BOOST_OPENMETHOD_OVERRIDE(poke2, (Animal&), std::string) { + return "generic"; +} + +} // namespace boost_gate + +namespace boost::om_reflection_test { + +struct Stray : boost_gate::Animal {}; + +} // namespace boost::om_reflection_test + +BOOST_OPENMETHOD_REGISTER( + register_classes<^^::, ^^boost_gate::default_registry_>); + +BOOST_OPENMETHOD_REGISTER( + register_classes< + ^^::, register_classes_opts::scan_boost, + ^^boost_gate::scan_boost_registry>); + +BOOST_AUTO_TEST_CASE(boost_is_scanned_only_on_request) { + using namespace boost_gate; + using boost::om_reflection_test::Stray; + + auto default_comp = initialize(); + BOOST_TEST((registered(default_comp))); + BOOST_TEST((!registered(default_comp))); + + auto scan_boost_comp = initialize(); + BOOST_TEST((registered(scan_boost_comp))); + BOOST_TEST((registered(scan_boost_comp))); +} + +// ============================================================================= +// Several registries in one registration + +namespace two_registries { + +struct first_registry : test_registry_<__COUNTER__> {}; +struct second_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, first_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { + return "bark"; +} + +} // namespace two_registries + +BOOST_OPENMETHOD_REGISTER( + register_classes< + ^^two_registries::Animal, ^^two_registries::Dog, + ^^two_registries::first_registry, ^^two_registries::second_registry>); + +BOOST_AUTO_TEST_CASE(several_registries_in_one_registration) { + using namespace two_registries; + + auto first_comp = initialize(); + BOOST_TEST((registered(first_comp))); + BOOST_TEST((registered(first_comp))); + + auto second_comp = initialize(); + BOOST_TEST((registered(second_comp))); + BOOST_TEST((registered(second_comp))); + + Dog dog; + BOOST_TEST(poke(dog) == "bark"); +} + +#endif diff --git a/test/test_rolex.cpp b/test/test_rolex.cpp index 4bef424b..1723fc40 100644 --- a/test/test_rolex.cpp +++ b/test/test_rolex.cpp @@ -6,6 +6,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE test_rolex #include @@ -37,7 +39,7 @@ struct Metro : Public {}; struct Taxi : Expense {}; struct PrivateJet : Expense {}; -BOOST_OPENMETHOD_CLASSES( +BOOST_OPENMETHOD_TEST_CLASSES( Role, Employee, Manager, Founder, Expense, Public, Bus, Metro, Taxi, PrivateJet); @@ -184,3 +186,7 @@ BOOST_AUTO_TEST_CASE(approve_via_wrapper) { BOOST_TEST(call_approve(m, taxi, 10) == true); BOOST_TEST(call_approve(f, taxi, 10) == true); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_bad_call.cpp b/test/test_runtime_errors_bad_call.cpp index 1cb696d1..c698ea14 100644 --- a/test/test_runtime_errors_bad_call.cpp +++ b/test/test_runtime_errors_bad_call.cpp @@ -17,7 +17,7 @@ using namespace test_matrices; using capture = capture_errors; -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); +BOOST_OPENMETHOD_TEST_CLASSES(matrix, dense_matrix, diagonal_matrix); BOOST_OPENMETHOD( times, (virtual_ptr, virtual_ptr), void); @@ -53,3 +53,7 @@ BOOST_AUTO_TEST_CASE(bad_calls) { BOOST_TEST(capture().find("ambiguous") != std::string::npos); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_bad_call_type_ids.cpp b/test/test_runtime_errors_bad_call_type_ids.cpp index 00df1707..5bc6e77e 100644 --- a/test/test_runtime_errors_bad_call_type_ids.cpp +++ b/test/test_runtime_errors_bad_call_type_ids.cpp @@ -17,7 +17,7 @@ using namespace test_matrices; using capture = capture_errors; -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); +BOOST_OPENMETHOD_TEST_CLASSES(matrix, dense_matrix, diagonal_matrix); BOOST_OPENMETHOD( times, (virtual_ptr, virtual_ptr), void); @@ -48,3 +48,7 @@ BOOST_AUTO_TEST_CASE(bad_call_type_ids) { BOOST_FAIL("wrong exception"); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp b/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp index 3bbe4292..0f2e2c56 100644 --- a/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp +++ b/test/test_runtime_errors_bad_call_type_ids_smart_ptr.cpp @@ -18,7 +18,7 @@ using namespace test_matrices; using capture = capture_errors; -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); +BOOST_OPENMETHOD_TEST_CLASSES(matrix, dense_matrix, diagonal_matrix); BOOST_OPENMETHOD( times, (shared_virtual_ptr, shared_virtual_ptr), @@ -41,3 +41,7 @@ BOOST_AUTO_TEST_CASE(bad_call_type_ids_smart_ptr) { BOOST_FAIL("wrong exception"); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_duplicate_overrider.cpp b/test/test_runtime_errors_duplicate_overrider.cpp index f387bf7f..4559b9a7 100644 --- a/test/test_runtime_errors_duplicate_overrider.cpp +++ b/test/test_runtime_errors_duplicate_overrider.cpp @@ -7,6 +7,8 @@ #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE runtime_errors_duplicate_overrider #include @@ -21,7 +23,7 @@ struct Animal { struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (virtual_ptr), const char*); @@ -58,3 +60,7 @@ BOOST_AUTO_TEST_CASE(duplicate_overrider_is_ambiguous) { BOOST_CHECK_THROW(poke(dog), ambiguous_call); BOOST_TEST(capture().find("ambiguous") != std::string::npos); } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_initialize_unknown_class.cpp b/test/test_runtime_errors_initialize_unknown_class.cpp index 1dc50856..cb3e4f92 100644 --- a/test/test_runtime_errors_initialize_unknown_class.cpp +++ b/test/test_runtime_errors_initialize_unknown_class.cpp @@ -3,6 +3,10 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +// This test is *about* a class that is not registered, so the library must not +// register it by reflection. +#define BOOST_OPENMETHOD_TEST_EXPLICIT_CLASS_REGISTRATION + #include "test_capture_errors.hpp" #include diff --git a/test/test_runtime_errors_throw_error.cpp b/test/test_runtime_errors_throw_error.cpp index 81d9ea33..65b8ed7a 100644 --- a/test/test_runtime_errors_throw_error.cpp +++ b/test/test_runtime_errors_throw_error.cpp @@ -10,8 +10,9 @@ struct test_registry; #include #include -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::throw_error_handler> {}; +struct test_registry : + boost::openmethod::default_registry::with< + boost::openmethod::policies::throw_error_handler> {}; #include "test_util.hpp" @@ -21,7 +22,7 @@ struct test_registry : boost::openmethod::default_registry::with< using namespace boost::openmethod; using namespace test_matrices; -BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); +BOOST_OPENMETHOD_TEST_CLASSES(matrix, dense_matrix, diagonal_matrix); BOOST_OPENMETHOD( times, (virtual_, virtual_), void); @@ -40,3 +41,7 @@ BOOST_AUTO_TEST_CASE(throw_error) { BOOST_FAIL("wrong exception"); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_shared_virtual_ptr_dispatch.cpp b/test/test_shared_virtual_ptr_dispatch.cpp index 8dc1cfb5..f59ff804 100644 --- a/test/test_shared_virtual_ptr_dispatch.cpp +++ b/test/test_shared_virtual_ptr_dispatch.cpp @@ -30,8 +30,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( BOOST_OPENMETHOD_ID(poke), auto(shared_virtual_ptr)->std::string, Registry>; - BOOST_OPENMETHOD_REGISTER(typename poke::template override< - poke_bear>>); + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + poke_bear>>); using fight = method< BOOST_OPENMETHOD_ID(fight), @@ -42,10 +43,11 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( ->std::string, Registry>; - BOOST_OPENMETHOD_REGISTER(typename fight::template override, - shared_virtual_ptr, - shared_virtual_ptr>>); + BOOST_OPENMETHOD_REGISTER( + typename fight::template override, + shared_virtual_ptr, + shared_virtual_ptr>>); initialize(); diff --git a/test/test_smart_virtual_ptr_value_semantics.cpp b/test/test_smart_virtual_ptr_value_semantics.cpp index 528bfcda..fa6109b7 100644 --- a/test/test_smart_virtual_ptr_value_semantics.cpp +++ b/test/test_smart_virtual_ptr_value_semantics.cpp @@ -443,3 +443,7 @@ template struct check_illegal_smart_ops< template struct check_illegal_smart_ops< boost::intrusive_ptr, std::unique_ptr, direct_vector>; + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_static_rtti.cpp b/test/test_static_rtti.cpp index 536a03dd..a4de824f 100644 --- a/test/test_static_rtti.cpp +++ b/test/test_static_rtti.cpp @@ -11,12 +11,14 @@ struct static_registry; #include #include -struct static_registry - : boost::openmethod::registry {}; +struct static_registry : + boost::openmethod::registry {}; #define BOOST_TEST_MODULE openmethod #include +#include "test_classes.hpp" + struct Animal {}; struct Dog : Animal {}; @@ -25,7 +27,7 @@ struct Cat : Animal {}; using namespace boost::openmethod::aliases; -BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); @@ -54,3 +56,7 @@ BOOST_AUTO_TEST_CASE(static_rtti) { BOOST_TEST(os.str() == "bark"); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_type_erasure_static_rtti.cpp b/test/test_type_erasure_static_rtti.cpp index dafcd68a..f7b1b38e 100644 --- a/test/test_type_erasure_static_rtti.cpp +++ b/test/test_type_erasure_static_rtti.cpp @@ -36,9 +36,10 @@ struct Cat { std::string name; }; -struct Dispatchable : boost::mpl::vector< - te::copy_constructible<>, te::relaxed, - openmethod_vptr> {}; +struct Dispatchable : + boost::mpl::vector< + te::copy_constructible<>, te::relaxed, + openmethod_vptr> {}; using erased = te::any; diff --git a/test/test_util.hpp b/test/test_util.hpp index ab428b22..824301bb 100644 --- a/test/test_util.hpp +++ b/test/test_util.hpp @@ -13,6 +13,8 @@ #include #include +#include "test_classes.hpp" + struct unique_category { using category = unique_category; }; @@ -24,14 +26,14 @@ struct unique final : unique_category { }; template -struct test_registry_ - : boost::openmethod::default_registry::with, Policies...> {}; +struct test_registry_ : + boost::openmethod::default_registry::with, Policies...> {}; #define TEST_NS BOOST_PP_CAT(test, __COUNTER__) struct capture_cout { - capture_cout(std::streambuf* new_buffer) - : old(std::cout.rdbuf(new_buffer)) { + capture_cout(std::streambuf* new_buffer) : + old(std::cout.rdbuf(new_buffer)) { } ~capture_cout() { @@ -113,9 +115,9 @@ auto fight_bear(VirtualWarriorPtr, VirtualAxePtr, VirtualBearPtr) { } template -struct indirect_test_registry - : test_registry_::template with< - boost::openmethod::policies::indirect_vptr> {}; +struct indirect_test_registry : + test_registry_::template with< + boost::openmethod::policies::indirect_vptr> {}; template using policy_types = diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp index 3672cedb..5e9a6c29 100644 --- a/test/test_virtual_any_boost.cpp +++ b/test/test_virtual_any_boost.cpp @@ -12,6 +12,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE openmethod #include @@ -261,9 +263,9 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // a type that is never named statically anywhere is not registered -struct throw_registry - : default_registry::with< - policies::runtime_checks, policies::throw_error_handler> {}; +struct throw_registry : + default_registry::with< + policies::runtime_checks, policies::throw_error_handler> {}; struct Dog { std::string name; @@ -308,7 +310,7 @@ struct Animal { struct Cat : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Cat); BOOST_OPENMETHOD( meet, (const virtual_boost_any&, virtual_ptr), std::string); @@ -334,3 +336,7 @@ BOOST_AUTO_TEST_CASE(virtual_any_mixed_with_virtual_ptr) { BOOST_TEST(meet(pi, felix) == "someone meets an animal"); } } // namespace BOOST_OPENMETHOD_GENSYM + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp index bc96172f..eaf91c0b 100644 --- a/test/test_virtual_any_std.cpp +++ b/test/test_virtual_any_std.cpp @@ -12,6 +12,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE openmethod #include @@ -261,9 +263,9 @@ namespace BOOST_OPENMETHOD_GENSYM { // ----------------------------------------------------------------------------- // a type that is never named statically anywhere is not registered -struct throw_registry - : default_registry::with< - policies::runtime_checks, policies::throw_error_handler> {}; +struct throw_registry : + default_registry::with< + policies::runtime_checks, policies::throw_error_handler> {}; struct Dog { std::string name; @@ -308,7 +310,7 @@ struct Animal { struct Cat : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Cat); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Cat); BOOST_OPENMETHOD( meet, (const virtual_std_any&, virtual_ptr), std::string); @@ -334,3 +336,7 @@ BOOST_AUTO_TEST_CASE(virtual_any_mixed_with_virtual_ptr) { BOOST_TEST(meet(pi, felix) == "someone meets an animal"); } } // namespace BOOST_OPENMETHOD_GENSYM + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_by_ref.cpp b/test/test_virtual_ptr_by_ref.cpp index 83a030a8..19d55d29 100644 --- a/test/test_virtual_ptr_by_ref.cpp +++ b/test/test_virtual_ptr_by_ref.cpp @@ -6,6 +6,8 @@ #include #include +#include "test_classes.hpp" + #include #define BOOST_TEST_MODULE virtual_ptr_by_ref @@ -21,7 +23,7 @@ struct Animal { struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (const virtual_ptr&, std::ostream&), void); @@ -60,3 +62,7 @@ BOOST_AUTO_TEST_CASE(test_virtual_ptr_by_ref) { BOOST_CHECK(os.is_equal("bark")); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_dispatch.cpp b/test/test_virtual_ptr_dispatch.cpp index 70802b24..eb3a8455 100644 --- a/test/test_virtual_ptr_dispatch.cpp +++ b/test/test_virtual_ptr_dispatch.cpp @@ -28,8 +28,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( using poke = method< BOOST_OPENMETHOD_ID(poke), auto(virtual_ptr)->std::string, Registry>; - BOOST_OPENMETHOD_REGISTER(typename poke::template override< - poke_bear>>); + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + poke_bear>>); using fight = method< BOOST_OPENMETHOD_ID(fight), diff --git a/test/test_virtual_ptr_final.cpp b/test/test_virtual_ptr_final.cpp index 56f67708..29be3c63 100644 --- a/test/test_virtual_ptr_final.cpp +++ b/test/test_virtual_ptr_final.cpp @@ -26,8 +26,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( using poke = method< BOOST_OPENMETHOD_ID(poke), auto(virtual_ptr)->std::string, Registry>; - BOOST_OPENMETHOD_REGISTER(typename poke::template override< - poke_bear>>); + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + poke_bear>>); initialize(); diff --git a/test/test_virtual_ptr_non_polymorphic.cpp b/test/test_virtual_ptr_non_polymorphic.cpp index 932cd0a9..d9395a1d 100644 --- a/test/test_virtual_ptr_non_polymorphic.cpp +++ b/test/test_virtual_ptr_non_polymorphic.cpp @@ -6,6 +6,8 @@ #include #include +#include "test_classes.hpp" + #include #define BOOST_TEST_MODULE virtual_ptr_non_polymorphic @@ -18,7 +20,7 @@ struct Animal {}; struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); @@ -37,3 +39,7 @@ BOOST_AUTO_TEST_CASE(test_virtual_ptr_non_polymorphic) { BOOST_CHECK(os.is_equal("bark")); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_shared_by_const_ref.cpp b/test/test_virtual_ptr_shared_by_const_ref.cpp index a902d983..1b2c7243 100644 --- a/test/test_virtual_ptr_shared_by_const_ref.cpp +++ b/test/test_virtual_ptr_shared_by_const_ref.cpp @@ -7,6 +7,8 @@ #include #include +#include "test_classes.hpp" + #include #define BOOST_TEST_MODULE virtual_ptr_shared_by_const_ref @@ -22,7 +24,7 @@ struct Animal { struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD( poke, (const shared_virtual_ptr&, std::ostream&), void); @@ -42,3 +44,7 @@ BOOST_AUTO_TEST_CASE(test_virtual_shared_by_const_reference) { BOOST_CHECK(os.is_equal("bark")); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_shared_by_value.cpp b/test/test_virtual_ptr_shared_by_value.cpp index 66db0f93..1ea10bf5 100644 --- a/test/test_virtual_ptr_shared_by_value.cpp +++ b/test/test_virtual_ptr_shared_by_value.cpp @@ -7,6 +7,8 @@ #include #include +#include "test_classes.hpp" + #include #define BOOST_TEST_MODULE virtual_ptr_shared_by_value @@ -22,7 +24,7 @@ struct Animal { struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); @@ -40,3 +42,7 @@ BOOST_AUTO_TEST_CASE(test_virtual_shared_by_value) { BOOST_CHECK(os.is_equal("bark")); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_unique.cpp b/test/test_virtual_ptr_unique.cpp index 73dba04a..726d59b4 100644 --- a/test/test_virtual_ptr_unique.cpp +++ b/test/test_virtual_ptr_unique.cpp @@ -7,6 +7,8 @@ #include #include +#include "test_classes.hpp" + #include #define BOOST_TEST_MODULE virtual_ptr_unique @@ -22,7 +24,7 @@ struct Animal { struct Dog : Animal {}; -BOOST_OPENMETHOD_CLASSES(Animal, Dog); +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog); BOOST_OPENMETHOD(poke, (unique_virtual_ptr, std::ostream&), void); @@ -40,3 +42,7 @@ BOOST_AUTO_TEST_CASE(test_virtual_unique) { BOOST_CHECK(os.is_equal("bark")); } } + +// Registers the classes above by reflection, when the compiler supports it. +// Must come last: reflection sees only what precedes it. +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_value_semantics.cpp b/test/test_virtual_ptr_value_semantics.cpp index c4c5a1dd..01e9c33a 100644 --- a/test/test_virtual_ptr_value_semantics.cpp +++ b/test/test_virtual_ptr_value_semantics.cpp @@ -274,7 +274,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(indirect_virtual_ptr, Registry, test_policies) { // Add a class, to make sure dispatch data is not re-constructed in the same // place with the same values: struct Cat : Animal {}; - BOOST_OPENMETHOD_CLASSES(Animal, Cat, Registry); + BOOST_OPENMETHOD_TEST_CLASSES(Animal, Cat, Registry); init_test(); diff --git a/test/test_virtual_ptr_value_semantics.hpp b/test/test_virtual_ptr_value_semantics.hpp index 0f630c57..b6bf1818 100644 --- a/test/test_virtual_ptr_value_semantics.hpp +++ b/test/test_virtual_ptr_value_semantics.hpp @@ -37,6 +37,9 @@ struct Cat : virtual Animal {}; struct Dog : Animal {}; +// These tests exercise virtual_ptr itself and declare no method, so there is no +// virtual parameter for reflection to start from: the classes are registered by +// hand under every standard. BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); struct id; @@ -62,8 +65,8 @@ struct direct_vector : test_registry_<__COUNTER__> {}; struct indirect_vector : test_registry_<__COUNTER__>::with {}; -struct direct_map - : test_registry_<__COUNTER__>::with>::without {}; +struct direct_map : + test_registry_<__COUNTER__>::with>::without {}; struct indirect_map : direct_map::with {};