From 6b029785f1f04c13e2150428d8a127ba1b520848 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 29 Aug 2026 14:42:09 -0400 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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 08/19] 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(^^::); From a3e98071a454f497ab311a4d3e56729eef5b2d14 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 30 Aug 2026 02:03:13 -0400 Subject: [PATCH 09/19] doc: give the reflection API a reference, on MrDocs stubs MrDocs' front-end does not implement P2996, so the reference build runs at `-D CMAKE_CXX_STANDARD=20` and sees `BOOST_OPENMETHOD_HAS_REFLECTION` as 0: every `#if BOOST_OPENMETHOD_HAS_REFLECTION` block is invisible to it, and the whole C++26 reflection API was missing from the reference. `register_classes` and `current_namespace` are now restated as documentation stubs in an `#elif defined(__MRDOCS__)` branch beside the real declarations, following `inplace_vptr_derived`: each doc comment exists exactly once, on the stub, and the real declaration carries only a `@see`. `detail/reflection.hpp` forward-declares `std::meta::info` and `std::meta::access_context` under `#ifdef __MRDOCS__` so `current_namespace` can render its exact signature; `include-symbols` keeps them out of the pages. The `register_classes` stub is `template` where the real one splits `First`/`Rest` - the split exists only because a pack cannot carry a default argument, and MrDocs prints a default argument as raw source text, so the real spelling would leak `detail::`. `register_classes_opts` needs no stub at all: it is plain C++17, so its guard is widened to `|| defined(__MRDOCS__)` and MrDocs reads the real definition. `BOOST_OPENMETHOD_REGISTER_CLASSES` was not extracted either, though for a different reason: its doc comment sat above the `#if`, separated by preprocessor directives from the `#else` `#define` that MrDocs compiles, so nothing attached it. `ref_macros.adoc` has linked to that page all along, and the link rendered as a literal `href="#reference:BOOST_OPENMETHOD_REGISTER_CLASSES.adoc"`. Moving the comment down to the fallback definition produces the page. Rendering those comments for the first time exposed three markup traps, none of which asciidoctor or MrDocs reports: * a line starting with `- ` becomes a list item, so the em-dash before "pass it explicitly" split the paragraph and opened a stray bullet; * `@ref` inside `**bold**` breaks the span, leaving literal asterisks - the options bullet now bolds plain text, like its three siblings; * an inline `` `^^::` `` loses both carets and renders as `::`. Escaping the first, `` `\^^::` ``, comes through intact. Found by building the page with eight candidate spellings; only that one survives. `registries_and_policies.adoc` pointed at `xref:reference:use_classes.adoc`, which resolves only for macros - MrDocs puts those at the top level and namespace-scoped symbols under `reference/boost/openmethod/`. It is now `cpp:use_classes[]`, and the prose that names the new symbols links to them the same way. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- CLAUDE.md | 53 ++++++- doc/modules/ROOT/pages/basics.adoc | 12 +- doc/modules/ROOT/pages/core_api.adoc | 8 +- .../ROOT/pages/registries_and_policies.adoc | 4 +- include/boost/openmethod/core.hpp | 130 +++++++++++------- .../boost/openmethod/detail/reflection.hpp | 37 ++++- include/boost/openmethod/macros.hpp | 19 +-- 7 files changed, 182 insertions(+), 81 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2122b4d8..5789ce4c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -317,13 +317,60 @@ Two rules keep the reference clean; the long-form version lives next to the macr Between them these cover every constraint in the library, so **do not declare a member twice**, an unqualified copy under `#ifdef __MRDOCS__` beside the real one. Only MrDocs ever compiles that copy, so the two drift apart silently and the reference then documents a constraint the library -does not have. The `#ifdef __MRDOCS__` blocks that remain remove declarations from the reference -(friends, deleted overloads, the `VirtualTraits` blueprint) rather than restate them. +does not have. Most of the `#ifdef __MRDOCS__` blocks therefore *remove* declarations from the +reference (friends, deleted overloads) or describe something that has no real counterpart at all +(the `VirtualTraits` blueprint). Only expressions are affected: types are printed from the AST, so the macro may appear anywhere in one (`method::operator()` takes `typename BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) StripVirtualDecorator::type...` and -renders correctly). After a doc build, `grep -rl MRDOCS doc/html/` must return nothing. +renders correctly). + +**The C++26 reflection API is the one exception, and it is a forced one.** MrDocs' front-end does +not implement P2996, so the reference build runs at `-D CMAKE_CXX_STANDARD=20` and +`BOOST_OPENMETHOD_HAS_REFLECTION` is 0 there: every `#if BOOST_OPENMETHOD_HAS_REFLECTION` block is +invisible to it. `register_classes` and `current_namespace` are therefore restated as +documentation stubs in an `#elif defined(__MRDOCS__)` branch at the end of `core.hpp`, and +`detail/reflection.hpp` forward-declares `std::meta::info` and `std::meta::access_context` under +`#ifdef __MRDOCS__` so those stubs can spell their real signatures. Two rules contain the drift: + +- **Each doc comment exists exactly once, on the stub.** The real declaration carries only + `//! @see @ref for documentation.`, as `inplace_vptr_derived` already does. Never copy + a doc comment into both branches. +- **`register_classes_opts` is not stubbed.** It is plain C++17, so its guard is widened to + `#if BOOST_OPENMETHOD_HAS_REFLECTION || defined(__MRDOCS__)` and the real definition is what + MrDocs reads. Prefer widening a guard to writing a stub whenever the code parses as C++20. + +The `register_classes` stub is spelled `template` where the real one is +`template` - the split exists only because a +pack cannot carry a default argument, and MrDocs prints a default argument as raw source text, so +the real spelling would leak `detail::` into the reference. + +**A macro defined in both branches of an `#if` must carry its doc comment on the `#else` one.** +MrDocs compiles that branch, and a comment separated from its `#define` by preprocessor directives +is not attached to it - `BOOST_OPENMETHOD_REGISTER_CLASSES` silently produced no page at all until +its comment was moved down. Symptom to watch for: a `xref:reference:.adoc` that renders as a +literal `href="#reference:.adoc"`. + +### Doc-comment markup traps + +MrDocs parses `//!` comments as Markdown plus Doxygen commands, then emits AsciiDoc. Three shapes +mis-render silently; all three were found by rendering, none by reading the source: + +- **A line starting with `- ` becomes a list item.** House style uses ` - ` as an em-dash, which is + fine mid-line but starts a stray bullet at the head of one. Rewrap so the dash never begins a + line, or reword to a semicolon. +- **`@ref` inside `**bold**` breaks the span**, leaving literal `**` in the output. Bold plain text + only: `**Options**: at most one @ref ... value`, not `**One @ref ... value**`. +- **An inline `` `^^::` `` loses both carets** and renders as `::`. Escape the first one - + `` `\^^::` `` - which comes through as `^^::`. Only this spelling is affected; `` `^^app` `` and + `^^::` inside an `@code` block are fine. + +An `xref:reference:.adoc` path works only for macros, which MrDocs puts at the top level. +A namespace-scoped symbol lives under `reference/boost/openmethod/`, so link it with +`cpp:[]`, which resolves wherever the page ends up. + +After a doc build, `grep -rl MRDOCS doc/html/` must return nothing. ## Common Development Patterns diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index 95a9fbea..58af4cef 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -150,8 +150,8 @@ 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 +cpp:register_classes_opts[] options with `|`; and reflections of registries to +register the classes in - cpp: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 @@ -167,10 +167,10 @@ 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. +program never heard of anyway. The cpp:register_classes_opts::scan_std[] and +cpp:register_classes_opts::scan_boost[] options bring them back in; a namespace +*listed* explicitly is always scanned. cpp: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 f1b3efc0..856257fb 100644 --- a/doc/modules/ROOT/pages/core_api.adoc +++ b/doc/modules/ROOT/pages/core_api.adoc @@ -78,10 +78,10 @@ We register the classes with `use_classes`: include::{example}/core_api.cpp[tag=use_classes] ---- -With a compiler that supports C++26 reflection, `register_classes` replaces that -list. It scans one or more namespaces for the registry's methods, and registers -the classes they dispatch on along with everything in those namespaces that -derives from them: +With a compiler that supports C++26 reflection, cpp: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++] ---- diff --git a/doc/modules/ROOT/pages/registries_and_policies.adoc b/doc/modules/ROOT/pages/registries_and_policies.adoc index bfd16427..01d158b5 100644 --- a/doc/modules/ROOT/pages/registries_and_policies.adoc +++ b/doc/modules/ROOT/pages/registries_and_policies.adoc @@ -96,8 +96,8 @@ 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 +cpp: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]. diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 455e94c8..1fc36837 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -3116,6 +3116,68 @@ BOOST_FORCEINLINE auto use_reflected_classes_in() -> void { #if BOOST_OPENMETHOD_HAS_REFLECTION +// MrDocs' front-end does not implement P2996, so it never sees this branch. +// The reference documentation for `current_namespace` and `register_classes` +// is on the stubs in the `#elif` branch below; keep the two in step. + +//! @see @ref current_namespace for documentation. +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; +} + +//! @see @ref register_classes for documentation. +template< + auto First = + detail::scope_marker{std::meta::access_context::current().scope()}, + auto... Rest> +class register_classes { + static_assert( + detail::register_classes_args_are_valid(), + "arguments must be reflections of namespaces, classes or registries, " + "or one register_classes_opts value"); + static_assert( + detail::register_classes_args_are_ordered(), + "order the arguments as namespaces, classes, options, registries"); + static_assert( + detail::register_classes_option_count() <= 1u, + "combine the options into a single register_classes_opts argument with " + "|"); + static_assert( + detail::register_classes_has_scan_source(), + "the enclosing namespace cannot be captured here; pass a namespace, " + "or current_namespace(), or use BOOST_OPENMETHOD_REGISTER_CLASSES"); + + using found_registries = + detail::register_classes_registries; + using registries = mp11::mp_if< + mp11::mp_empty, + mp11::mp_list, found_registries>; + + template + static auto use(mp11::mp_list*) -> void { + (..., detail::use_reflected_classes_in()); + } + + public: + register_classes() { + use(static_cast(nullptr)); + } +}; + +#elif defined(__MRDOCS__) + +// Documentation stubs. MrDocs compiles this branch instead of the real +// declarations above, which its front-end cannot parse. The `std::meta` +// stand-ins they use are declared in detail/reflection.hpp. + //! Get the current namespace. //! //! Returns a reflection of the namespace enclosing the point of the call. Use @@ -3128,15 +3190,7 @@ BOOST_FORCEINLINE auto use_reflected_classes_in() -> void { //! 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; -} + -> std::meta::info; //! Find the classes taking part in dispatch by reflection, and register them //! @@ -3147,20 +3201,21 @@ consteval auto current_namespace( //! The arguments are non-type template arguments, in four groups, each //! optional, in this order: //! -//! @li **Namespaces** to scan, as reflections: `^^app`, `^^::`. +//! @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 **Options**: at most one @ref register_classes_opts value, combining +//! them 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 +//! @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 @@ -3170,8 +3225,8 @@ consteval auto current_namespace( //! 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` 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 @@ -3197,46 +3252,15 @@ consteval auto current_namespace( //! This class template is available only if the compiler supports C++26 //! reflection, i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. //! -//! @tparam First,Rest Reflections of namespaces, classes and registries, and -//! at most one @ref register_classes_opts value, in that order. +//! @tparam Args Reflections of namespaces, classes and registries, and at +//! most one @ref register_classes_opts value, in that order. //! //! @see [Core API](xref:ROOT:core_api.adoc) -template< - auto First = - detail::scope_marker{std::meta::access_context::current().scope()}, - auto... Rest> +template class register_classes { - static_assert( - detail::register_classes_args_are_valid(), - "arguments must be reflections of namespaces, classes or registries, " - "or one register_classes_opts value"); - static_assert( - detail::register_classes_args_are_ordered(), - "order the arguments as namespaces, classes, options, registries"); - static_assert( - detail::register_classes_option_count() <= 1u, - "combine the options into a single register_classes_opts argument with " - "|"); - static_assert( - detail::register_classes_has_scan_source(), - "the enclosing namespace cannot be captured here; pass a namespace, " - "or current_namespace(), or use BOOST_OPENMETHOD_REGISTER_CLASSES"); - - using found_registries = - detail::register_classes_registries; - using registries = mp11::mp_if< - mp11::mp_empty, - mp11::mp_list, found_registries>; - - template - static auto use(mp11::mp_list*) -> void { - (..., detail::use_reflected_classes_in()); - } - public: - register_classes() { - use(static_cast(nullptr)); - } + //! Register the selected classes in each target registry. + register_classes(); }; #endif diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index d745b32e..ffab9a33 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -21,11 +21,10 @@ #define BOOST_OPENMETHOD_HAS_REFLECTION 0 #endif -#if BOOST_OPENMETHOD_HAS_REFLECTION - -#include - -#include +// The option namespace below is plain C++17 - nothing in it is a reflection - +// so it is also compiled for MrDocs, which cannot parse the rest of this +// header. See the `#ifdef __MRDOCS__` block further down. +#if BOOST_OPENMETHOD_HAS_REFLECTION || defined(__MRDOCS__) namespace boost::openmethod { @@ -62,6 +61,34 @@ constexpr auto operator|(opts a, opts b) -> opts { } // namespace boost::openmethod +#endif + +#ifdef __MRDOCS__ + +// MrDocs' front-end does not implement P2996, so the reflection API cannot be +// parsed and is documented on stubs instead - see the `#elif defined(__MRDOCS__)` +// branch at the end of core.hpp. The declarations below exist only so those +// stubs can spell their real signatures; `include-symbols` in mrdocs.yml keeps +// them out of the reference. They are never seen by a real compiler. +namespace std::meta { + +struct info; + +class access_context { + public: + static auto current() -> access_context; +}; + +} // namespace std::meta + +#endif + +#if BOOST_OPENMETHOD_HAS_REFLECTION + +#include + +#include + namespace boost::openmethod::detail { consteval auto has_opt( diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 3566ffce..1abe2a21 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -589,6 +589,17 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>) +// The reference documentation for this macro is on the fallback definition +// below. MrDocs compiles the `#else` branch, and a doc comment separated +// from its `#define` by preprocessor directives is not attached to it. +#if BOOST_OPENMETHOD_HAS_REFLECTION +#define BOOST_OPENMETHOD_REGISTER_CLASSES(...) \ + BOOST_OPENMETHOD_REGISTER( \ + ::boost::openmethod::register_classes< \ + ::boost::openmethod::detail::scope_marker{ \ + ::std::meta::access_context::current().scope() } __VA_OPT__( \ + , ) __VA_ARGS__>) +#else //! Find the classes taking part in dispatch by reflection, and register them. //! //! It makes @ref BOOST_OPENMETHOD_CLASSES unnecessary in most cases. @@ -626,14 +637,6 @@ inline constexpr bool method_not_found = false; //! @param ... Namespaces, classes, options, registries - see above. //! //! @see [Methods and Overriders](xref:ROOT:basics.adoc) -#if BOOST_OPENMETHOD_HAS_REFLECTION -#define BOOST_OPENMETHOD_REGISTER_CLASSES(...) \ - BOOST_OPENMETHOD_REGISTER( \ - ::boost::openmethod::register_classes< \ - ::boost::openmethod::detail::scope_marker{ \ - ::std::meta::access_context::current().scope() } __VA_OPT__( \ - , ) __VA_ARGS__>) -#else #define BOOST_OPENMETHOD_REGISTER_CLASSES(...) static_assert(true) #endif From 72941910ca0db726248e8f0a3c8ee72f341f4201 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 31 Aug 2026 18:59:40 -0400 Subject: [PATCH 10/19] reflection: group the register_classes arguments, drop the scan options `register_classes` took a flat list of reflections, sorted by kind and required to appear in canonical order. The arguments are now groups: register_classes<{^^zoo, ^^pets}, {^^Animal}, {^^r1, ^^r2}> A group holds one kind of reflection - the namespaces to scan, the classes to register, the registries to register them in, in that order - and a group of one needs no braces, so `register_classes<^^zoo, ^^r1>` still means what it did. Groups are still identified by what they hold rather than by position, so any of them may be left out: `register_classes<{^^r1}>` names only registries. The argument type, `detail::reflection_group`, is a class-type non-type template parameter with a deduced-class-type placeholder, deducing its length from the braced template argument through CTAD (P2308). A pack of them replaces the old `template`; `auto` cannot be deduced from a braced-init-list ("unable to deduce 'auto' from '{1, 2}'"). Its converting constructor is what lets a lone reflection stand for a group of one, `consteval` because `std::meta::info` is a consteval-only type, and constrained so that it cannot be picked over the copy constructor. None of this is P2996: with `int` in place of `std::meta::info` the same shape compiles clean on gcc 13, 15 and 16 and on clang 18 and 22 at `-std=c++20` with `-pedantic-errors`, and on MSVC v18 at `/std:c++20` with `/W4 /permissive-`. The options are gone with `register_classes_opts`, `has_opt` and the `opts` parameter threaded through the scan. A scan now always recurses, and never descends into `std` or `boost`; a namespace *listed* explicitly is scanned whatever it is, which is how a class in either gets registered, and was already true before. `no_recurse` bought little for a mechanism whose cost is dominated by the namespaces it is pointed at. With no namespace group the scan covers `^^::`, where it used to cover the namespace enclosing the registrar. Listing classes no longer turns the scan off: they are extra roots, and the classes deriving from them are registered too. `current_namespace()` survives as the way to narrow a scan to one namespace, which is the only thing that still needs it. That default is what removes `detail::scope_marker` and the `scope` member each group used to carry - a default member initializer calling `access_context::current()`, evaluated at the initialization site. Without it `reflection_group` needs no aggregate-ness, which is what made the converting constructor above possible, and `register_classes` needs no `First`/`Rest` split: an empty pack already covers `register_classes<>`. Nothing separates `BOOST_OPENMETHOD_REGISTER_CLASSES` from the bare template now except its expanding to nothing without reflection. Three static_asserts diagnose an argument list, ordered so that one mistake yields one message: an unknown reflection, then a group mixing two kinds, then groups out of order. `compile_fail_reflection_mixed_group.cpp` covers the second; `compile_fail_reflection_no_scan_source.cpp` goes, as a registry on its own is now a scan of `^^::` rather than an error. `.clang-format` loses `TemplateNames: register_classes`. It was added so that the flat spelling parsed as a template-id, and it is what turns a braced argument into `{^^a }`; without it both spellings come out right, and clang-format 18 can read the file again. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- .clang-format | 2 - CLAUDE.md | 17 +- doc/modules/ROOT/pages/basics.adoc | 42 +- doc/modules/ROOT/pages/core_api.adoc | 8 +- include/boost/openmethod/core.hpp | 375 +++++++++--------- .../boost/openmethod/detail/reflection.hpp | 121 ++---- include/boost/openmethod/macros.hpp | 18 +- test/compile_fail_reflection_arg_order.cpp | 8 +- ...> compile_fail_reflection_mixed_group.cpp} | 14 +- 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 | 210 ++++++---- 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, 456 insertions(+), 431 deletions(-) rename test/{compile_fail_reflection_no_scan_source.cpp => compile_fail_reflection_mixed_group.cpp} (61%) diff --git a/.clang-format b/.clang-format index 9e499faf..194cddeb 100644 --- a/.clang-format +++ b/.clang-format @@ -133,6 +133,4 @@ StatementMacros: TabWidth: 8 UseCRLF: false UseTab: Never -TemplateNames: - - register_classes ... diff --git a/CLAUDE.md b/CLAUDE.md index 5789ce4c..ef9492a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -337,14 +337,15 @@ documentation stubs in an `#elif defined(__MRDOCS__)` branch at the end of `core - **Each doc comment exists exactly once, on the stub.** The real declaration carries only `//! @see @ref for documentation.`, as `inplace_vptr_derived` already does. Never copy a doc comment into both branches. -- **`register_classes_opts` is not stubbed.** It is plain C++17, so its guard is widened to - `#if BOOST_OPENMETHOD_HAS_REFLECTION || defined(__MRDOCS__)` and the real definition is what - MrDocs reads. Prefer widening a guard to writing a stub whenever the code parses as C++20. - -The `register_classes` stub is spelled `template` where the real one is -`template` - the split exists only because a -pack cannot carry a default argument, and MrDocs prints a default argument as raw source text, so -the real spelling would leak `detail::` into the reference. +- **Prefer widening a guard to writing a stub** whenever the code parses as C++20: a declaration + that is plain C++17 or C++20 can be guarded with + `#if BOOST_OPENMETHOD_HAS_REFLECTION || defined(__MRDOCS__)`, and MrDocs then reads the real + definition instead of a copy that can drift. + +The `register_classes` stub is spelled `template` where the real one is +`template`. The stub drops the group type, which is an +implementation detail the reference has no reason to name - its braces are the only thing a +caller writes. **A macro defined in both branches of an `#if` must carry its doc comment on the `#else` one.** MrDocs compiles that branch, and a comment separated from its `#define` by preprocessor directives diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index 58af4cef..5dce8a8a 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -139,7 +139,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream& os, Dog&), void) { os << "bark"; } -BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); // registers all four classes +BOOST_OPENMETHOD_REGISTER_CLASSES(); // registers all four classes ---- The macro scans the namespaces it is given - and the namespaces nested in them @@ -148,15 +148,26 @@ 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 -cpp:register_classes_opts[] options with `|`; and reflections of registries to -register the classes in - cpp: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. +The arguments are groups of reflections, each optional, in this order: the +namespaces to scan; the classes to register; and the registries to register the +classes in - cpp:default_registry[] when none is named. A group holds one kind +of reflection, and is written in braces - or, for a group of one, as the +reflection itself: + +[source,c++] +---- +BOOST_OPENMETHOD_REGISTER_CLASSES({^^zoo, ^^pets}, {^^my_registry}); +BOOST_OPENMETHOD_REGISTER_CLASSES(^^zoo, ^^my_registry); +---- + +With no namespace group - `BOOST_OPENMETHOD_REGISTER_CLASSES()`, or any other +group on its own, `{^^my_registry}` included - the global namespace is scanned. +Use cpp:current_namespace[] to scan only the namespace the registration sits +in. + +A listed class is registered whether a method dispatches on it or not, and it +is one more root for the scan: every class the scan finds deriving from it is +registered too, with the inheritance relations read from reflection. 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 @@ -165,12 +176,11 @@ 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 cpp:register_classes_opts::scan_std[] and -cpp:register_classes_opts::scan_boost[] options bring them back in; a namespace -*listed* explicitly is always scanned. cpp:register_classes_opts::no_recurse[] -restricts the scan to the members declared directly in the listed namespaces. +A scan does not descend into the `std` and `boost` namespaces, so scanning the +global namespace costs little more than a narrower one would - a method cannot +dispatch on a class the program never heard of anyway. The exclusion applies to +recursion only: a namespace *listed* explicitly is always scanned, which is how +a class in `std` or `boost` gets registered. 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 856257fb..396a620f 100644 --- a/doc/modules/ROOT/pages/core_api.adoc +++ b/doc/modules/ROOT/pages/core_api.adoc @@ -79,13 +79,13 @@ include::{example}/core_api.cpp[tag=use_classes] ---- With a compiler that supports C++26 reflection, cpp: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: +that list. It scans the global namespace - or the ones it is given - for the +registry's methods, and registers the classes they dispatch on along with +everything in those namespaces that derives from them: [source,c++] ---- -BOOST_OPENMETHOD_REGISTER(register_classes<^^::>); +BOOST_OPENMETHOD_REGISTER(register_classes<>); ---- A method declared the way `postfix` is above - an alias for a `method` diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 1fc36837..0d07b0ec 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -2812,14 +2812,13 @@ struct method_traits_aux> { template using method_classes = typename method_traits_aux::type; -// How `register_classes` interprets one of its template arguments. -enum class register_classes_arg : unsigned char { - invalid, - scope, // a scope_marker - the use site's enclosing scope - namespace_, // a namespace to scan - class_, // a class to register - options, // a register_classes_opts value - registry, // a registry to add the classes to +// How `register_classes` interprets an argument group, and the items in it. +enum class register_classes_kind : unsigned char { + invalid, // not a reflection register_classes accepts, or a mixed group + empty, // `{}` - carries no kind, and takes no part in the ordering + namespaces, // namespaces to scan + classes, // classes to register + registries, // registries to add the classes to }; consteval auto is_registry_type(std::meta::info type) -> bool { @@ -2829,103 +2828,144 @@ consteval auto is_registry_type(std::meta::info type) -> bool { type})); } -template -consteval auto classify_register_classes_arg() -> register_classes_arg { - using Type = decltype(Arg); +// How `register_classes` interprets one item of one of its argument groups. +consteval auto register_classes_item_kind(std::meta::info item) + -> register_classes_kind { + auto entity = std::meta::dealias(item); - 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_kind::namespaces; + } - 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_kind::registries; } - 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_kind::classes; + } - return register_classes_arg::class_; + return register_classes_kind::invalid; +} + +// True if the group holds an item that is not a reflection `register_classes` +// accepts. The two checks below let such a group pass, so a mistake yields one +// diagnostic instead of three. +template +consteval auto register_classes_group_has_unknown() -> bool { + for (auto index = 0u; index != Group.size; ++index) { + if (register_classes_item_kind(Group.items[index]) == + register_classes_kind::invalid) { + return true; } + } - return register_classes_arg::invalid; - } else { - return register_classes_arg::invalid; + return false; +} + +// The kind of the items in `Group`; `invalid` if they are not all of the same +// kind, or if one of them is not a reflection `register_classes` accepts. +template +consteval auto register_classes_group_kind() -> register_classes_kind { + auto kind = register_classes_kind::empty; + + for (auto index = 0u; index != Group.size; ++index) { + auto item = register_classes_item_kind(Group.items[index]); + + if (item == register_classes_kind::invalid) { + return register_classes_kind::invalid; + } + + if (kind != register_classes_kind::empty && item != kind) { + return register_classes_kind::invalid; + } + + kind = item; } + + return kind; } -template -consteval auto register_classes_args_are_valid() -> bool { +template +consteval auto register_classes_groups_are_valid() -> bool { + return (... && !register_classes_group_has_unknown()); +} + +template +consteval auto register_classes_groups_are_homogeneous() -> bool { return ( ... && - (classify_register_classes_arg() != - register_classes_arg::invalid)); + (register_classes_group_has_unknown() || + register_classes_group_kind() != + register_classes_kind::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()...}; +// The groups must come in the order the `register_classes_kind` enumerators +// are declared in: namespaces, classes, registries. +template +consteval auto register_classes_groups_are_ordered() -> bool { + register_classes_kind kinds[] = {register_classes_group_kind()...}; + auto last = register_classes_kind::empty; + + for (auto kind : kinds) { + // An empty group carries no kind, and a group the checks above + // rejected has no meaningful one either. + if (kind == register_classes_kind::empty || + kind == register_classes_kind::invalid) { + continue; + } - for (auto index = 1u; index != sizeof...(Args); ++index) { - if (kinds[index] < kinds[index - 1]) { + if (kind < last) { return false; } + + last = kind; } return true; } -template -consteval auto register_classes_option_count() -> unsigned { - return ( - 0u + ... + - (classify_register_classes_arg() == - register_classes_arg::options)); -} +// The items of every group whose kind is `Kind`, in order, without repeats. +template +consteval auto register_classes_items(register_classes_kind kind) + -> std::vector { + std::vector items; -template -consteval auto register_classes_has_scan_source() -> bool { - return ( - ... || - (classify_register_classes_arg() <= - register_classes_arg::class_)); -} + (..., [&] { + for (auto index = 0u; index != Groups.size; ++index) { + auto item = Groups.items[index]; -// `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<>; -}; + if (register_classes_item_kind(item) == kind) { + push_unique(items, std::meta::dealias(item)); + } + } + }()); -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 -}; + return items; +} -template -using register_classes_registries = mp11::mp_append< - mp11::mp_list<>, typename register_classes_registry::type...>; +template +consteval auto register_classes_registries_info() -> std::meta::info { + return std::meta::substitute( + ^^mp11::mp_list, + register_classes_items(register_classes_kind::registries)); +} + +// `mp_list` for the registries the groups name. +// +// clang-format off: the formatter predates P2996 and eats the spaces around +// the splice, leaving `typename[:...:]`. +template +using register_classes_registries = + typename [: register_classes_registries_info() :]; +// clang-format on -// 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 +// The classes to register for the argument groups `Groups`, each with its +// direct bases: the classes the methods of `Registry` dispatch on, the classes +// the groups list, 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. // @@ -2933,50 +2973,30 @@ using register_classes_registries = mp11::mp_append< // scan produces. A scan of the global namespace reaches every class in the // program that is not in `std` or `boost`, and instantiating a trait once per // pair of them costs far more than walking their base classes does. -template +template consteval auto reflected_registered_classes_info() -> std::meta::info { - // Partition the arguments. Registries take no part here: the caller calls + // Partition the groups. 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; - } - }()); + auto namespaces = + register_classes_items(register_classes_kind::namespaces); + auto virtual_classes = + register_classes_items(register_classes_kind::classes); - // 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); - } + for (auto& type : virtual_classes) { + type = std::meta::remove_cv(type); + } - namespaces.push_back(scope); + // With no namespace to start from, scan the global namespace. Listing + // classes does not change that: they are extra roots for the scan, not a + // way to turn it off. + if (namespaces.empty()) { + namespaces.push_back(^^::); } std::vector methods, classes; for (auto ns : namespaces) { - scan_namespace(ns, ^^method, methods, classes, opts); + scan_namespace(ns, ^^method, methods, classes); } // Add the classes the methods dispatch on. @@ -3094,18 +3114,17 @@ 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 +// Register the classes the groups select 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; + using registered = reflected_registered_classes; use_reflected_classes(static_cast(nullptr)); } } @@ -3134,36 +3153,27 @@ consteval auto current_namespace( } //! @see @ref register_classes for documentation. -template< - auto First = - detail::scope_marker{std::meta::access_context::current().scope()}, - auto... Rest> +template 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"); + detail::register_classes_groups_are_valid(), + "a group holds reflections of namespaces, classes or registries"); static_assert( - detail::register_classes_option_count() <= 1u, - "combine the options into a single register_classes_opts argument with " - "|"); + detail::register_classes_groups_are_homogeneous(), + "a group holds one kind of reflection; put the namespaces, the " + "classes and the registries in groups of their own"); 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"); + detail::register_classes_groups_are_ordered(), + "order the groups as namespaces, classes, registries"); - using found_registries = - detail::register_classes_registries; + 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()); + (..., detail::use_reflected_classes_in()); } public: @@ -3181,10 +3191,13 @@ class register_classes { //! Get the current namespace. //! //! Returns a reflection of the namespace enclosing the point of the call. Use -//! it to pass the current namespace to @ref register_classes when the argument -//! list contains no namespace to scan, e.g. -//! `register_classes`. Do not pass an -//! argument: the default captures the caller's context. +//! it to narrow a scan to that namespace, which @ref register_classes does not +//! do on its own: with no namespace listed it scans the global one. Do not +//! pass an argument: the default captures the caller's context. +//! +//! @code +//! register_classes<{current_namespace()}, {^^my_registry}> +//! @endcode //! //! This function is available only if the compiler supports C++26 reflection, //! i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. @@ -3198,40 +3211,46 @@ consteval auto current_namespace( //! 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 **Options**: at most one @ref register_classes_opts value, combining -//! them with `|`. -//! @li **Registries** to register the classes in, as reflections: -//! `^^my_registry`. Each one receives the registration. The default is +//! The arguments are groups of reflections, each written in braces - or, for a +//! group of one, as the reflection itself. A group holds one kind of +//! reflection, and the groups come in this order; each one is optional, and +//! omitting all of them is the common case: +//! +//! @li **Namespaces** to scan: `{^^app, ^^zoo}`, `{\^^::}`. +//! @li **Classes** to register: `{^^Animal}`. They are registered whether a +//! method dispatches on them or not, and they are extra roots for the scan, +//! which registers the classes it finds deriving from them. +//! @li **Registries** to register the classes in: `{^^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. +//! @code +//! register_classes<^^app, ^^my_registry> +//! register_classes<{^^app, ^^zoo}, {^^r1, ^^r2}> +//! register_classes<{^^Animal, ^^Dog}> +//! register_classes<^^my_registry> +//! register_classes<> +//! @endcode +//! +//! A group holding two kinds of reflection is an error, and so is a group out +//! of order. Braces around a single reflection change nothing: +//! `register_classes<^^app>` and `register_classes<{^^app}>` are the same +//! registration. +//! +//! The scan covers the listed namespaces and the namespaces nested in them, +//! except `std` and `boost`: walking those would cost a great deal and find +//! nothing, as a method cannot be declared on a class the program has never +//! heard of. The exclusion applies to recursion only, so a class in `std` or +//! `boost` is registered by *listing* the namespace it is in. 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 is given, the global namespace is scanned - whatever the +//! other groups hold, and for @ref BOOST_OPENMETHOD_REGISTER_CLASSES too. Use +//! @ref current_namespace to scan only the namespace the registrar is in. //! //! 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. @@ -3252,11 +3271,11 @@ consteval auto current_namespace( //! This class template is available only if the compiler supports C++26 //! reflection, i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. //! -//! @tparam Args Reflections of namespaces, classes and registries, and at -//! most one @ref register_classes_opts value, in that order. +//! @tparam Groups Braced groups of reflections: namespaces, classes, +//! registries, in that order. //! //! @see [Core API](xref:ROOT:core_api.adoc) -template +template class register_classes { public: //! Register the selected classes in each target registry. diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index ffab9a33..72fbb384 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -21,48 +21,6 @@ #define BOOST_OPENMETHOD_HAS_REFLECTION 0 #endif -// The option namespace below is plain C++17 - nothing in it is a reflection - -// so it is also compiled for MrDocs, which cannot parse the rest of this -// header. See the `#ifdef __MRDOCS__` block further down. -#if BOOST_OPENMETHOD_HAS_REFLECTION || defined(__MRDOCS__) - -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 - -#endif - #ifdef __MRDOCS__ // MrDocs' front-end does not implement P2996, so the reflection API cannot be @@ -85,25 +43,41 @@ class access_context { #if BOOST_OPENMETHOD_HAS_REFLECTION +#include +#include #include #include 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; +// One argument of `register_classes`: a group of reflections, written in +// braces - or, for a group of one, as the reflection itself, which the +// converting constructor turns into a group of one. +// +// The constructor is constrained, so that it cannot be picked over the copy +// constructor, and `consteval`, as `std::meta::info` is a consteval-only type. +// +// `Size` is deduced from the number of items; the array is never empty, as +// `{}` must produce a valid type too. +template +struct reflection_group { + std::meta::info items[Size ? Size : 1]{}; + std::size_t size = Size; + + consteval reflection_group() = default; + + template + requires(... && std::is_same_v) + consteval reflection_group(T... items_) : + items{items_...}, size(sizeof...(T)) { + } }; +template +reflection_group(T...) -> reflection_group; +reflection_group() -> reflection_group<0>; + // ============================================================================= // base classes @@ -167,25 +141,16 @@ using reflected_bases = typename [: reflected_bases_info() :]; // namespace scan // True if `member` is a namespace that a recursive scan does not enter: `std` -// and `boost`, unless the options say otherwise. Walking them would cost a -// great deal and find nothing: a method cannot be declared on a class the -// program has never heard of. The nested namespaces - `std::chrono`, -// `boost::mp11`, the inline versioning ones - are reached only through their -// parent, so they are left out with it. The exclusion applies only to -// recursion: a namespace listed explicitly is always scanned. -consteval auto is_excluded_namespace( - std::meta::info member, register_classes_opts::opts opts) -> bool { +// and `boost`. 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, which is how a class in `std` or `boost` is registered. +consteval auto is_excluded_namespace(std::meta::info member) -> 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; + return ns == ^^::std || ns == ^^::boost; } consteval auto contains( @@ -241,24 +206,22 @@ consteval auto specialization_named_by(std::meta::info member) return std::meta::info(); } -// Walk `ns` and, unless `opts` says `no_recurse`, the namespaces nested in it, -// collecting the specializations of `Template` that its members name, and the -// complete class types they declare. Nothing else is retained: the scan of a -// large namespace must not build a list of everything in it. +// 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, register_classes_opts::opts opts) { + std::vector& classes) { // A named local, for the reason given in `collect_reflected_bases`. auto members = std::meta::members_of(ns, std::meta::access_context::unchecked()); for (auto member : members) { if (std::meta::is_namespace(member)) { - if (!has_opt(opts, register_classes_opts::no_recurse) && - !is_excluded_namespace(member, opts)) { - scan_namespace( - member, Template, specializations, classes, opts); + if (!is_excluded_namespace(member)) { + scan_namespace(member, Template, specializations, classes); } continue; diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 1abe2a21..c489c398 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -595,10 +595,7 @@ inline constexpr bool method_not_found = false; #if BOOST_OPENMETHOD_HAS_REFLECTION #define BOOST_OPENMETHOD_REGISTER_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER( \ - ::boost::openmethod::register_classes< \ - ::boost::openmethod::detail::scope_marker{ \ - ::std::meta::access_context::current().scope() } __VA_OPT__( \ - , ) __VA_ARGS__>) + ::boost::openmethod::register_classes<__VA_ARGS__>) #else //! Find the classes taking part in dispatch by reflection, and register them. //! @@ -606,11 +603,10 @@ inline constexpr bool method_not_found = false; //! //! 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. +//! through verbatim: groups of reflections, each written in braces - or, for a +//! group of one, as the reflection itself - holding the namespaces to scan, +//! the classes to register, and the registries; each group optional, in that +//! order. With no namespace group, the global 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: @@ -627,14 +623,14 @@ inline constexpr bool method_not_found = false; //! os << "bark"; //! } //! -//! BOOST_OPENMETHOD_REGISTER_CLASSES(^^::); // 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 ... Namespaces, classes, options, registries - see above. +//! @param ... Braced groups: namespaces, classes, registries - see above. //! //! @see [Methods and Overriders](xref:ROOT:basics.adoc) #define BOOST_OPENMETHOD_REGISTER_CLASSES(...) static_assert(true) diff --git a/test/compile_fail_reflection_arg_order.cpp b/test/compile_fail_reflection_arg_order.cpp index bff8b3a3..85288a39 100644 --- a/test/compile_fail_reflection_arg_order.cpp +++ b/test/compile_fail_reflection_arg_order.cpp @@ -4,7 +4,7 @@ // 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 +// expected-error: order the groups as namespaces, classes, registries #include @@ -12,7 +12,7 @@ // 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 +#error order the groups as namespaces, classes, registries #else @@ -24,9 +24,9 @@ struct Animal { virtual ~Animal() = default; }; -// The registry must come last. +// The registry group must come last. BOOST_OPENMETHOD_REGISTER( - boost::openmethod::register_classes<^^app::my_registry, ^^app::Animal>); + boost::openmethod::register_classes<{^^app::my_registry}, {^^app::Animal}>); } // namespace app diff --git a/test/compile_fail_reflection_no_scan_source.cpp b/test/compile_fail_reflection_mixed_group.cpp similarity index 61% rename from test/compile_fail_reflection_no_scan_source.cpp rename to test/compile_fail_reflection_mixed_group.cpp index 14ba74f7..a314a30d 100644 --- a/test/compile_fail_reflection_no_scan_source.cpp +++ b/test/compile_fail_reflection_mixed_group.cpp @@ -4,7 +4,7 @@ // 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 +// expected-error: a group holds one kind of reflection #include @@ -12,7 +12,7 @@ // 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 +#error a group holds one kind of reflection #else @@ -20,11 +20,13 @@ 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. +struct Animal { + virtual ~Animal() = default; +}; + +// A class and a registry cannot share a group. BOOST_OPENMETHOD_REGISTER( - boost::openmethod::register_classes<^^app::my_registry>); + boost::openmethod::register_classes<{^^app::Animal, ^^app::my_registry}>); } // namespace app diff --git a/test/test_custom_rtti_deferred.cpp b/test/test_custom_rtti_deferred.cpp index 61935c4e..365197f6 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_custom_rtti_simple.cpp b/test/test_custom_rtti_simple.cpp index 5c48f2a8..6009d1df 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_custom_rtti_simple_projection.cpp b/test/test_custom_rtti_simple_projection.cpp index 8cb911da..96a2502c 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_custom_rtti_virtual_base.cpp b/test/test_custom_rtti_virtual_base.cpp index e4c62363..0b000600 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_across_namespaces.cpp b/test/test_dispatch_across_namespaces.cpp index aaf6dc51..9b1d234a 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_boost_any.cpp b/test/test_dispatch_boost_any.cpp index 8c575a5f..7d696d2b 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_REGISTER_CLASSES(^^::); +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 4c8f7ba8..41d17dac 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_intrusive_ptr.cpp b/test/test_dispatch_intrusive_ptr.cpp index 7ff45b3c..10900846 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_lvalue_refs.cpp b/test/test_dispatch_lvalue_refs.cpp index 1881aa63..8db87ede 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_multi.cpp b/test/test_dispatch_multi.cpp index 49bd773b..0c198afd 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_next_fn.cpp b/test/test_dispatch_next_fn.cpp index 2141248c..f488822f 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_pointer.cpp b/test/test_dispatch_pointer.cpp index 950fd110..dc7b73cb 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_rvalue_refs.cpp b/test/test_dispatch_rvalue_refs.cpp index 9172263d..f333b1be 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_REGISTER_CLASSES(^^::); +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 699e21c8..00414140 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_REGISTER_CLASSES(^^::); +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 1bfab5fc..69ec1173 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index e7a480ba..45bd6925 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_dispatch_unique_ptr.cpp b/test/test_dispatch_unique_ptr.cpp index c9a75737..160a36de 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_n2216_covariant_return_type.cpp b/test/test_n2216_covariant_return_type.cpp index b198fdde..1e1414c9 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_n2216_pick_any_ambiguous.cpp b/test/test_n2216_pick_any_ambiguous.cpp index 61eda1ff..5f9eb875 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_namespaces.cpp b/test/test_namespaces.cpp index 8e9301bf..d072b33f 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_pointer_to_method.cpp b/test/test_pointer_to_method.cpp index adc2e6cb..6ecd63db 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_reflection.cpp b/test/test_reflection.cpp index 8efbc982..85694985 100644 --- a/test/test_reflection.cpp +++ b/test/test_reflection.cpp @@ -83,6 +83,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Cat&), std::string) { } // namespace macro_interface +// Braces around a group of one are optional; the fixtures below use them. BOOST_OPENMETHOD_REGISTER( register_classes<^^macro_interface, ^^macro_interface::test_registry>); @@ -145,7 +146,7 @@ BOOST_OPENMETHOD_REGISTER(value::override); } // namespace core_interface BOOST_OPENMETHOD_REGISTER( - register_classes<^^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 +185,7 @@ BOOST_OPENMETHOD(poke, (virtual_), void, test_registry); } // namespace method_only BOOST_OPENMETHOD_REGISTER( - register_classes<^^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 +229,7 @@ BOOST_OPENMETHOD_OVERRIDE(meet, (Carnivore&, Herbivore&), std::string) { } // namespace inheritance BOOST_OPENMETHOD_REGISTER( - register_classes<^^inheritance, ^^inheritance::test_registry>); + register_classes<{^^inheritance}, {^^inheritance::test_registry}>); BOOST_AUTO_TEST_CASE(virtual_and_multiple_inheritance) { using namespace inheritance; @@ -287,7 +288,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { BOOST_OPENMETHOD_REGISTER( register_classes< - ^^repeated_inheritance, ^^repeated_inheritance::test_registry>); + {^^repeated_inheritance}, {^^repeated_inheritance::test_registry}>); BOOST_AUTO_TEST_CASE(repeated_inheritance_does_not_break_the_scan) { using namespace repeated_inheritance; @@ -336,7 +337,8 @@ BOOST_OPENMETHOD_OVERRIDE( } // namespace nested -BOOST_OPENMETHOD_REGISTER(register_classes<^^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 +389,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { } // namespace unused_bases BOOST_OPENMETHOD_REGISTER( - register_classes<^^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 +445,7 @@ BOOST_OPENMETHOD_OVERRIDE(label, (Dog&), std::string) { } // namespace shared_bases BOOST_OPENMETHOD_REGISTER( - register_classes<^^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 +498,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (C&), std::string) { } // namespace direct_bases BOOST_OPENMETHOD_REGISTER( - register_classes<^^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 +559,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { } // namespace opted_out BOOST_OPENMETHOD_REGISTER( - register_classes<^^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); @@ -601,8 +603,8 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { BOOST_OPENMETHOD_REGISTER( register_classes< - ^^two_namespaces::zoo, ^^two_namespaces::pets, - ^^two_namespaces::test_registry>); + {^^two_namespaces::zoo, ^^two_namespaces::pets}, + {^^two_namespaces::test_registry}>); BOOST_AUTO_TEST_CASE(several_namespaces_in_one_registration) { using namespace two_namespaces; @@ -617,7 +619,7 @@ BOOST_AUTO_TEST_CASE(several_namespaces_in_one_registration) { } // ============================================================================= -// With no namespace and no class, the enclosing namespace is scanned +// With no namespace group, the global namespace is scanned namespace enclosing_macro { @@ -635,12 +637,17 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { return "bark"; } -// Only the registry is named; the macro captures the enclosing namespace. +namespace elsewhere { + +// Only the registry is named, and the registrar sits in a namespace holding +// neither the method nor the classes: what is scanned is `^^::`. BOOST_OPENMETHOD_REGISTER_CLASSES(^^enclosing_macro::test_registry); +} // namespace elsewhere + } // namespace enclosing_macro -BOOST_AUTO_TEST_CASE(macro_scans_the_enclosing_namespace) { +BOOST_AUTO_TEST_CASE(macro_scans_the_global_namespace) { using namespace enclosing_macro; auto comp = initialize(); @@ -652,6 +659,47 @@ BOOST_AUTO_TEST_CASE(macro_scans_the_enclosing_namespace) { BOOST_TEST(poke(dog) == "bark"); } +namespace enclosing_template { + +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"; +} + +namespace elsewhere { + +// Same, without the macro. +BOOST_OPENMETHOD_REGISTER( + register_classes<{^^enclosing_template::test_registry}>); + +} // namespace elsewhere + +} // namespace enclosing_template + +BOOST_AUTO_TEST_CASE(template_scans_the_global_namespace) { + using namespace enclosing_template; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + Dog dog; + BOOST_TEST(poke(dog) == "bark"); +} + +// ============================================================================= +// current_namespace() narrows the scan to the enclosing namespace + namespace enclosing_helper { struct test_registry : test_registry_<__COUNTER__> {}; @@ -668,13 +716,19 @@ 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. +// `^^::` would reach `outside::Stray` as well. BOOST_OPENMETHOD_REGISTER( - register_classes); + register_classes< + {current_namespace()}, {^^enclosing_helper::test_registry}>); } // namespace enclosing_helper +namespace enclosing_helper_outside { + +struct Stray : enclosing_helper::Animal {}; + +} // namespace enclosing_helper_outside + BOOST_AUTO_TEST_CASE(current_namespace_names_the_enclosing_namespace) { using namespace enclosing_helper; @@ -682,13 +736,15 @@ BOOST_AUTO_TEST_CASE(current_namespace_names_the_enclosing_namespace) { BOOST_TEST((registered(comp))); 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 +// Classes without a namespace root a scan of the global namespace namespace classes_only { @@ -709,30 +765,21 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { } // 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`. +// Naming no namespace does not turn the scan off: `Animal` roots a scan of +// `^^::`, which finds `Dog` and `Bulldog` under it. BOOST_OPENMETHOD_REGISTER( register_classes< - ^^classes_only::Animal, ^^classes_only::Bulldog, - ^^classes_only::test_registry>); + {^^classes_only::Animal}, {^^classes_only::test_registry}>); -BOOST_AUTO_TEST_CASE(listed_classes_without_a_namespace_disable_the_scan) { +BOOST_AUTO_TEST_CASE(listed_classes_root_a_scan_of_the_global_namespace) { using namespace classes_only; auto comp = initialize(); BOOST_TEST((registered(comp))); + 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"); } @@ -785,54 +832,12 @@ BOOST_AUTO_TEST_CASE(a_listed_class_roots_the_scan) { } // ============================================================================= -// 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 +// boost is skipped by a recursive scan, and reached by listing it namespace boost_gate { struct default_registry_ : test_registry_<__COUNTER__> {}; -struct scan_boost_registry : test_registry_<__COUNTER__> {}; +struct listed_registry : test_registry_<__COUNTER__> {}; struct Animal { virtual ~Animal() = default; @@ -843,7 +848,7 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { return "generic"; } -BOOST_OPENMETHOD(poke2, (virtual_), std::string, scan_boost_registry); +BOOST_OPENMETHOD(poke2, (virtual_), std::string, listed_registry); BOOST_OPENMETHOD_OVERRIDE(poke2, (Animal&), std::string) { return "generic"; } @@ -856,15 +861,18 @@ struct Stray : boost_gate::Animal {}; } // namespace boost::om_reflection_test -BOOST_OPENMETHOD_REGISTER( - register_classes<^^::, ^^boost_gate::default_registry_>); +// The default scan of `^^::` recurses everywhere except into `std` and +// `boost`, so it does not reach `Stray`. +BOOST_OPENMETHOD_REGISTER(register_classes<{^^boost_gate::default_registry_}>); +// Listing a namespace always scans it, `boost` and its nested namespaces +// included. `boost_gate` is listed too, as that is where the method is. BOOST_OPENMETHOD_REGISTER( register_classes< - ^^::, register_classes_opts::scan_boost, - ^^boost_gate::scan_boost_registry>); + {^^boost_gate, ^^boost::om_reflection_test}, + {^^boost_gate::listed_registry}>); -BOOST_AUTO_TEST_CASE(boost_is_scanned_only_on_request) { +BOOST_AUTO_TEST_CASE(boost_is_scanned_only_when_listed) { using namespace boost_gate; using boost::om_reflection_test::Stray; @@ -872,9 +880,9 @@ BOOST_AUTO_TEST_CASE(boost_is_scanned_only_on_request) { 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))); + auto listed_comp = initialize(); + BOOST_TEST((registered(listed_comp))); + BOOST_TEST((registered(listed_comp))); } // ============================================================================= @@ -901,8 +909,8 @@ BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), std::string) { BOOST_OPENMETHOD_REGISTER( register_classes< - ^^two_registries::Animal, ^^two_registries::Dog, - ^^two_registries::first_registry, ^^two_registries::second_registry>); + {^^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; @@ -919,4 +927,32 @@ BOOST_AUTO_TEST_CASE(several_registries_in_one_registration) { BOOST_TEST(poke(dog) == "bark"); } +// ============================================================================= +// No registry: the default one + +namespace default_registry_target { + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +// No registry group, so the classes go to BOOST_OPENMETHOD_DEFAULT_REGISTRY; +// no namespace group either, so nothing is scanned. +BOOST_OPENMETHOD_REGISTER( + register_classes<{ + ^^default_registry_target::Animal, ^^default_registry_target::Dog}>); + +} // namespace default_registry_target + +BOOST_AUTO_TEST_CASE(no_registry_group_means_the_default_registry) { + using namespace default_registry_target; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); +} + #endif diff --git a/test/test_rolex.cpp b/test/test_rolex.cpp index 1723fc40..a61583f8 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_runtime_errors_bad_call.cpp b/test/test_runtime_errors_bad_call.cpp index c698ea14..611eabcf 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_REGISTER_CLASSES(^^::); +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 5bc6e77e..7388fe3b 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_REGISTER_CLASSES(^^::); +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 0f2e2c56..1d240740 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_runtime_errors_duplicate_overrider.cpp b/test/test_runtime_errors_duplicate_overrider.cpp index 4559b9a7..ce05a300 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_runtime_errors_throw_error.cpp b/test/test_runtime_errors_throw_error.cpp index 65b8ed7a..a23e7c13 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_REGISTER_CLASSES(^^::); +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 fa6109b7..705de6a8 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_static_rtti.cpp b/test/test_static_rtti.cpp index a4de824f..df35c345 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_virtual_any_boost.cpp b/test/test_virtual_any_boost.cpp index 5e9a6c29..243c7a35 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_virtual_any_std.cpp b/test/test_virtual_any_std.cpp index eaf91c0b..d2c7a1f4 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_virtual_ptr_by_ref.cpp b/test/test_virtual_ptr_by_ref.cpp index 19d55d29..ffdc8a04 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_virtual_ptr_non_polymorphic.cpp b/test/test_virtual_ptr_non_polymorphic.cpp index d9395a1d..e9a3e877 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_REGISTER_CLASSES(^^::); +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 1b2c7243..b869b36f 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_REGISTER_CLASSES(^^::); +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 1ea10bf5..e90e52d9 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); diff --git a/test/test_virtual_ptr_unique.cpp b/test/test_virtual_ptr_unique.cpp index 726d59b4..09efee93 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_REGISTER_CLASSES(^^::); +BOOST_OPENMETHOD_REGISTER_CLASSES(); From 79dcdca2b4c2e6253d88bf0bae75365b4b08b1d6 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 31 Aug 2026 19:20:40 -0400 Subject: [PATCH 11/19] test: find a method through a registrar alone, with no alias `register_classes` documents two ways a namespace member can name a method specialization: a type alias, and a registrar variable whose type is nested in the specialization. Only the first was tested in isolation (`method_only`); every fixture with a registrar also had an alias standing next to it, so a regression in the variable branch of `specialization_named_by` would have been masked by the alias channel in every existing test. `registrar_only` spells the method type out in full everywhere and registers a hand-written overrider with `method<...>::override<...>`: that registrar is the only namespace member whose type involves the specialization, so the test passes only through the variable branch. The alias the test case itself uses to call the method is function-local, which the scan - namespace members only - never sees. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- test/test_reflection.cpp | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/test_reflection.cpp b/test/test_reflection.cpp index 85694985..9b9dbfe2 100644 --- a/test/test_reflection.cpp +++ b/test/test_reflection.cpp @@ -165,6 +165,55 @@ BOOST_AUTO_TEST_CASE(core_interface_dispatch) { BOOST_TEST(value::fn(virtual_ptr(plus)) == "plus"); } +// ============================================================================= +// A method with no alias is found through an overrider registrar + +namespace registrar_only { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Node { + virtual ~Node() = default; +}; + +struct Literal : Node {}; + +struct value_id; + +auto value_literal(virtual_ptr) -> std::string { + return "literal"; +} + +// The method type is spelled out in full; the registrar variable below is the +// only namespace member whose type involves the specialization. The scan finds +// the method through it - `specialization_named_by` accepts a variable whose +// type is nested in a `method` specialization. +BOOST_OPENMETHOD_REGISTER( + method< + value_id, std::string(virtual_ptr), + test_registry>::override); + +} // namespace registrar_only + +BOOST_OPENMETHOD_REGISTER( + register_classes<{^^registrar_only}, {^^registrar_only::test_registry}>); + +BOOST_AUTO_TEST_CASE(method_without_alias_is_found_through_a_registrar) { + using namespace registrar_only; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + Literal literal; + + using value = method< + value_id, std::string(virtual_ptr), test_registry>; + BOOST_TEST( + value::fn(virtual_ptr(literal)) == "literal"); +} + // ============================================================================= // A method with no overrider at all From ffa4228aa57bb5c2d141b653070bdc51cf76383e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 31 Aug 2026 19:16:04 -0400 Subject: [PATCH 12/19] reflection: fix register_classes<> with no groups at all `BOOST_OPENMETHOD_REGISTER_CLASSES()` expands to `register_classes<>`, which scans the global namespace and names nothing. That is the documented common case, and the shape some 35 converted tests use - but the empty pack broke two things in `detail`, both of them invisible under the flags this suite happens to build with. `register_classes_groups_are_ordered` formed `register_classes_kind kinds[] = {register_classes_group_kind()...}`, which is a zero-size array when the pack is empty. That is not standard C++: GCC rejects it outright under plain `-Wpedantic`, clang under `-pedantic-errors`. It compiled here only because the suite builds with `-Wall -Wextra -Werror` and no `-pedantic`, so any consumer that adds it got a hard error on the library's primary entry point. The empty case is now taken before the array is formed. `register_classes_items` reads its `kind` parameter only inside the fold over `Groups`, which expands to nothing when the pack is empty, so the parameter is never read and GCC 16 reports it under `-Wunused-but-set-parameter`. That one was not theoretical: it broke the b2 C++26 leg outright - every test using `BOOST_OPENMETHOD_REGISTER_CLASSES()` failed to compile under `toolset=gcc cxxstd=26`, which is `-Werror` here. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- include/boost/openmethod/core.hpp | 45 ++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 0d07b0ec..65acb632 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -2906,25 +2906,35 @@ consteval auto register_classes_groups_are_homogeneous() -> bool { // are declared in: namespaces, classes, registries. template consteval auto register_classes_groups_are_ordered() -> bool { - register_classes_kind kinds[] = {register_classes_group_kind()...}; - auto last = register_classes_kind::empty; - - for (auto kind : kinds) { - // An empty group carries no kind, and a group the checks above - // rejected has no meaningful one either. - if (kind == register_classes_kind::empty || - kind == register_classes_kind::invalid) { - continue; - } + // `register_classes<>` registers the classes of a scan of the global + // namespace, and is the shape `BOOST_OPENMETHOD_REGISTER_CLASSES()` + // expands to. It must be taken before the array below is formed: with no + // group, that array has size zero, which is not standard C++ - GCC rejects + // it outright under -Wpedantic, clang under -pedantic-errors. + if constexpr (sizeof...(Groups) == 0) { + return true; + } else { + register_classes_kind kinds[] = { + register_classes_group_kind()...}; + auto last = register_classes_kind::empty; + + for (auto kind : kinds) { + // An empty group carries no kind, and a group the checks above + // rejected has no meaningful one either. + if (kind == register_classes_kind::empty || + kind == register_classes_kind::invalid) { + continue; + } - if (kind < last) { - return false; + if (kind < last) { + return false; + } + + last = kind; } - last = kind; + return true; } - - return true; } // The items of every group whose kind is `Kind`, in order, without repeats. @@ -2933,6 +2943,11 @@ consteval auto register_classes_items(register_classes_kind kind) -> std::vector { std::vector items; + // `Groups` is empty for `register_classes<>`, which names nothing and + // scans the global namespace. The fold below then expands to nothing and + // never reads `kind`, which GCC reports under -Wunused-but-set-parameter. + static_cast(kind); + (..., [&] { for (auto index = 0u; index != Groups.size; ++index) { auto item = Groups.items[index]; From 68f281f0cdaab15b1cdb9d29feec5ceb72e004a1 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 31 Aug 2026 19:16:21 -0400 Subject: [PATCH 13/19] reflection: correct what the scan collects Three defects in what `register_classes` gathers, each of them silent, and one piece of dead code. **Classes nested in classes were never found.** `scan_namespace` recursed into nested namespaces only, so `struct Outer { struct Inner : Animal {}; };` was skipped - and the reference promises "every class in the scanned namespaces that derives from one of them". An `Inner` dispatched as `Animal&` got `unknown_class` under `runtime_checks`, and an out-of-range vptr lookup without them. It is now `scan_scope`, and walks a class as it walks a namespace. Recursion enters only a class the scope actually *declares*, which `parent_of` answers: following an alias instead would walk whatever it points at, and a member `using` for `std::string` would drag the whole of `basic_string` in behind it. **cv-qualification was stripped asymmetrically.** `virtual_classes` entries got `remove_cv`, but the types the scan collected were only `dealias`'d, so a namespace-scope `using CDog = const Dog` put `const Dog` in the list as an entry distinct from `Dog` - a second lattice node with its own `static_vptr`, perfect-hash slot and dispatch table row, for a spelling nothing else uses. The scan now applies `remove_cv` too. **Ambiguous bases were documented as left out, but nothing left them out.** `collect_reflected_bases` only de-duplicated, so with `Left : Animal`, `Right : Animal`, `Repeated : Left, Right`, `Repeated` was registered under `Animal` - consuming a class node, a hash slot and dispatch table cells for a class that can never be passed, as the conversion is ill-formed. `collect_dispatchable_bases` now drops such a base. Ambiguity is decided by `is_convertible_type`, which is exact - a virtual base is one subobject however many paths reach it - but instantiates a template, so it is asked only about the bases the walk arrived at more than once. A hierarchy without repeated inheritance instantiates nothing, which is what the cost note in `reflected_registered_classes_info` asks for. `reflected_bases_info` and `reflected_bases` were used by nothing in `include/`, `test/` or `doc/`, and go, along with the mp11 include that only they needed. The tests cover the two behaviours that had none: a nested-class fixture (public and private nesting, plain and cv aliases) and an assertion that the repeated-inheritance class is absent from the registry. Both were checked against the unfixed code - the first aborts, the second fails its `BOOST_TEST`. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- include/boost/openmethod/core.hpp | 25 +-- .../boost/openmethod/detail/reflection.hpp | 148 +++++++++++------- test/test_reflection.cpp | 68 ++++++++ 3 files changed, 178 insertions(+), 63 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 65acb632..dcb70ab3 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -3011,7 +3011,7 @@ consteval auto reflected_registered_classes_info() -> std::meta::info { std::vector methods, classes; for (auto ns : namespaces) { - scan_namespace(ns, ^^method, methods, classes); + scan_scope(ns, ^^method, methods, classes); } // Add the classes the methods dispatch on. @@ -3045,7 +3045,7 @@ consteval auto reflected_registered_classes_info() -> std::meta::info { for (auto found : classes) { std::vector bases; - collect_reflected_bases(found, bases); + collect_dispatchable_bases(found, bases); for (auto base : bases) { if (contains(virtual_classes, base)) { @@ -3067,7 +3067,7 @@ consteval auto reflected_registered_classes_info() -> std::meta::info { for (auto index = 0u; index != count; ++index) { std::vector bases; - collect_reflected_bases(registered[index], bases); + collect_dispatchable_bases(registered[index], bases); for (auto base : bases) { if (base == registered[index]) { @@ -3252,11 +3252,14 @@ consteval auto current_namespace( //! `register_classes<^^app>` and `register_classes<{^^app}>` are the same //! registration. //! -//! The scan covers the listed namespaces and the namespaces nested in them, -//! except `std` and `boost`: walking those would cost a great deal and find -//! nothing, as a method cannot be declared on a class the program has never -//! heard of. The exclusion applies to recursion only, so a class in `std` or -//! `boost` is registered by *listing* the namespace it is in. The scan finds +//! The scan covers the listed namespaces, the namespaces nested in them, and +//! the classes nested in the classes it finds, except `std` and `boost`: +//! walking those would cost a great deal and find nothing, as a method cannot +//! be declared on a class the program has never heard of. The exclusion +//! applies to recursion only, so a class in `std` or `boost` is registered by +//! *listing* the namespace it is in. A class is also found through an alias +//! that names it, but the scan does not walk *into* an alias: only a class the +//! scanned scope declares is descended into. 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 @@ -3280,8 +3283,10 @@ consteval auto current_namespace( //! 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. +//! which rejects it, repeated inheritance is not an error here: a base a class +//! inherits more than once cannot be converted to, so it cannot take part in +//! that class' dispatch, and it is left out of its bases. A class left with no +//! registered base is not registered at all. //! //! This class template is available only if the compiler supports C++26 //! reflection, i.e. if `BOOST_OPENMETHOD_HAS_REFLECTION` is 1. diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index 72fbb384..c2bf336e 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -47,8 +47,6 @@ class access_context { #include #include -#include - namespace boost::openmethod::detail { // One argument of `register_classes`: a group of reflections, written in @@ -78,6 +76,27 @@ template reflection_group(T...) -> reflection_group; reflection_group() -> reflection_group<0>; +// ============================================================================= +// vectors of reflections + +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); + } +} + // ============================================================================= // base classes @@ -85,13 +104,15 @@ reflection_group() -> reflection_group<0>; // `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. +// library. A base the walk arrives at again is appended to `repeated` as well: +// it is a candidate for ambiguity, which `collect_dispatchable_bases` decides. consteval void collect_reflected_bases( - std::meta::info type, std::vector& types) { - for (auto seen : types) { - if (seen == type) { - return; - } + std::meta::info type, std::vector& types, + std::vector& repeated) { + if (contains(types, type)) { + push_unique(repeated, type); + + return; } types.push_back(type); @@ -104,8 +125,8 @@ consteval void collect_reflected_bases( // 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. + // so each frame gets its own. `scan_scope` 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 @@ -115,30 +136,48 @@ consteval void collect_reflected_bases( for (auto base : bases) { if (std::meta::is_public(base)) { - collect_reflected_bases(std::meta::type_of(base), types); + collect_reflected_bases(std::meta::type_of(base), types, repeated); } } } -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); +// True if a `derived` can be converted to a `base` - that is, if `base` is a +// public base class of `derived` and naming it is unambiguous. Exact, as it +// asks the language: a virtual base is one subobject however many paths reach +// it. It costs a template instantiation, so ask it only where the answer can +// differ from what the walk above already knows. +consteval auto is_dispatchable_base( + std::meta::info derived, std::meta::info base) -> bool { + return std::meta::is_convertible_type( + std::meta::add_pointer(derived), std::meta::add_pointer(base)); } -// `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 +// Append to `types` the classes `type` can dispatch as: itself, and the base +// classes reachable from it through public inheritance, less the ones that +// repeated inheritance makes ambiguous. An ambiguous base cannot take part in +// dispatch - no reference to it can be formed - so it is left out. +// `use_classes` rejects such a hierarchy outright; `register_classes` cannot, +// because it sees classes it was never asked about. +// +// Only the bases the walk arrived at more than once can be ambiguous, and only +// those are put to `is_dispatchable_base`: a hierarchy without repeated +// inheritance instantiates nothing. +consteval void collect_dispatchable_bases( + std::meta::info type, std::vector& types) { + std::vector reachable, repeated; + collect_reflected_bases(type, reachable, repeated); + + for (auto base : reachable) { + if (contains(repeated, base) && !is_dispatchable_base(type, base)) { + continue; + } + + types.push_back(base); + } +} // ============================================================================= -// namespace scan +// scan // True if `member` is a namespace that a recursive scan does not enter: `std` // and `boost`. Walking them would cost a great deal and find nothing: a method @@ -153,24 +192,6 @@ consteval auto is_excluded_namespace(std::meta::info member) -> bool { return ns == ^^::std || ns == ^^::boost; } -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 @@ -206,22 +227,29 @@ 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. -consteval void scan_namespace( - std::meta::info ns, std::meta::info Template, +// Walk `scope` - a namespace, or a class - and the namespaces and classes +// 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. +// +// A member that *names* a class registers it, which is how a class reached +// through an alias is found. Recursion is narrower: it enters only a class the +// scope actually *declares*, which `parent_of` answers. Following an alias +// instead would walk whatever it points at - a member `using` for +// `std::string` would drag the whole of `basic_string` in behind it - and +// `std` is excluded from the scan for that very reason. +consteval void scan_scope( + std::meta::info scope, std::meta::info Template, std::vector& specializations, std::vector& classes) { // A named local, for the reason given in `collect_reflected_bases`. auto members = - std::meta::members_of(ns, std::meta::access_context::unchecked()); + std::meta::members_of(scope, std::meta::access_context::unchecked()); for (auto member : members) { if (std::meta::is_namespace(member)) { if (!is_excluded_namespace(member)) { - scan_namespace(member, Template, specializations, classes); + scan_scope(member, Template, specializations, classes); } continue; @@ -235,11 +263,25 @@ consteval void scan_namespace( } if (std::meta::is_type(member)) { - auto type = std::meta::dealias(member); + // An alias may add cv-qualification - + // `using CDog = const Dog` - which is not a distinct class to + // register: the registry would hold `Dog` and `const Dog` as two + // lattice nodes, each with its own hash slot and dispatch table + // row. + auto type = std::meta::remove_cv(std::meta::dealias(member)); if (std::meta::is_class_type(type) && std::meta::is_complete_type(type)) { + auto known = contains(classes, type); push_unique(classes, type); + + // The injected class name makes a class a member of itself, + // and a class already walked may be named again; `known` + // stops both. + if (!known && std::meta::has_parent(type) && + std::meta::parent_of(type) == scope) { + scan_scope(type, Template, specializations, classes); + } } } } diff --git a/test/test_reflection.cpp b/test/test_reflection.cpp index 9b9dbfe2..0af2f1b7 100644 --- a/test/test_reflection.cpp +++ b/test/test_reflection.cpp @@ -348,6 +348,10 @@ BOOST_AUTO_TEST_CASE(repeated_inheritance_does_not_break_the_scan) { BOOST_TEST((registered(comp))); BOOST_TEST((registered(comp))); BOOST_TEST((registered(comp))); + // Animal is the only registered class Repeated derives from, and it is + // ambiguous, so Repeated is left with no base to dispatch under and is not + // registered at all. + BOOST_TEST((!registered(comp))); Dog dog; Left left; @@ -1004,4 +1008,68 @@ BOOST_AUTO_TEST_CASE(no_registry_group_means_the_default_registry) { BOOST_TEST((registered(comp))); } +// ============================================================================= +// Classes nested in classes + +namespace nested_classes { + +struct test_registry : test_registry_<__COUNTER__> {}; + +struct Animal { + virtual ~Animal() = default; +}; + +// The scan descends into a class as it does into a namespace, whatever the +// access of what it finds: `Kennel::Dog` takes part in dispatch through +// `Animal&`, and never has to be named from outside. +struct Kennel { + struct Dog : Animal {}; + + private: + struct Puppy : Dog {}; + + public: + static auto make_puppy() -> std::unique_ptr { + return std::make_unique(); + } +}; + +// A class the scope only *names* is registered, but not descended into: Alias +// adds nothing, and Kennel::Dog is found once. An alias that adds cv- +// qualification names the same class too, not a second one to give a lattice +// node, a hash slot and a dispatch table row of its own. +using Alias = Kennel; +using ConstDog = const Kennel::Dog; + +BOOST_OPENMETHOD(poke, (virtual_), std::string, test_registry); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Animal&), std::string) { + return "generic"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Kennel::Dog&), std::string) { + return "bark"; +} + +} // namespace nested_classes + +BOOST_OPENMETHOD_REGISTER( + register_classes<{^^nested_classes}, {^^nested_classes::test_registry}>); + +BOOST_AUTO_TEST_CASE(classes_nested_in_classes_are_found) { + using namespace nested_classes; + + auto comp = initialize(); + + BOOST_TEST((registered(comp))); + BOOST_TEST((registered(comp))); + + Animal animal; + Kennel::Dog dog; + BOOST_TEST(poke(animal) == "generic"); + BOOST_TEST(poke(dog) == "bark"); + // Puppy is private to Kennel, and reached only as an Animal. + BOOST_TEST(poke(*Kennel::make_puppy()) == "bark"); +} + #endif From 47703e2e3a7ea5c2c7f6b0851b74c50c5f8c3376 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 31 Aug 2026 19:16:33 -0400 Subject: [PATCH 14/19] build: probe for -freflection instead of assuming every gcc takes it `test/Jamfile` applied `-freflection` on `gcc,26`, with no condition on the compiler version. Every GCC older than 16 accepts `-std=c++26` but rejects the flag - `error: unrecognized command line option` - so `b2 toolset=gcc-14 cxxstd=26`, or any super-project job that adds `cxxstd=26` to a gcc-14 or gcc-15 leg, failed to build the whole test directory, where it had worked before. Pinning the condition to `gcc-16` would fix that (b2 registers the major version, so it does match 16.0.1), but it fails in the other direction: a later GCC that no longer needs the flag, or needs it under a different spelling, would silently lose reflection coverage. So this does what the CMake side already does and asks the compiler. `config//has_reflection` compiles a `` snippet with `-freflection` under whatever standard the build request carries, and `check-target-builds` adds the flag only when that succeeds. The probe is its own project rather than a target under `test/` because a b2 subproject inherits its parent's requirements: declared there, the check target would carry the very conditional it is being consulted for, and asking for it would ask for itself - b2 reports it as an unresolvable reference to `config//has_reflection` from `test/config`. Verified against the three cases: gcc 16 at `cxxstd=26` answers yes and puts the flag on the command line, gcc 16 at `cxxstd=17` answers no, and clang at `cxxstd=26` answers no. gcc 13 does not accept `-std=c++26` at all, so the `[ requires cxx17_... ]` checks skip the directory before this is reached. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- config/Jamfile | 13 +++++++++++++ config/has_reflection.cpp | 20 ++++++++++++++++++++ test/Jamfile | 13 +++++++++---- 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 config/Jamfile create mode 100644 config/has_reflection.cpp diff --git a/config/Jamfile b/config/Jamfile new file mode 100644 index 00000000..bf01ff45 --- /dev/null +++ b/config/Jamfile @@ -0,0 +1,13 @@ +# Boost.OpenMethod Library Configuration Checks Jamfile +# +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt + +# The probe consulted by ../test/Jamfile. It sits here, and not under test/, +# because a b2 subproject inherits its parent's requirements: a check target +# declared in the test project would carry the very conditional it is being +# consulted for, and asking for it would ask for itself. + +obj has_reflection : has_reflection.cpp : -freflection ; +explicit has_reflection ; diff --git a/config/has_reflection.cpp b/config/has_reflection.cpp new file mode 100644 index 00000000..c1b109ca --- /dev/null +++ b/config/has_reflection.cpp @@ -0,0 +1,20 @@ +// 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) + +// Probe for C++26 reflection (P2996), compiled with -freflection. See +// ../Jamfile. + +#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); diff --git a/test/Jamfile b/test/Jamfile index 5a588b62..add64787 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -47,11 +47,16 @@ project 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 + # that flag only alongside a C++26 standard flag, so the probe compiles a + # snippet with -freflection under whatever standard the build + # request asks for, and answers yes only when both are in place. Probing + # beats matching a toolset version in both directions: a GCC that predates + # reflection rejects -freflection outright and would take the whole + # directory down with it, and a later one that stops needing the flag is + # not left out. The library needs nothing else: it detects reflection from # __cpp_impl_reflection, and falls back to explicit class registration. - gcc,26:-freflection + [ check-target-builds ../config//has_reflection "C++26 reflection" + : -freflection ] extra From 3e694b3717b8f29094a19130ea2545a4542de6b9 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 31 Aug 2026 19:16:43 -0400 Subject: [PATCH 15/19] test: fix the registrations the C++26 conversion got wrong Three files traded `BOOST_OPENMETHOD_CLASSES` for the scan on the wrong reasoning, in both directions. `test_virtual_ptr_value_semantics.cpp` registers an extra class between two `initialize` calls, "to make sure dispatch data is not re-constructed in the same place with the same values". That `Cat` is declared inside the test case body, and the file has no `BOOST_OPENMETHOD_REGISTER_CLASSES()`, so under C++26 the macro expanded to nothing and no scan could see a function-local class: nothing was added between the two calls, and the following `p.vptr() != static_vptr` was left waiting for an identical rebuild to land at a different address. It goes back to registering by hand, as the sibling registration in the header already does. `test_dispatch_multi.cpp` and `test_namespaces.cpp` went the other way. Both kept a hand registration for `dense_matrix` and `delphinus::Dolphin` on the grounds that "reflection has no way of finding it". Both are namespace-scope classes deriving publicly from a class the methods in the file dispatch on, so the scan finds them exactly like the others; the registration was redundant and the reason given would mislead anyone copying the pattern. They are folded back into the scanned list. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- test/test_dispatch_multi.cpp | 10 ++++------ test/test_namespaces.cpp | 11 +++++------ test/test_virtual_ptr_value_semantics.cpp | 6 ++++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/test/test_dispatch_multi.cpp b/test/test_dispatch_multi.cpp index 0c198afd..ac50be43 100644 --- a/test/test_dispatch_multi.cpp +++ b/test/test_dispatch_multi.cpp @@ -14,12 +14,10 @@ using namespace boost::openmethod; using namespace test_matrices; -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); +// dense_matrix has no overrider of its own and is named in no method +// signature, but it is a namespace-scope class deriving publicly from a class +// the methods below dispatch on, so the scan finds it like the others. +BOOST_OPENMETHOD_TEST_CLASSES(matrix, diagonal_matrix, dense_matrix); BOOST_OPENMETHOD( times, (virtual_, virtual_), string_pair); diff --git a/test/test_namespaces.cpp b/test/test_namespaces.cpp index d072b33f..a1554810 100644 --- a/test/test_namespaces.cpp +++ b/test/test_namespaces.cpp @@ -33,13 +33,12 @@ class Dolphin : public interfaces::Animal {}; using boost::openmethod::virtual_; +// Dolphin has no overrider of its own and is named in no method signature, +// but it is a namespace-scope class deriving publicly from a class the methods +// below dispatch on, so the scan finds it like the others. 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); + interfaces::Animal, canis::Dog, canis::Bulldog, felis::Cat, + delphinus::Dolphin); // open method with single virtual argument <=> virtual function "from outside" BOOST_OPENMETHOD(poke, (virtual_), std::string); diff --git a/test/test_virtual_ptr_value_semantics.cpp b/test/test_virtual_ptr_value_semantics.cpp index 01e9c33a..1eb6269e 100644 --- a/test/test_virtual_ptr_value_semantics.cpp +++ b/test/test_virtual_ptr_value_semantics.cpp @@ -272,9 +272,11 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(indirect_virtual_ptr, Registry, test_policies) { BOOST_TEST(p.vptr() == Registry::template static_vptr); // Add a class, to make sure dispatch data is not re-constructed in the same - // place with the same values: + // place with the same values. `Cat` is local to this function, so a + // namespace scan cannot see it: the registration is by hand under every + // standard, like the one in the header. struct Cat : Animal {}; - BOOST_OPENMETHOD_TEST_CLASSES(Animal, Cat, Registry); + BOOST_OPENMETHOD_CLASSES(Animal, Cat, Registry); init_test(); From b095aa95e8a82ff9cef8d69772323c5eeb3cdd37 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 31 Aug 2026 19:16:51 -0400 Subject: [PATCH 16/19] doc: let explicit_registration.hpp own the whole registry-override recipe The snippet header included to get at `default_registry` before defining the override macro - the pre-core-header idiom the guidelines drop, and the one thing the documented hand-check looks for: git grep -n "openmethod/preamble.hpp\|openmethod/default_registry.hpp" -- doc test It now has the shape test_capture_errors.hpp uses: forward-declare the registry, define BOOST_OPENMETHOD_DEFAULT_REGISTRY, include , then define the registry. The four error snippets that include it are unchanged - they include the library themselves afterwards, and the include guard makes that a no-op - and the check now returns prose only. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- doc/modules/ROOT/snippets/explicit_registration.hpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/modules/ROOT/snippets/explicit_registration.hpp b/doc/modules/ROOT/snippets/explicit_registration.hpp index 9dd08535..b2066bb3 100644 --- a/doc/modules/ROOT/snippets/explicit_registration.hpp +++ b/doc/modules/ROOT/snippets/explicit_registration.hpp @@ -16,12 +16,18 @@ #ifndef BOOST_OPENMETHOD_SNIPPETS_EXPLICIT_REGISTRATION_HPP #define BOOST_OPENMETHOD_SNIPPETS_EXPLICIT_REGISTRATION_HPP -#include +// Like test_capture_errors.hpp, this header owns the whole recipe: the +// declaration, the BOOST_OPENMETHOD_DEFAULT_REGISTRY definition, the library +// include, and the registry itself. A pre-core header of the library - +// preamble.hpp, default_registry.hpp - belongs nowhere outside include/. + +struct snippet_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY snippet_registry + +#include struct snippet_registry : boost::openmethod::default_registry::with< boost::openmethod::policies::explicit_class_registration> {}; -#define BOOST_OPENMETHOD_DEFAULT_REGISTRY snippet_registry - #endif From 0eb5b7c13eef33bb307054b4d2a7712f74a7c753 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 1 Sep 2026 15:56:04 -0400 Subject: [PATCH 17/19] ci: name the scan by the name it now has `use_classes_in` became `register_classes` two commits later, and this comment was the one reference the rename missed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60987562..705ad446 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,8 @@ jobs: 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. + # to nothing here, so every class has to be found by the scan that + # BOOST_OPENMETHOD_REGISTER_CLASSES starts. - name: Build and test run: | cmake -S . -B ../build -G Ninja \ From 92e720cf9c1c12123286e1b6eebd4b7345fd4fff Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Wed, 2 Sep 2026 09:13:33 -0400 Subject: [PATCH 18/19] build: refer to the reflection probe by project id The check-target-builds requirement in test/Jamfile is inherited by the test subprojects, and b2 resolves a relative target reference from the directory of the project it is evaluating it for: from test/dynamic_loading, ../config//has_reflection does not exist. The Drone macOS 12.4 stage, which builds two variants and three standards, hit that path and failed with "could not resolve project reference '../config'". Give the config project an id, register it with use-project, and refer to the probe by the id, which resolves the same from every project. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012QA4PCHE4oooe1ZtG9aKj7 --- config/Jamfile | 6 ++++++ test/Jamfile | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/config/Jamfile b/config/Jamfile index bf01ff45..a90f641f 100644 --- a/config/Jamfile +++ b/config/Jamfile @@ -8,6 +8,12 @@ # because a b2 subproject inherits its parent's requirements: a check target # declared in the test project would carry the very conditional it is being # consulted for, and asking for it would ask for itself. +# +# The project has an id, and the test Jamfile refers to the probe by it: the +# requirement is inherited by the test subprojects too, and a relative +# reference would be resolved from each of their directories. + +project /boost/openmethod/config ; obj has_reflection : has_reflection.cpp : -freflection ; explicit has_reflection ; diff --git a/test/Jamfile b/test/Jamfile index add64787..ddc78570 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -11,6 +11,8 @@ import-search /boost/config/checks ; import testing ; import config : requires ; +use-project /boost/openmethod/config : ../config ; + project : requirements @@ -55,8 +57,8 @@ project # directory down with it, and a later one that stops needing the flag is # not left out. The library needs nothing else: it detects reflection from # __cpp_impl_reflection, and falls back to explicit class registration. - [ check-target-builds ../config//has_reflection "C++26 reflection" - : -freflection ] + [ check-target-builds /boost/openmethod/config//has_reflection + "C++26 reflection" : -freflection ] extra From 44cb0dd05999a49771fd0c3c56d739248d19c565 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Wed, 2 Sep 2026 17:54:02 -0400 Subject: [PATCH 19/19] reflection: remove current_namespace `register_classes<{current_namespace()}, ...>` narrowed a scan to the namespace enclosing the registration. Naming the namespace does the same thing, in fewer characters and without a second way to say it, so the function goes: one public name less, and one less thing for the reference to explain. With it goes the only stub that had to spell a `std::meta` type, and so the `std::meta::info` and `std::meta::access_context` forward declarations that `detail/reflection.hpp` carried under `#ifdef __MRDOCS__` purely to let it. The `register_classes` stub that remains names no such type. CLAUDE.md records the arrangement, and says where to put a stand-in should a future stub need one. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UDTJyUvPbFj23o8zVcuvHt --- CLAUDE.md | 9 ++-- doc/modules/ROOT/pages/basics.adoc | 3 +- include/boost/openmethod/core.hpp | 44 +++--------------- .../boost/openmethod/detail/reflection.hpp | 20 -------- test/test_reflection.cpp | 46 ------------------- 5 files changed, 13 insertions(+), 109 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ef9492a0..336b859d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -329,10 +329,11 @@ renders correctly). **The C++26 reflection API is the one exception, and it is a forced one.** MrDocs' front-end does not implement P2996, so the reference build runs at `-D CMAKE_CXX_STANDARD=20` and `BOOST_OPENMETHOD_HAS_REFLECTION` is 0 there: every `#if BOOST_OPENMETHOD_HAS_REFLECTION` block is -invisible to it. `register_classes` and `current_namespace` are therefore restated as -documentation stubs in an `#elif defined(__MRDOCS__)` branch at the end of `core.hpp`, and -`detail/reflection.hpp` forward-declares `std::meta::info` and `std::meta::access_context` under -`#ifdef __MRDOCS__` so those stubs can spell their real signatures. Two rules contain the drift: +invisible to it. `register_classes` is therefore restated as a documentation stub in an +`#elif defined(__MRDOCS__)` branch at the end of `core.hpp`. That stub names no `std::meta` type, +so nothing has to stand in for one; should a future stub need to spell one, forward-declare it +under `#ifdef __MRDOCS__` in `detail/reflection.hpp`, where `include-symbols` in `mrdocs.yml` +(`boost::openmethod::**`) keeps it out of the reference. Two rules contain the drift: - **Each doc comment exists exactly once, on the stub.** The real declaration carries only `//! @see @ref for documentation.`, as `inplace_vptr_derived` already does. Never copy diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index 5dce8a8a..2453cc95 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -162,8 +162,7 @@ BOOST_OPENMETHOD_REGISTER_CLASSES(^^zoo, ^^my_registry); With no namespace group - `BOOST_OPENMETHOD_REGISTER_CLASSES()`, or any other group on its own, `{^^my_registry}` included - the global namespace is scanned. -Use cpp:current_namespace[] to scan only the namespace the registration sits -in. +Name a namespace to scan only that one. A listed class is registered whether a method dispatches on it or not, and it is one more root for the scan: every class the scan finds deriving from it is diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index dcb70ab3..8c865084 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -3151,21 +3151,8 @@ BOOST_FORCEINLINE auto use_reflected_classes_in() -> void { #if BOOST_OPENMETHOD_HAS_REFLECTION // MrDocs' front-end does not implement P2996, so it never sees this branch. -// The reference documentation for `current_namespace` and `register_classes` -// is on the stubs in the `#elif` branch below; keep the two in step. - -//! @see @ref current_namespace for documentation. -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; -} +// The reference documentation for `register_classes` is on the stub in the +// `#elif` branch below; keep the two in step. //! @see @ref register_classes for documentation. template @@ -3199,26 +3186,9 @@ class register_classes { #elif defined(__MRDOCS__) -// Documentation stubs. MrDocs compiles this branch instead of the real -// declarations above, which its front-end cannot parse. The `std::meta` -// stand-ins they use are declared in detail/reflection.hpp. - -//! Get the current namespace. -//! -//! Returns a reflection of the namespace enclosing the point of the call. Use -//! it to narrow a scan to that namespace, which @ref register_classes does not -//! do on its own: with no namespace listed it scans the global one. Do not -//! pass an argument: the default captures the caller's context. -//! -//! @code -//! register_classes<{current_namespace()}, {^^my_registry}> -//! @endcode -//! -//! 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; +// Documentation stub. MrDocs compiles this branch instead of the real +// declaration above, which its front-end cannot parse. It names no `std::meta` +// type, so nothing has to stand in for one. //! Find the classes taking part in dispatch by reflection, and register them //! @@ -3267,8 +3237,8 @@ consteval auto current_namespace( //! selected on. //! //! If no namespace is given, the global namespace is scanned - whatever the -//! other groups hold, and for @ref BOOST_OPENMETHOD_REGISTER_CLASSES too. Use -//! @ref current_namespace to scan only the namespace the registrar is in. +//! other groups hold, and for @ref BOOST_OPENMETHOD_REGISTER_CLASSES too. Name +//! a namespace to scan only that one. //! //! 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. diff --git a/include/boost/openmethod/detail/reflection.hpp b/include/boost/openmethod/detail/reflection.hpp index c2bf336e..a4eec0d6 100644 --- a/include/boost/openmethod/detail/reflection.hpp +++ b/include/boost/openmethod/detail/reflection.hpp @@ -21,26 +21,6 @@ #define BOOST_OPENMETHOD_HAS_REFLECTION 0 #endif -#ifdef __MRDOCS__ - -// MrDocs' front-end does not implement P2996, so the reflection API cannot be -// parsed and is documented on stubs instead - see the `#elif defined(__MRDOCS__)` -// branch at the end of core.hpp. The declarations below exist only so those -// stubs can spell their real signatures; `include-symbols` in mrdocs.yml keeps -// them out of the reference. They are never seen by a real compiler. -namespace std::meta { - -struct info; - -class access_context { - public: - static auto current() -> access_context; -}; - -} // namespace std::meta - -#endif - #if BOOST_OPENMETHOD_HAS_REFLECTION #include diff --git a/test/test_reflection.cpp b/test/test_reflection.cpp index 0af2f1b7..0e415135 100644 --- a/test/test_reflection.cpp +++ b/test/test_reflection.cpp @@ -750,52 +750,6 @@ BOOST_AUTO_TEST_CASE(template_scans_the_global_namespace) { BOOST_TEST(poke(dog) == "bark"); } -// ============================================================================= -// current_namespace() narrows the scan to the enclosing namespace - -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"; -} - -// `^^::` would reach `outside::Stray` as well. -BOOST_OPENMETHOD_REGISTER( - register_classes< - {current_namespace()}, {^^enclosing_helper::test_registry}>); - -} // namespace enclosing_helper - -namespace enclosing_helper_outside { - -struct Stray : enclosing_helper::Animal {}; - -} // namespace enclosing_helper_outside - -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))); - BOOST_TEST( - (!registered(comp))); - - Dog dog; - BOOST_TEST(poke(dog) == "bark"); -} - // ============================================================================= // Classes without a namespace root a scan of the global namespace