From 6b029785f1f04c13e2150428d8a127ba1b520848 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 14:42:09 -0400 Subject: [PATCH 1/8] chore: adopt clang-format 22, tune two layout choices Two things the formatter was getting wrong, and the version bump that one of them needs. `TemplateNames` - added in clang-format 20 - tells the formatter that `register_classes<` opens a template argument list rather than a comparison, so a registration inside a function-like macro call breaks after the open parenthesis instead of aligning at it. `PenaltyReturnTypeOnItsOwnLine` goes from 60 to 200, so a declaration too long for one line breaks its parameter list rather than putting `auto` on a line of its own: static auto initialize( const Context& ctx, const std::tuple& options) -> void; rather than static auto initialize(const Context& ctx, const std::tuple& options) -> void; 200 is well past the threshold - the layout stops changing above 120 - and gives what 1000 gives, so the choice is not on a knife edge. The rest of the diff is the drift from clang-format 18 to 22: trailing return types and `if constexpr` continuations, mostly. `.clang-format` now requires clang-format 20 or later, which rejects no key in this file; 22 reproduces the tree byte for byte. `register_classes` itself arrives with the reflection branch; naming it here only teaches the formatter, and costs nothing. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- .clang-format | 4 +- .../ROOT/examples/accept_no_visitors.cpp | 6 ++- doc/modules/ROOT/examples/inplace_vptr.cpp | 3 +- doc/modules/ROOT/examples/matrix_readme.cpp | 9 ++-- doc/modules/ROOT/examples/rolex/1/main.cpp | 5 +- doc/modules/ROOT/examples/rolex/1/roles.hpp | 4 +- doc/modules/ROOT/examples/rolex/2/main.cpp | 5 +- doc/modules/ROOT/examples/rolex/2/roles.hpp | 4 +- doc/modules/ROOT/examples/rolex/3/main.cpp | 5 +- doc/modules/ROOT/examples/rolex/3/roles.hpp | 4 +- doc/modules/ROOT/examples/rolex/4/main.cpp | 5 +- doc/modules/ROOT/examples/rolex/4/roles.hpp | 2 +- .../ROOT/examples/rolex/4/salesman.cpp | 3 +- doc/modules/ROOT/examples/rolex/5/main.cpp | 7 +-- .../virtual_ptr_alt/1/virtual_ptr_alt.cpp | 30 ++++++++---- .../virtual_ptr_alt/3/virtual_ptr_alt.cpp | 15 +++--- include/boost/openmethod/core.hpp | 48 +++++++++---------- .../boost/openmethod/detail/ostdstream.hpp | 4 +- .../boost/openmethod/detail/static_list.hpp | 8 ++-- include/boost/openmethod/initialize.hpp | 11 ++--- include/boost/openmethod/inplace_vptr.hpp | 16 +++---- .../boost/openmethod/interop/boost_any.hpp | 18 +++---- .../interop/boost_intrusive_ptr.hpp | 8 ++-- .../openmethod/interop/boost_type_erasure.hpp | 48 +++++++++---------- include/boost/openmethod/interop/std_any.hpp | 18 +++---- .../openmethod/interop/std_shared_ptr.hpp | 9 ++-- .../boost/openmethod/interop/virtual_any.hpp | 33 +++++++------ .../boost/openmethod/policies/vptr_map.hpp | 4 +- test/dynamic_loading/main.cpp | 4 +- test/dynamic_loading/method.cpp | 4 +- test/dynamic_loading/overrider.cpp | 4 +- test/test_core.cpp | 4 +- test/test_dispatch_comma_in_return_type.cpp | 2 +- test/test_shared_virtual_ptr_dispatch.cpp | 14 +++--- test/test_virtual_ptr_dispatch.cpp | 5 +- test/test_virtual_ptr_final.cpp | 5 +- 36 files changed, 208 insertions(+), 170 deletions(-) diff --git a/.clang-format b/.clang-format index b26254bd..88b6b2c7 100644 --- a/.clang-format +++ b/.clang-format @@ -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/doc/modules/ROOT/examples/accept_no_visitors.cpp b/doc/modules/ROOT/examples/accept_no_visitors.cpp index 4bcc656b..41b1756e 100644 --- a/doc/modules/ROOT/examples/accept_no_visitors.cpp +++ b/doc/modules/ROOT/examples/accept_no_visitors.cpp @@ -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_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/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..c4bade75 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,27 @@ #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/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 0ffdafc1..80a120a5 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -660,9 +660,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); @@ -1766,8 +1765,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 +1811,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>(); } @@ -2239,8 +2238,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,15 +2266,15 @@ 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>; @@ -2386,8 +2385,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 +2426,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 +2441,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 +2461,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 +2519,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; } 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/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..c8108bac 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -568,8 +568,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; @@ -1757,10 +1757,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..afbbac43 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -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..bf5bf52b 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -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..4d167e79 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -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..f645d0a6 100644 --- a/include/boost/openmethod/interop/std_shared_ptr.hpp +++ b/include/boost/openmethod/interop/std_shared_ptr.hpp @@ -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..3ffb707d 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -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>; 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/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/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_dispatch_comma_in_return_type.cpp b/test/test_dispatch_comma_in_return_type.cpp index 34be736f..c4fb6d3a 100644 --- a/test/test_dispatch_comma_in_return_type.cpp +++ b/test/test_dispatch_comma_in_return_type.cpp @@ -14,7 +14,7 @@ using namespace boost::openmethod; struct Test { - virtual ~Test(){}; + virtual ~Test() {}; }; BOOST_OPENMETHOD_CLASSES(Test); 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_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(); From a5f8f65946aff4ee8d9a87de8a357dc2f8963ea2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 15:09:12 -0400 Subject: [PATCH 2/8] chore: put the colon at the end of the line `BreakConstructorInitializers` and `BreakInheritanceList` go from BeforeColon to AfterColon, so a member initializer list and a base clause trail their colon rather than lead it, and their continuations sit on a flat +4 ladder instead of aligning two columns past the colon: explicit virtual_ptr(std::nullptr_t) : vp(detail::box_vptr(detail::null_vptr)), obj(nullptr) { rather than explicit virtual_ptr(std::nullptr_t) : vp(detail::box_vptr(detail::null_vptr)), obj(nullptr) { Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- .clang-format | 4 +- .../ROOT/examples/accept_no_visitors.cpp | 8 +- doc/modules/ROOT/examples/matrix.cpp | 4 +- .../ROOT/examples/throw_error_handler.cpp | 4 +- .../virtual_ptr_alt/3/virtual_ptr_alt.cpp | 5 +- doc/modules/ROOT/snippets/policies.cpp | 47 ++--- doc/modules/ROOT/snippets/static_rtti.cpp | 4 +- .../ROOT/snippets/type_erasure_concept.cpp | 7 +- include/boost/openmethod/core.hpp | 176 +++++++++--------- include/boost/openmethod/default_registry.hpp | 15 +- include/boost/openmethod/initialize.hpp | 20 +- .../boost/openmethod/interop/boost_any.hpp | 12 +- .../openmethod/interop/boost_type_erasure.hpp | 20 +- include/boost/openmethod/interop/std_any.hpp | 12 +- .../openmethod/interop/std_shared_ptr.hpp | 8 +- .../boost/openmethod/interop/virtual_any.hpp | 72 +++---- .../policies/default_error_handler.hpp | 21 ++- .../policies/throw_error_handler.hpp | 4 +- include/boost/openmethod/preamble.hpp | 4 +- test/compile_fail_std_any_custom_rtti.cpp | 4 +- .../compile_fail_type_erasure_custom_rtti.cpp | 4 +- ..._fail_virtual_ptr_different_registries.cpp | 5 +- .../custom_registry/registry.hpp | 7 +- test/test_capture_errors.hpp | 4 +- test/test_checked_registry.hpp | 7 +- test/test_custom_rtti_deferred.cpp | 6 +- test/test_custom_rtti_simple.cpp | 6 +- test/test_custom_rtti_simple_projection.cpp | 4 +- test/test_custom_rtti_virtual_base.cpp | 6 +- test/test_dispatch_boost_any.cpp | 6 +- test/test_dispatch_std_any.cpp | 6 +- test/test_dispatch_type_erasure.cpp | 14 +- test/test_inplace_vptr.cpp | 12 +- test/test_runtime_errors_throw_error.cpp | 5 +- test/test_static_rtti.cpp | 4 +- test/test_type_erasure_static_rtti.cpp | 7 +- test/test_util.hpp | 14 +- test/test_virtual_any_boost.cpp | 6 +- test/test_virtual_any_std.cpp | 6 +- test/test_virtual_ptr_value_semantics.hpp | 4 +- 40 files changed, 297 insertions(+), 287 deletions(-) diff --git a/.clang-format b/.clang-format index 88b6b2c7..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 diff --git a/doc/modules/ROOT/examples/accept_no_visitors.cpp b/doc/modules/ROOT/examples/accept_no_visitors.cpp index 41b1756e..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; 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/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/3/virtual_ptr_alt.cpp b/doc/modules/ROOT/examples/virtual_ptr_alt/3/virtual_ptr_alt.cpp index c4bade75..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 @@ -10,8 +10,9 @@ // tag::content[] struct Node : boost::openmethod::inplace_vptr_base {}; -struct Variable : Node, - boost::openmethod::inplace_vptr_derived { +struct Variable : + Node, + boost::openmethod::inplace_vptr_derived { Variable(int value) : v(value) { } 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 80a120a5..07c9bd8d 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -210,8 +210,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 +424,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; @@ -550,8 +550,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 +560,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 +622,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; @@ -871,9 +871,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 @@ -907,10 +907,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 @@ -946,10 +946,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` @@ -980,8 +980,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 @@ -1212,8 +1212,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: @@ -1227,8 +1227,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` @@ -1240,17 +1240,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 @@ -1283,11 +1282,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 @@ -1312,11 +1311,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 @@ -1348,11 +1347,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 @@ -1376,8 +1375,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 @@ -1409,11 +1408,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` @@ -1912,12 +1910,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 @@ -1933,10 +1931,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 " @@ -1944,8 +1942,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< @@ -2059,8 +2057,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 @@ -2281,10 +2279,10 @@ class method }; 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(); @@ -2598,8 +2596,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 +2611,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 +2635,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>; 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/initialize.hpp b/include/boost/openmethod/initialize.hpp index c8108bac..f5cde9fe 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -258,8 +258,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 +348,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 +362,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 +405,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; @@ -652,8 +652,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; diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index afbbac43..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 diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index bf5bf52b..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< diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index 4d167e79..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 diff --git a/include/boost/openmethod/interop/std_shared_ptr.hpp b/include/boost/openmethod/interop/std_shared_ptr.hpp index f645d0a6..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"); diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 3ffb707d..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); @@ -526,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< @@ -546,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"); }; @@ -576,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/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/preamble.hpp b/include/boost/openmethod/preamble.hpp index 0d53ee71..79f21071 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -1059,8 +1059,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 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/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..a63cbc0c 100644 --- a/test/test_capture_errors.hpp +++ b/test/test_capture_errors.hpp @@ -29,8 +29,8 @@ struct capture_output : boost::openmethod::policies::output { }; }; -struct test_registry - : boost::openmethod::default_registry::with {}; +struct test_registry : + boost::openmethod::default_registry::with {}; template struct capture_errors { diff --git a/test/test_checked_registry.hpp b/test/test_checked_registry.hpp index b9a31c10..e29b132f 100644 --- a/test/test_checked_registry.hpp +++ b/test/test_checked_registry.hpp @@ -21,8 +21,9 @@ struct test_registry; #include #include -struct test_registry : boost::openmethod::default_registry::with< - boost::openmethod::policies::runtime_checks, - boost::openmethod::policies::throw_error_handler> {}; +struct test_registry : + boost::openmethod::default_registry::with< + boost::openmethod::policies::runtime_checks, + boost::openmethod::policies::throw_error_handler> {}; #endif diff --git a/test/test_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp index e56a2de2..f8867a1b 100644 --- a/test/test_custom_rtti_deferred.cpp +++ b/test/test_custom_rtti_deferred.cpp @@ -144,9 +144,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 diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp index 98f70647..e2152d79 100644 --- a/test/test_custom_rtti_simple.cpp +++ b/test/test_custom_rtti_simple.cpp @@ -90,9 +90,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 diff --git a/test/test_custom_rtti_simple_projection.cpp b/test/test_custom_rtti_simple_projection.cpp index d2f06b3b..d109bf66 100644 --- a/test/test_custom_rtti_simple_projection.cpp +++ b/test/test_custom_rtti_simple_projection.cpp @@ -77,8 +77,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 diff --git a/test/test_custom_rtti_virtual_base.cpp b/test/test_custom_rtti_virtual_base.cpp index 0ccb51b9..5497eaa0 100644 --- a/test/test_custom_rtti_virtual_base.cpp +++ b/test/test_custom_rtti_virtual_base.cpp @@ -115,9 +115,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 diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index b3bf13b0..0a66094a 100644 --- a/test/test_dispatch_boost_any.cpp +++ b/test/test_dispatch_boost_any.cpp @@ -325,9 +325,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; diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 05b68ba4..6b82dbe2 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -322,9 +322,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; 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_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_runtime_errors_throw_error.cpp b/test/test_runtime_errors_throw_error.cpp index 81d9ea33..6835a806 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" diff --git a/test/test_static_rtti.cpp b/test/test_static_rtti.cpp index 536a03dd..d07a524e 100644 --- a/test/test_static_rtti.cpp +++ b/test/test_static_rtti.cpp @@ -11,8 +11,8 @@ struct static_registry; #include #include -struct static_registry - : boost::openmethod::registry {}; +struct static_registry : + boost::openmethod::registry {}; #define BOOST_TEST_MODULE openmethod #include 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..28699932 100644 --- a/test/test_util.hpp +++ b/test/test_util.hpp @@ -24,14 +24,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 +113,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..5382dabf 100644 --- a/test/test_virtual_any_boost.cpp +++ b/test/test_virtual_any_boost.cpp @@ -261,9 +261,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; diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp index bc96172f..76fefc2c 100644 --- a/test/test_virtual_any_std.cpp +++ b/test/test_virtual_any_std.cpp @@ -261,9 +261,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; diff --git a/test/test_virtual_ptr_value_semantics.hpp b/test/test_virtual_ptr_value_semantics.hpp index 0f630c57..cacf4191 100644 --- a/test/test_virtual_ptr_value_semantics.hpp +++ b/test/test_virtual_ptr_value_semantics.hpp @@ -62,8 +62,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 {}; From d05496c1d938bd65199f5daa2a539a5edf96010f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 24 Aug 2026 16:46:37 -0400 Subject: [PATCH 3/8] register classes using reflection if c++26 Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 35 ++ CMakeLists.txt | 75 +++ doc/modules/ROOT/examples/CMakeLists.txt | 2 + doc/modules/ROOT/pages/basics.adoc | 92 +++ doc/modules/ROOT/pages/core_api.adoc | 16 + doc/modules/ROOT/pages/ref_macros.adoc | 2 + .../ROOT/pages/registries_and_policies.adoc | 9 + doc/modules/ROOT/snippets/CMakeLists.txt | 1 + .../ROOT/snippets/errors_missing_base.cpp | 2 + .../snippets/errors_missing_class_call.cpp | 2 + .../snippets/errors_missing_class_method.cpp | 2 + .../errors_missing_class_overrider.cpp | 2 + .../ROOT/snippets/explicit_registration.hpp | 27 + include/boost/openmethod/core.hpp | 250 ++++++++ .../boost/openmethod/detail/reflection.hpp | 180 ++++++ include/boost/openmethod/initialize.hpp | 71 ++- include/boost/openmethod/macros.hpp | 45 ++ include/boost/openmethod/preamble.hpp | 44 ++ test/CMakeLists.txt | 3 + test/Jamfile | 7 + test/test_capture_errors.hpp | 14 + test/test_checked_registry.hpp | 15 + ..._class_registration_missing_base_class.cpp | 4 + ...s_registration_unknown_class_overrider.cpp | 4 + test/test_classes.hpp | 30 + test/test_compiler.cpp | 48 ++ test/test_custom_rtti_deferred.cpp | 8 +- test/test_custom_rtti_simple.cpp | 8 +- test/test_custom_rtti_simple_projection.cpp | 8 +- test/test_custom_rtti_virtual_base.cpp | 8 +- test/test_dispatch_across_namespaces.cpp | 8 +- test/test_dispatch_boost_any.cpp | 10 +- test/test_dispatch_comma_in_return_type.cpp | 8 +- test/test_dispatch_intrusive_ptr.cpp | 8 +- test/test_dispatch_lvalue_refs.cpp | 6 +- test/test_dispatch_multi.cpp | 11 +- test/test_dispatch_next_fn.cpp | 8 +- test/test_dispatch_pointer.cpp | 6 +- test/test_dispatch_rvalue_refs.cpp | 6 +- test/test_dispatch_shared_ptr_by_ref.cpp | 6 +- test/test_dispatch_shared_ptr_by_value.cpp | 6 +- test/test_dispatch_std_any.cpp | 10 +- test/test_dispatch_unique_ptr.cpp | 6 +- test/test_n2216_covariant_return_type.cpp | 6 +- test/test_n2216_pick_any_ambiguous.cpp | 6 +- test/test_namespaces.cpp | 16 +- test/test_pointer_to_method.cpp | 8 +- test/test_reflection.cpp | 569 ++++++++++++++++++ test/test_rolex.cpp | 8 +- test/test_runtime_errors_bad_call.cpp | 6 +- .../test_runtime_errors_bad_call_type_ids.cpp | 6 +- ...ime_errors_bad_call_type_ids_smart_ptr.cpp | 6 +- ...est_runtime_errors_duplicate_overrider.cpp | 8 +- ...untime_errors_initialize_unknown_class.cpp | 4 + test/test_runtime_errors_throw_error.cpp | 6 +- ...test_smart_virtual_ptr_value_semantics.cpp | 4 + test/test_static_rtti.cpp | 8 +- test/test_util.hpp | 2 + test/test_virtual_any_boost.cpp | 8 +- test/test_virtual_any_std.cpp | 8 +- test/test_virtual_ptr_by_ref.cpp | 8 +- test/test_virtual_ptr_non_polymorphic.cpp | 8 +- test/test_virtual_ptr_shared_by_const_ref.cpp | 8 +- test/test_virtual_ptr_shared_by_value.cpp | 8 +- test/test_virtual_ptr_unique.cpp | 8 +- test/test_virtual_ptr_value_semantics.cpp | 2 +- test/test_virtual_ptr_value_semantics.hpp | 3 + 67 files changed, 1771 insertions(+), 66 deletions(-) create mode 100644 doc/modules/ROOT/snippets/explicit_registration.hpp create mode 100644 include/boost/openmethod/detail/reflection.hpp create mode 100644 test/test_classes.hpp create mode 100644 test/test_reflection.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 874b7827..370e3b82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,41 @@ 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: + 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/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index 0d50f66d..f0128fe7 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_CLASSES_IN.adoc[BOOST_OPENMETHOD_CLASSES_IN] +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,88 @@ 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_CLASSES_IN.adoc[BOOST_OPENMETHOD_CLASSES_IN] +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_CLASSES_IN(::); // registers all four classes +---- + +The macro scans the namespace it is given - and the namespaces nested in it - +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. + +A base class that no method dispatches on 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. + +Classes declared in the standard library's or the compiler's own headers are not +scanned, so `::` costs little more than the narrower namespace would. + +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..84e55776 100644 --- a/doc/modules/ROOT/pages/core_api.adoc +++ b/doc/modules/ROOT/pages/core_api.adoc @@ -78,6 +78,22 @@ We register the classes with `use_classes`: include::{example}/core_api.cpp[tag=use_classes] ---- +With a compiler that supports C++26 reflection, `use_classes_in` replaces that +list. It scans a namespace for the registry's methods, and registers the classes +they dispatch on along with everything in the namespace that derives from them: + +[source,c++] +---- +BOOST_OPENMETHOD_REGISTER(use_classes_in<^^::>); +---- + +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..502957ca 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_CLASSES_IN.adoc[*BOOST_OPENMETHOD_CLASSES_IN*] +| 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/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 07c9bd8d..86e37d44 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`. @@ -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 @@ -2756,6 +2784,228 @@ void method::override_impl< this->vp_type_ids); } +// ============================================================================= +// use_classes_in + +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; + +// The classes to register for a scan of `Namespace`, each with its direct +// bases: the classes the methods of `Registry` dispatch on, and the ones the +// scan 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`, 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 { + std::vector methods, classes; + scan_namespace(Namespace, ^^method, methods, classes); + + // The classes the methods dispatch on. + std::vector virtual_classes; + + 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 + +#endif + +} // namespace detail + +//! Add the classes of a namespace to a registry +//! +//! `use_classes_in` is a registrar class that finds the classes taking part in +//! dispatch by reflection, and adds them to a registry. It makes @ref +//! use_classes unnecessary in most cases. +//! +//! It scans `Namespace`, and the namespaces nested in it, for the methods of +//! `Registry`; collects the classes they dispatch on; and registers those, +//! along with every class in the scanned namespaces that derives from one of +//! them. A base class that no method dispatches on is not registered: it could +//! never be selected on. Classes declared in the standard library's or the +//! compiler's own headers are not scanned. +//! +//! Reflection sees only what precedes it, so `use_classes_in` 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 Namespace A reflection of a namespace, e.g. `^^::`. +//! @tparam Registry A @ref registry. +//! +//! @see [Core API](xref:ROOT:core_api.adoc) +#if BOOST_OPENMETHOD_HAS_REFLECTION +template< + std::meta::info Namespace, + class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY> +class use_classes_in { + public: + use_classes_in() { + if constexpr (Registry::has_reflected_class_registration) { + using registered = + detail::reflected_registered_classes; + detail::use_reflected_classes( + static_cast(nullptr)); + } + } +}; +#endif + //! Aliases for the most frequently used types in the library. namespace aliases { diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp new file mode 100644 index 00000000..f2b54aae --- /dev/null +++ b/include/boost/openmethod/detail/reflection.hpp @@ -0,0 +1,180 @@ +// 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::detail { + +// ============================================================================= +// 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); + + for (auto base : + std::meta::bases_of(type, std::meta::access_context::unchecked())) { + 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 the `std` namespace, which a scan does not enter. +// Walking it 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`, `std::ranges`, the inline versioning ones - are reached only +// through their parent, so they are left out with it. +consteval auto is_std_namespace(std::meta::info member) -> bool { + return std::meta::dealias(member) == ^^::std; +} + +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 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) { + for (auto member : + std::meta::members_of(ns, std::meta::access_context::unchecked())) { + if (std::meta::is_namespace(member)) { + if (!is_std_namespace(member)) { + scan_namespace(member, Template, specializations, classes); + } + + 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/initialize.hpp b/include/boost/openmethod/initialize.hpp index f5cde9fe..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 { @@ -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(); @@ -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 diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 4e895841..b25f0fbd 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,49 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>) +//! Register the classes of a namespace. +//! +//! Finds the classes taking part in dispatch by reflection, and registers them. +//! It makes @ref BOOST_OPENMETHOD_CLASSES unnecessary in most cases. +//! +//! This macro is a wrapper around @ref boost::openmethod::use_classes_in; see +//! its documentation for more details. +//! +//! 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_CLASSES_IN(::); // 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 NAMESPACE The namespace to scan, e.g. `::`. +//! @param ... The registry, optionally. +//! +//! @see [Methods and Overriders](xref:ROOT:basics.adoc) +#if BOOST_OPENMETHOD_HAS_REFLECTION +#define BOOST_OPENMETHOD_CLASSES_IN(NAMESPACE, ...) \ + BOOST_OPENMETHOD_REGISTER( \ + ::boost::openmethod::use_classes_in<^^NAMESPACE __VA_OPT__(, ) \ + __VA_ARGS__>) +#else +#define BOOST_OPENMETHOD_CLASSES_IN(NAMESPACE, ...) 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/preamble.hpp b/include/boost/openmethod/preamble.hpp index 79f21071..95c88933 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_CLASSES_IN 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_CLASSES_IN 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 // ----------------------------------------------------------------------------- @@ -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/test_capture_errors.hpp b/test/test_capture_errors.hpp index a63cbc0c..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 { }; }; +#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 e29b132f..657e5bd1 100644 --- a/test/test_checked_registry.hpp +++ b/test/test_checked_registry.hpp @@ -15,15 +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 +#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_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp index f8867a1b..ed254271 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 { @@ -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_CLASSES_IN(::); diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp index e2152d79..8ae81e97 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 { @@ -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_CLASSES_IN(::); diff --git a/test/test_custom_rtti_simple_projection.cpp b/test/test_custom_rtti_simple_projection.cpp index d109bf66..92157158 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'; @@ -86,7 +88,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); @@ -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_CLASSES_IN(::); diff --git a/test/test_custom_rtti_virtual_base.cpp b/test/test_custom_rtti_virtual_base.cpp index 5497eaa0..23edaf70 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 { @@ -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_CLASSES_IN(::); diff --git a/test/test_dispatch_across_namespaces.cpp b/test/test_dispatch_across_namespaces.cpp index ed1fb76d..cbcd09c4 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_CLASSES_IN(::); diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index 0a66094a..3cbade1a 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), @@ -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_CLASSES_IN(::); diff --git a/test/test_dispatch_comma_in_return_type.cpp b/test/test_dispatch_comma_in_return_type.cpp index c4fb6d3a..7b2b7d34 100644 --- a/test/test_dispatch_comma_in_return_type.cpp +++ b/test/test_dispatch_comma_in_return_type.cpp @@ -8,6 +8,8 @@ #include #include +#include "test_classes.hpp" + #define BOOST_TEST_MODULE dispatch_comma_in_return_type #include @@ -17,7 +19,7 @@ struct 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_CLASSES_IN(::); diff --git a/test/test_dispatch_intrusive_ptr.cpp b/test/test_dispatch_intrusive_ptr.cpp index f22749b2..c3b3a108 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_CLASSES_IN(::); diff --git a/test/test_dispatch_lvalue_refs.cpp b/test/test_dispatch_lvalue_refs.cpp index 4068e840..5c9c0dbc 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_CLASSES_IN(::); diff --git a/test/test_dispatch_multi.cpp b/test/test_dispatch_multi.cpp index f1cb5a83..8a0e58a2 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_CLASSES_IN(::); diff --git a/test/test_dispatch_next_fn.cpp b/test/test_dispatch_next_fn.cpp index fde927d6..f3a7ce17 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_CLASSES_IN(::); diff --git a/test/test_dispatch_pointer.cpp b/test/test_dispatch_pointer.cpp index 925f3da2..7d701d5f 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_CLASSES_IN(::); diff --git a/test/test_dispatch_rvalue_refs.cpp b/test/test_dispatch_rvalue_refs.cpp index d89f8c29..39b455a4 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_CLASSES_IN(::); diff --git a/test/test_dispatch_shared_ptr_by_ref.cpp b/test/test_dispatch_shared_ptr_by_ref.cpp index 6b5bb0ee..2e14030f 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_CLASSES_IN(::); diff --git a/test/test_dispatch_shared_ptr_by_value.cpp b/test/test_dispatch_shared_ptr_by_value.cpp index 064d6a99..58b9e87b 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_CLASSES_IN(::); diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 6b82dbe2..c7003d70 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); @@ -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_CLASSES_IN(::); diff --git a/test/test_dispatch_unique_ptr.cpp b/test/test_dispatch_unique_ptr.cpp index 2fd4db28..c07a2f51 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_CLASSES_IN(::); diff --git a/test/test_n2216_covariant_return_type.cpp b/test/test_n2216_covariant_return_type.cpp index 361c15e9..6a99693d 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_CLASSES_IN(::); diff --git a/test/test_n2216_pick_any_ambiguous.cpp b/test/test_n2216_pick_any_ambiguous.cpp index dc351017..479fde3d 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_CLASSES_IN(::); diff --git a/test/test_namespaces.cpp b/test/test_namespaces.cpp index 9273a293..e7198246 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_CLASSES_IN(::); diff --git a/test/test_pointer_to_method.cpp b/test/test_pointer_to_method.cpp index 92138988..62bed875 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_CLASSES_IN(::); diff --git a/test/test_reflection.cpp b/test/test_reflection.cpp new file mode 100644 index 00000000..03ca67a9 --- /dev/null +++ b/test/test_reflection.cpp @@ -0,0 +1,569 @@ +// 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 - `use_classes_in` and +// `BOOST_OPENMETHOD_CLASSES_IN`. 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( + use_classes_in<^^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( + use_classes_in<^^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( + use_classes_in<^^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( + use_classes_in<^^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( + use_classes_in< + ^^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(use_classes_in<^^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( + use_classes_in<^^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( + use_classes_in<^^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( + use_classes_in<^^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( + use_classes_in<^^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); +} + +#endif diff --git a/test/test_rolex.cpp b/test/test_rolex.cpp index 4bef424b..0079ade8 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_CLASSES_IN(::); diff --git a/test/test_runtime_errors_bad_call.cpp b/test/test_runtime_errors_bad_call.cpp index 1cb696d1..a203491c 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_CLASSES_IN(::); diff --git a/test/test_runtime_errors_bad_call_type_ids.cpp b/test/test_runtime_errors_bad_call_type_ids.cpp index 00df1707..9707d7fa 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_CLASSES_IN(::); 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..5d48f077 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_CLASSES_IN(::); diff --git a/test/test_runtime_errors_duplicate_overrider.cpp b/test/test_runtime_errors_duplicate_overrider.cpp index f387bf7f..72f618ab 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_CLASSES_IN(::); 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 6835a806..25b88e00 100644 --- a/test/test_runtime_errors_throw_error.cpp +++ b/test/test_runtime_errors_throw_error.cpp @@ -22,7 +22,7 @@ struct test_registry : 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); @@ -41,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_CLASSES_IN(::); diff --git a/test/test_smart_virtual_ptr_value_semantics.cpp b/test/test_smart_virtual_ptr_value_semantics.cpp index 528bfcda..a5764d96 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_CLASSES_IN(::); diff --git a/test/test_static_rtti.cpp b/test/test_static_rtti.cpp index d07a524e..2e42c899 100644 --- a/test/test_static_rtti.cpp +++ b/test/test_static_rtti.cpp @@ -17,6 +17,8 @@ struct static_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_CLASSES_IN(::); diff --git a/test/test_util.hpp b/test/test_util.hpp index 28699932..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; }; diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp index 5382dabf..c81104f4 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 @@ -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_CLASSES_IN(::); diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp index 76fefc2c..45d4fdf4 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 @@ -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_CLASSES_IN(::); diff --git a/test/test_virtual_ptr_by_ref.cpp b/test/test_virtual_ptr_by_ref.cpp index 83a030a8..087a8910 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_CLASSES_IN(::); diff --git a/test/test_virtual_ptr_non_polymorphic.cpp b/test/test_virtual_ptr_non_polymorphic.cpp index 932cd0a9..995e00c3 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_CLASSES_IN(::); diff --git a/test/test_virtual_ptr_shared_by_const_ref.cpp b/test/test_virtual_ptr_shared_by_const_ref.cpp index a902d983..0ca159e7 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_CLASSES_IN(::); diff --git a/test/test_virtual_ptr_shared_by_value.cpp b/test/test_virtual_ptr_shared_by_value.cpp index 66db0f93..f37bf8cf 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_CLASSES_IN(::); diff --git a/test/test_virtual_ptr_unique.cpp b/test/test_virtual_ptr_unique.cpp index 73dba04a..f6d42f5c 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_CLASSES_IN(::); 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 cacf4191..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; From 35c49e4ed6cfd434b8a25e80342386e0e4fa9771 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 12:47:00 -0400 Subject: [PATCH 4/8] reflection: hold the scanned ranges in named locals GCC 16 since r16-8246 - the fix for PR124575, "ICE with lifetime extension of consteval-only" - marks a lifetime-extended temporary of consteval-only type DECL_EXTERNAL. `vector` is such a type, and the range-for's __for_range is such a temporary, so the constant evaluator hands every frame of a recursive consteval call the same object: the inner call destroys the vector the outer call is still walking. Both recursive scanners tripped on it, and the whole C++26 leg of the b2 matrix failed to compile - 37 of 58 test/test_*.cpp, 148 targets: bits/stl_vector.h:792:29: error: accessing '' outside its lifetime note: declared here std::meta::bases_of(type, std::meta::access_context::unchecked())) { A plain automatic variable is not an extended-ref temporary, so each frame gets its own. Verified against the exact CI compiler (Ubuntu 16-20260322, r16-8246): 58/58 compile, where HEAD gave 21/58. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz --- .../boost/openmethod/detail/reflection.hpp | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index f2b54aae..f5bde76f 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -47,8 +47,19 @@ consteval void collect_reflected_bases( types.push_back(type); - for (auto base : - std::meta::bases_of(type, std::meta::access_context::unchecked())) { + // The range is held in a named local instead of being left to the + // range-for to lifetime-extend. GCC 16, since r16-8246 (the fix for + // PR124575), marks a lifetime-extended temporary of consteval-only type - + // which `vector` is - `DECL_EXTERNAL`. 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. + 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); } @@ -145,8 +156,11 @@ consteval void scan_namespace( std::meta::info ns, std::meta::info Template, std::vector& specializations, std::vector& classes) { - for (auto member : - std::meta::members_of(ns, std::meta::access_context::unchecked())) { + // 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 (!is_std_namespace(member)) { scan_namespace(member, Template, specializations, classes); From e2d53f45a1471a1049202c6a33afd72479f1692a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 12:47:05 -0400 Subject: [PATCH 5/8] ci: clone Boost.Test and Boost.DLL for the reflection job boost-clone defaults modules-exclude-paths to `test tests`, so it never scans our own test/ and cloned neither library. The job then died at CMake generate, before compiling anything: CMake Warning: Library 'test' given in BOOST_INCLUDE_LIBRARIES has not been found. CMake Error at test/CMakeLists.txt:96: links to Boost::unit_test_framework but the target was not found. The action unions an explicit `modules` with the scan results and resolves their own dependencies afterwards, so naming the two is enough. The Antora jobs use the same action and are unaffected: they never configure a test target. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 370e3b82..60987562 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,13 @@ jobs: - 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: . From 9d08e46c61c05b35a00d8104058d6ecc14459e26 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 13:03:26 -0400 Subject: [PATCH 6/8] reflection: name the exact gcc commit in the workaround comment r16-8246 is only the snapshot Boost.CI happens to install; the regression starts at d51a78f7a8f3. Isolated by building cc1plus at that commit and at its parent, 95d2eb6fa073, which differ by exactly the four-line DECL_EXTERNAL hunk in set_up_extended_ref_temp: the parent accepts the scan, the commit rejects it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz --- include/boost/openmethod/detail/reflection.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index f5bde76f..90755254 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -48,14 +48,15 @@ consteval void collect_reflected_bases( types.push_back(type); // The range is held in a named local instead of being left to the - // range-for to lifetime-extend. GCC 16, since r16-8246 (the fix for - // PR124575), marks a lifetime-extended temporary of consteval-only type - - // which `vector` is - `DECL_EXTERNAL`. 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. + // range-for to lifetime-extend. GCC 16, since d51a78f7a8f3 (2026-03-19, + // the fix for PR124575), marks a lifetime-extended temporary of + // consteval-only type - which `vector` is - `DECL_EXTERNAL`. + // 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. auto bases = std::meta::bases_of(type, std::meta::access_context::unchecked()); From 427f48118c7fa114f3d8e73f5f3c024adad74481 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 13:11:38 -0400 Subject: [PATCH 7/8] reflection: point the workaround comment at the upstream PRs The bug is GCC PR124645/PR124646, reported 2026-03-26 and fixed on 2026-04-02 by r16-8430: r16-8235 set DECL_EXTERNAL on the lifetime-extended temporary for both the at_function_scope_p and !at_function_scope_p cases, where only the latter was intended. Naming the PRs and the fixing revision is more use to a future reader than the introducing SHA alone - it says when the workaround can go. Not yet: Ubuntu 26.04, which Boost.CI uses for the C++26 leg, ships 16-20260322 (r16-8246), between the two. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015RWrWt9az5Bb9pjP9L9qRz --- include/boost/openmethod/detail/reflection.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index 90755254..5fbcb0fe 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -48,15 +48,19 @@ consteval void collect_reflected_bases( types.push_back(type); // The range is held in a named local instead of being left to the - // range-for to lifetime-extend. GCC 16, since d51a78f7a8f3 (2026-03-19, - // the fix for PR124575), marks a lifetime-extended temporary of - // consteval-only type - which `vector` is - `DECL_EXTERNAL`. - // 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 + // 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()); From 77996d81621101e9be9ff7f9fa6a85a301bef359 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 14:48:46 -0400 Subject: [PATCH 8/8] reflection: generalize registration, rename it register_classes `use_classes_in<^^ns, Registry>` took one namespace and one registry. `register_classes<...>` takes four groups of non-type arguments, each optional, in enforced order - namespaces to scan, classes to register, one `register_classes_opts` value, registries (as reflections now, since the pack is `auto...`) - and registers in every listed registry. Listed classes are dispatch roots: registered whether a method dispatches on them or not, along with the scanned classes deriving from them. Classes with no namespace disable the scan - exactly the listed classes are registered, their inheritance lattice read from reflection and flattened over unlisted intermediates. Scans now skip `boost` as well as `std` - worth ~0.2s per TU on a scan of `^^::` in a Boost.Test TU, and reversible with `scan_boost`/`scan_std`; `no_recurse` keeps a scan out of nested namespaces. The options live in a namespace rather than an enum class so a using-directive can make the terse spellings available. With no namespace and no class at all, the enclosing namespace is scanned. Three routes lead there, all resting on P2996's call-site evaluation of `access_context::current()`: the default template argument covers `register_classes<>`; a `detail::scope_marker` that `BOOST_OPENMETHOD_REGISTER_CLASSES` always prepends covers every macro form, registry-only and options-only included; and the public `current_namespace()` helper covers bare-template argument lists, which the template itself cannot capture - a static_assert points there. The macro no longer pastes `^^`; the caller writes it. The two new compile-fail tests produce the expected diagnostic via `#error` when reflection is off, so they pass under every configuration with no build-file changes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- doc/modules/ROOT/pages/basics.adoc | 50 ++- doc/modules/ROOT/pages/core_api.adoc | 9 +- doc/modules/ROOT/pages/ref_macros.adoc | 2 +- include/boost/openmethod/core.hpp | 317 +++++++++++++-- .../boost/openmethod/detail/reflection.hpp | 91 ++++- include/boost/openmethod/macros.hpp | 27 +- include/boost/openmethod/preamble.hpp | 4 +- test/compile_fail_reflection_arg_order.cpp | 37 ++ ...compile_fail_reflection_no_scan_source.cpp | 35 ++ test/test_custom_rtti_deferred.cpp | 2 +- test/test_custom_rtti_simple.cpp | 2 +- test/test_custom_rtti_simple_projection.cpp | 2 +- test/test_custom_rtti_virtual_base.cpp | 2 +- test/test_dispatch_across_namespaces.cpp | 2 +- test/test_dispatch_boost_any.cpp | 2 +- test/test_dispatch_comma_in_return_type.cpp | 2 +- test/test_dispatch_intrusive_ptr.cpp | 2 +- test/test_dispatch_lvalue_refs.cpp | 2 +- test/test_dispatch_multi.cpp | 2 +- test/test_dispatch_next_fn.cpp | 2 +- test/test_dispatch_pointer.cpp | 2 +- test/test_dispatch_rvalue_refs.cpp | 2 +- test/test_dispatch_shared_ptr_by_ref.cpp | 2 +- test/test_dispatch_shared_ptr_by_value.cpp | 2 +- test/test_dispatch_std_any.cpp | 2 +- test/test_dispatch_unique_ptr.cpp | 2 +- test/test_n2216_covariant_return_type.cpp | 2 +- test/test_n2216_pick_any_ambiguous.cpp | 2 +- test/test_namespaces.cpp | 2 +- test/test_pointer_to_method.cpp | 2 +- test/test_reflection.cpp | 379 +++++++++++++++++- test/test_rolex.cpp | 2 +- test/test_runtime_errors_bad_call.cpp | 2 +- .../test_runtime_errors_bad_call_type_ids.cpp | 2 +- ...ime_errors_bad_call_type_ids_smart_ptr.cpp | 2 +- ...est_runtime_errors_duplicate_overrider.cpp | 2 +- test/test_runtime_errors_throw_error.cpp | 2 +- ...test_smart_virtual_ptr_value_semantics.cpp | 2 +- test/test_static_rtti.cpp | 2 +- test/test_virtual_any_boost.cpp | 2 +- test/test_virtual_any_std.cpp | 2 +- test/test_virtual_ptr_by_ref.cpp | 2 +- test/test_virtual_ptr_non_polymorphic.cpp | 2 +- test/test_virtual_ptr_shared_by_const_ref.cpp | 2 +- test/test_virtual_ptr_shared_by_value.cpp | 2 +- test/test_virtual_ptr_unique.cpp | 2 +- 46 files changed, 883 insertions(+), 140 deletions(-) create mode 100644 test/compile_fail_reflection_arg_order.cpp create mode 100644 test/compile_fail_reflection_no_scan_source.cpp diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index f0128fe7..95a9fbea 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -86,7 +86,7 @@ inheritance lattice. [NOTE] ==== With a compiler that supports C++26 reflection, the class list is unnecessary. -xref:reference:BOOST_OPENMETHOD_CLASSES_IN.adoc[BOOST_OPENMETHOD_CLASSES_IN] +xref:reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc[BOOST_OPENMETHOD_REGISTER_CLASSES] finds the classes on its own - see <>. ==== @@ -121,7 +121,7 @@ include::{examplesdir}/ast.cpp[tag=content] 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_CLASSES_IN.adoc[BOOST_OPENMETHOD_CLASSES_IN] +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: @@ -139,24 +139,38 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream& os, Dog&), void) { os << "bark"; } -BOOST_OPENMETHOD_CLASSES_IN(::); // registers all four classes +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); // registers all four classes ---- -The macro scans the namespace it is given - and the namespaces nested in it - -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. - -A base class that no method dispatches on 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. - -Classes declared in the standard library's or the compiler's own headers are not -scanned, so `::` costs little more than the narrower namespace would. +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 diff --git a/doc/modules/ROOT/pages/core_api.adoc b/doc/modules/ROOT/pages/core_api.adoc index 84e55776..f1b3efc0 100644 --- a/doc/modules/ROOT/pages/core_api.adoc +++ b/doc/modules/ROOT/pages/core_api.adoc @@ -78,13 +78,14 @@ We register the classes with `use_classes`: include::{example}/core_api.cpp[tag=use_classes] ---- -With a compiler that supports C++26 reflection, `use_classes_in` replaces that -list. It scans a namespace for the registry's methods, and registers the classes -they dispatch on along with everything in the namespace that derives from them: +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(use_classes_in<^^::>); +BOOST_OPENMETHOD_REGISTER(register_classes<^^::>); ---- A method declared the way `postfix` is above - an alias for a `method` diff --git a/doc/modules/ROOT/pages/ref_macros.adoc b/doc/modules/ROOT/pages/ref_macros.adoc index 502957ca..cf1513cd 100644 --- a/doc/modules/ROOT/pages/ref_macros.adoc +++ b/doc/modules/ROOT/pages/ref_macros.adoc @@ -14,7 +14,7 @@ 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_CLASSES_IN.adoc[*BOOST_OPENMETHOD_CLASSES_IN*] +| 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. diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 86e37d44..455e94c8 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -2785,7 +2785,7 @@ void method::override_impl< } // ============================================================================= -// use_classes_in +// register_classes namespace detail { @@ -2812,24 +2812,174 @@ struct method_traits_aux> { template using method_classes = typename method_traits_aux::type; -// The classes to register for a scan of `Namespace`, each with its direct -// bases: the classes the methods of `Registry` dispatch on, and the ones the -// scan 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. +// 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`, and instantiating a trait once per pair of -// them costs far more than walking their base classes does. -template +// 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; - scan_namespace(Namespace, ^^method, methods, classes); - // The classes the methods dispatch on. - std::vector virtual_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. @@ -2944,30 +3094,92 @@ consteval auto reflected_registered_classes_info() -> std::meta::info { // // clang-format off: the formatter predates P2996 and eats the spaces around the // splice, leaving `typename[:...:]`. -template +template using reflected_registered_classes = - typename [: reflected_registered_classes_info() :]; + 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 -//! Add the classes of a namespace to a registry -//! -//! `use_classes_in` is a registrar class that finds the classes taking part in -//! dispatch by reflection, and adds them to a registry. It makes @ref -//! use_classes unnecessary in most cases. +#if BOOST_OPENMETHOD_HAS_REFLECTION + +//! Get the current namespace. //! -//! It scans `Namespace`, and the namespaces nested in it, for the methods of -//! `Registry`; collects the classes they dispatch on; and registers those, -//! along with every class in the scanned namespaces that derives from one of -//! them. A base class that no method dispatches on is not registered: it could -//! never be selected on. Classes declared in the standard library's or the -//! compiler's own headers are not scanned. +//! 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. //! -//! Reflection sees only what precedes it, so `use_classes_in` must come **after** -//! the declarations it is meant to find - at the bottom of the file. +//! 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` @@ -2985,25 +3197,48 @@ using reflected_registered_classes = //! This class template is available only if the compiler supports C++26 //! reflection, i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. //! -//! @tparam Namespace A reflection of a namespace, e.g. `^^::`. -//! @tparam Registry A @ref registry. +//! @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) -#if BOOST_OPENMETHOD_HAS_REFLECTION template< - std::meta::info Namespace, - class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY> -class use_classes_in { + 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: - use_classes_in() { - if constexpr (Registry::has_reflected_class_registration) { - using registered = - detail::reflected_registered_classes; - detail::use_reflected_classes( - static_cast(nullptr)); - } + register_classes() { + use(static_cast(nullptr)); } }; + #endif //! Aliases for the most frequently used types in the library. diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index 5fbcb0fe..d745b32e 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -27,8 +27,56 @@ #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 @@ -91,13 +139,26 @@ using reflected_bases = typename [: reflected_bases_info() :]; // ============================================================================= // namespace scan -// True if `member` is the `std` namespace, which a scan does not enter. -// Walking it 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`, `std::ranges`, the inline versioning ones - are reached only -// through their parent, so they are left out with it. -consteval auto is_std_namespace(std::meta::info member) -> bool { - return std::meta::dealias(member) == ^^::std; +// 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( @@ -153,22 +214,24 @@ consteval auto specialization_named_by(std::meta::info member) return std::meta::info(); } -// Walk `ns` and 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. +// 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) { + 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 (!is_std_namespace(member)) { - scan_namespace(member, Template, specializations, classes); + if (!has_opt(opts, register_classes_opts::no_recurse) && + !is_excluded_namespace(member, opts)) { + scan_namespace( + member, Template, specializations, classes, opts); } continue; diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index b25f0fbd..3566ffce 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -589,13 +589,17 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>) -//! Register the classes of a namespace. +//! Find the classes taking part in dispatch by reflection, and register them. //! -//! Finds the classes taking part in dispatch by reflection, and registers them. //! It makes @ref BOOST_OPENMETHOD_CLASSES unnecessary in most cases. //! -//! This macro is a wrapper around @ref boost::openmethod::use_classes_in; see -//! its documentation for more details. +//! 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: @@ -612,24 +616,25 @@ inline constexpr bool method_not_found = false; //! os << "bark"; //! } //! -//! BOOST_OPENMETHOD_CLASSES_IN(::); // registers all four classes +//! 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 NAMESPACE The namespace to scan, e.g. `::`. -//! @param ... The registry, optionally. +//! @param ... Namespaces, classes, options, registries - see above. //! //! @see [Methods and Overriders](xref:ROOT:basics.adoc) #if BOOST_OPENMETHOD_HAS_REFLECTION -#define BOOST_OPENMETHOD_CLASSES_IN(NAMESPACE, ...) \ +#define BOOST_OPENMETHOD_REGISTER_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER( \ - ::boost::openmethod::use_classes_in<^^NAMESPACE __VA_OPT__(, ) \ - __VA_ARGS__>) + ::boost::openmethod::register_classes< \ + ::boost::openmethod::detail::scope_marker{ \ + ::std::meta::access_context::current().scope() } __VA_OPT__( \ + , ) __VA_ARGS__>) #else -#define BOOST_OPENMETHOD_CLASSES_IN(NAMESPACE, ...) static_assert(true) +#define BOOST_OPENMETHOD_REGISTER_CLASSES(...) static_assert(true) #endif // The three macros below share a registry's state - the single variable diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 95c88933..9eb30dc2 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -177,7 +177,7 @@ 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_CLASSES_IN registers these classes on its own, and the +//! 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 @@ -220,7 +220,7 @@ struct missing_class : openmethod_error { //! include:errors_missing_class_call.cpp#fix //! //! @note With a compiler that supports C++26 reflection, @ref -//! BOOST_OPENMETHOD_CLASSES_IN registers these classes on its own, and the +//! 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 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/test_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp index ed254271..61935c4e 100644 --- a/test/test_custom_rtti_deferred.cpp +++ b/test/test_custom_rtti_deferred.cpp @@ -238,4 +238,4 @@ BOOST_AUTO_TEST_CASE(custom_rtti_deferred) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp index 8ae81e97..5c48f2a8 100644 --- a/test/test_custom_rtti_simple.cpp +++ b/test/test_custom_rtti_simple.cpp @@ -171,4 +171,4 @@ void call_poke(vptr a, std::ostream& os) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_custom_rtti_simple_projection.cpp b/test/test_custom_rtti_simple_projection.cpp index 92157158..8cb911da 100644 --- a/test/test_custom_rtti_simple_projection.cpp +++ b/test/test_custom_rtti_simple_projection.cpp @@ -119,4 +119,4 @@ BOOST_AUTO_TEST_CASE(custom_rtti_simple_projection) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_custom_rtti_virtual_base.cpp b/test/test_custom_rtti_virtual_base.cpp index 23edaf70..e4c62363 100644 --- a/test/test_custom_rtti_virtual_base.cpp +++ b/test/test_custom_rtti_virtual_base.cpp @@ -196,4 +196,4 @@ void call_poke(vptr a, std::ostream& os) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_across_namespaces.cpp b/test/test_dispatch_across_namespaces.cpp index cbcd09c4..aaf6dc51 100644 --- a/test/test_dispatch_across_namespaces.cpp +++ b/test/test_dispatch_across_namespaces.cpp @@ -48,4 +48,4 @@ BOOST_AUTO_TEST_CASE(across_namespaces) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index 3cbade1a..8c575a5f 100644 --- a/test/test_dispatch_boost_any.cpp +++ b/test/test_dispatch_boost_any.cpp @@ -404,4 +404,4 @@ BOOST_AUTO_TEST_CASE(boost_any_class_in_hierarchy) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +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 7b2b7d34..4c8f7ba8 100644 --- a/test/test_dispatch_comma_in_return_type.cpp +++ b/test/test_dispatch_comma_in_return_type.cpp @@ -37,4 +37,4 @@ BOOST_AUTO_TEST_CASE(comma_in_return_type) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_intrusive_ptr.cpp b/test/test_dispatch_intrusive_ptr.cpp index c3b3a108..7ff45b3c 100644 --- a/test/test_dispatch_intrusive_ptr.cpp +++ b/test/test_dispatch_intrusive_ptr.cpp @@ -171,4 +171,4 @@ BOOST_AUTO_TEST_CASE(intrusive_virtual_ptr_by_const_ref) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_lvalue_refs.cpp b/test/test_dispatch_lvalue_refs.cpp index 5c9c0dbc..1881aa63 100644 --- a/test/test_dispatch_lvalue_refs.cpp +++ b/test/test_dispatch_lvalue_refs.cpp @@ -40,4 +40,4 @@ BOOST_AUTO_TEST_CASE(cast_args_lvalue_refs) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_multi.cpp b/test/test_dispatch_multi.cpp index 8a0e58a2..49bd773b 100644 --- a/test/test_dispatch_multi.cpp +++ b/test/test_dispatch_multi.cpp @@ -77,4 +77,4 @@ BOOST_AUTO_TEST_CASE(simple) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_next_fn.cpp b/test/test_dispatch_next_fn.cpp index f3a7ce17..2141248c 100644 --- a/test/test_dispatch_next_fn.cpp +++ b/test/test_dispatch_next_fn.cpp @@ -54,4 +54,4 @@ BOOST_AUTO_TEST_CASE(test_next_fn) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_pointer.cpp b/test/test_dispatch_pointer.cpp index 7d701d5f..950fd110 100644 --- a/test/test_dispatch_pointer.cpp +++ b/test/test_dispatch_pointer.cpp @@ -40,4 +40,4 @@ BOOST_AUTO_TEST_CASE(cast_args_pointer) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_rvalue_refs.cpp b/test/test_dispatch_rvalue_refs.cpp index 39b455a4..9172263d 100644 --- a/test/test_dispatch_rvalue_refs.cpp +++ b/test/test_dispatch_rvalue_refs.cpp @@ -48,4 +48,4 @@ BOOST_AUTO_TEST_CASE(cast_args_rvalue_refs) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +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 2e14030f..699e21c8 100644 --- a/test/test_dispatch_shared_ptr_by_ref.cpp +++ b/test/test_dispatch_shared_ptr_by_ref.cpp @@ -45,4 +45,4 @@ BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_ref) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +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 58b9e87b..1bfab5fc 100644 --- a/test/test_dispatch_shared_ptr_by_value.cpp +++ b/test/test_dispatch_shared_ptr_by_value.cpp @@ -42,4 +42,4 @@ BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_value) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index c7003d70..e7a480ba 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -401,4 +401,4 @@ BOOST_AUTO_TEST_CASE(std_any_class_in_hierarchy) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_dispatch_unique_ptr.cpp b/test/test_dispatch_unique_ptr.cpp index c07a2f51..c9a75737 100644 --- a/test/test_dispatch_unique_ptr.cpp +++ b/test/test_dispatch_unique_ptr.cpp @@ -44,4 +44,4 @@ BOOST_AUTO_TEST_CASE(cast_args_unique_ptr) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_n2216_covariant_return_type.cpp b/test/test_n2216_covariant_return_type.cpp index 6a99693d..b198fdde 100644 --- a/test/test_n2216_covariant_return_type.cpp +++ b/test/test_n2216_covariant_return_type.cpp @@ -50,4 +50,4 @@ BOOST_AUTO_TEST_CASE(covariant_return_type) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_n2216_pick_any_ambiguous.cpp b/test/test_n2216_pick_any_ambiguous.cpp index 479fde3d..61eda1ff 100644 --- a/test/test_n2216_pick_any_ambiguous.cpp +++ b/test/test_n2216_pick_any_ambiguous.cpp @@ -46,4 +46,4 @@ BOOST_AUTO_TEST_CASE(pick_any_ambiguous) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_namespaces.cpp b/test/test_namespaces.cpp index e7198246..8e9301bf 100644 --- a/test/test_namespaces.cpp +++ b/test/test_namespaces.cpp @@ -111,4 +111,4 @@ auto main() -> int { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_pointer_to_method.cpp b/test/test_pointer_to_method.cpp index 62bed875..adc2e6cb 100644 --- a/test/test_pointer_to_method.cpp +++ b/test/test_pointer_to_method.cpp @@ -40,4 +40,4 @@ BOOST_AUTO_TEST_CASE(noadl) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_reflection.cpp b/test/test_reflection.cpp index 03ca67a9..8efbc982 100644 --- a/test/test_reflection.cpp +++ b/test/test_reflection.cpp @@ -3,8 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -// Tests for reflection-based class registration - `use_classes_in` and -// `BOOST_OPENMETHOD_CLASSES_IN`. Without a compiler that supports C++26 +// 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. @@ -84,7 +84,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Cat&), std::string) { } // namespace macro_interface BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^macro_interface, macro_interface::test_registry>); + register_classes<^^macro_interface, ^^macro_interface::test_registry>); BOOST_AUTO_TEST_CASE(macro_interface_dispatch) { using namespace macro_interface; @@ -145,7 +145,7 @@ BOOST_OPENMETHOD_REGISTER(value::override); } // namespace core_interface BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^core_interface, core_interface::test_registry>); + register_classes<^^core_interface, ^^core_interface::test_registry>); BOOST_AUTO_TEST_CASE(core_interface_dispatch) { using namespace core_interface; @@ -184,7 +184,7 @@ BOOST_OPENMETHOD(poke, (virtual_), void, test_registry); } // namespace method_only BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^method_only, method_only::test_registry>); + register_classes<^^method_only, ^^method_only::test_registry>); BOOST_AUTO_TEST_CASE(method_without_overrider) { using namespace method_only; @@ -228,7 +228,7 @@ BOOST_OPENMETHOD_OVERRIDE(meet, (Carnivore&, Herbivore&), std::string) { } // namespace inheritance BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^inheritance, inheritance::test_registry>); + register_classes<^^inheritance, ^^inheritance::test_registry>); BOOST_AUTO_TEST_CASE(virtual_and_multiple_inheritance) { using namespace inheritance; @@ -286,8 +286,8 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { } // namespace repeated_inheritance BOOST_OPENMETHOD_REGISTER( - use_classes_in< - ^^repeated_inheritance, repeated_inheritance::test_registry>); + register_classes< + ^^repeated_inheritance, ^^repeated_inheritance::test_registry>); BOOST_AUTO_TEST_CASE(repeated_inheritance_does_not_break_the_scan) { using namespace repeated_inheritance; @@ -336,7 +336,7 @@ BOOST_OPENMETHOD_OVERRIDE( } // namespace nested -BOOST_OPENMETHOD_REGISTER(use_classes_in<^^nested, nested::test_registry>); +BOOST_OPENMETHOD_REGISTER(register_classes<^^nested, ^^nested::test_registry>); BOOST_AUTO_TEST_CASE(nested_namespaces_and_smart_pointers) { using namespace nested; @@ -387,7 +387,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { } // namespace unused_bases BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^unused_bases, unused_bases::test_registry>); + 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; @@ -443,7 +443,7 @@ BOOST_OPENMETHOD_OVERRIDE(label, (Dog&), std::string) { } // namespace shared_bases BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^shared_bases, shared_bases::test_registry>); + register_classes<^^shared_bases, ^^shared_bases::test_registry>); BOOST_AUTO_TEST_CASE(a_base_another_method_dispatches_on_is_registered) { using namespace shared_bases; @@ -496,7 +496,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (C&), std::string) { } // namespace direct_bases BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^direct_bases, direct_bases::test_registry>); + register_classes<^^direct_bases, ^^direct_bases::test_registry>); BOOST_AUTO_TEST_CASE(recorded_bases_are_direct) { using namespace direct_bases; @@ -557,7 +557,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { } // namespace opted_out BOOST_OPENMETHOD_REGISTER( - use_classes_in<^^opted_out, opted_out::test_registry>); + 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); @@ -566,4 +566,357 @@ BOOST_AUTO_TEST_CASE(explicit_class_registration_disables_the_scan) { 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 0079ade8..1723fc40 100644 --- a/test/test_rolex.cpp +++ b/test/test_rolex.cpp @@ -189,4 +189,4 @@ BOOST_AUTO_TEST_CASE(approve_via_wrapper) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_bad_call.cpp b/test/test_runtime_errors_bad_call.cpp index a203491c..c698ea14 100644 --- a/test/test_runtime_errors_bad_call.cpp +++ b/test/test_runtime_errors_bad_call.cpp @@ -56,4 +56,4 @@ BOOST_AUTO_TEST_CASE(bad_calls) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +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 9707d7fa..5bc6e77e 100644 --- a/test/test_runtime_errors_bad_call_type_ids.cpp +++ b/test/test_runtime_errors_bad_call_type_ids.cpp @@ -51,4 +51,4 @@ BOOST_AUTO_TEST_CASE(bad_call_type_ids) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +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 5d48f077..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 @@ -44,4 +44,4 @@ BOOST_AUTO_TEST_CASE(bad_call_type_ids_smart_ptr) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_duplicate_overrider.cpp b/test/test_runtime_errors_duplicate_overrider.cpp index 72f618ab..4559b9a7 100644 --- a/test/test_runtime_errors_duplicate_overrider.cpp +++ b/test/test_runtime_errors_duplicate_overrider.cpp @@ -63,4 +63,4 @@ BOOST_AUTO_TEST_CASE(duplicate_overrider_is_ambiguous) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_runtime_errors_throw_error.cpp b/test/test_runtime_errors_throw_error.cpp index 25b88e00..65b8ed7a 100644 --- a/test/test_runtime_errors_throw_error.cpp +++ b/test/test_runtime_errors_throw_error.cpp @@ -44,4 +44,4 @@ BOOST_AUTO_TEST_CASE(throw_error) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_smart_virtual_ptr_value_semantics.cpp b/test/test_smart_virtual_ptr_value_semantics.cpp index a5764d96..fa6109b7 100644 --- a/test/test_smart_virtual_ptr_value_semantics.cpp +++ b/test/test_smart_virtual_ptr_value_semantics.cpp @@ -446,4 +446,4 @@ template struct check_illegal_smart_ops< // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_static_rtti.cpp b/test/test_static_rtti.cpp index 2e42c899..a4de824f 100644 --- a/test/test_static_rtti.cpp +++ b/test/test_static_rtti.cpp @@ -59,4 +59,4 @@ BOOST_AUTO_TEST_CASE(static_rtti) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp index c81104f4..5e9a6c29 100644 --- a/test/test_virtual_any_boost.cpp +++ b/test/test_virtual_any_boost.cpp @@ -339,4 +339,4 @@ BOOST_AUTO_TEST_CASE(virtual_any_mixed_with_virtual_ptr) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp index 45d4fdf4..eaf91c0b 100644 --- a/test/test_virtual_any_std.cpp +++ b/test/test_virtual_any_std.cpp @@ -339,4 +339,4 @@ BOOST_AUTO_TEST_CASE(virtual_any_mixed_with_virtual_ptr) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_by_ref.cpp b/test/test_virtual_ptr_by_ref.cpp index 087a8910..19d55d29 100644 --- a/test/test_virtual_ptr_by_ref.cpp +++ b/test/test_virtual_ptr_by_ref.cpp @@ -65,4 +65,4 @@ BOOST_AUTO_TEST_CASE(test_virtual_ptr_by_ref) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_non_polymorphic.cpp b/test/test_virtual_ptr_non_polymorphic.cpp index 995e00c3..d9395a1d 100644 --- a/test/test_virtual_ptr_non_polymorphic.cpp +++ b/test/test_virtual_ptr_non_polymorphic.cpp @@ -42,4 +42,4 @@ BOOST_AUTO_TEST_CASE(test_virtual_ptr_non_polymorphic) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +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 0ca159e7..1b2c7243 100644 --- a/test/test_virtual_ptr_shared_by_const_ref.cpp +++ b/test/test_virtual_ptr_shared_by_const_ref.cpp @@ -47,4 +47,4 @@ BOOST_AUTO_TEST_CASE(test_virtual_shared_by_const_reference) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +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 f37bf8cf..1ea10bf5 100644 --- a/test/test_virtual_ptr_shared_by_value.cpp +++ b/test/test_virtual_ptr_shared_by_value.cpp @@ -45,4 +45,4 @@ BOOST_AUTO_TEST_CASE(test_virtual_shared_by_value) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); diff --git a/test/test_virtual_ptr_unique.cpp b/test/test_virtual_ptr_unique.cpp index f6d42f5c..726d59b4 100644 --- a/test/test_virtual_ptr_unique.cpp +++ b/test/test_virtual_ptr_unique.cpp @@ -45,4 +45,4 @@ BOOST_AUTO_TEST_CASE(test_virtual_unique) { // Registers the classes above by reflection, when the compiler supports it. // Must come last: reflection sees only what precedes it. -BOOST_OPENMETHOD_CLASSES_IN(::); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^::);