diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index cd4f4856..ee21b262 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -3038,6 +3038,72 @@ struct validate_overrider_parameter< "of corresponding overrider parameter"); }; +// The pieces of a `method` specialization, for the two templates below. The +// method type is what a guide function returns, so this is how the macro layer +// takes it apart. +template +struct method_parts; + +template +struct method_parts> { + using registry = Reg; +}; + +// Rewrite a `virtual_ptr` parameter into `Registry`, whatever registry it +// names, and leave every other parameter alone. Used to ask whether an +// overrider would match a method if only the registries agreed - see +// `enable_guide_ignoring_registry` in macros.hpp. +template +struct rebind_parameter_registry { + using type = Parameter; +}; + +template +struct rebind_parameter_registry> { + using type = virtual_ptr; +}; + +template +struct rebind_parameter_registry&> { + using type = virtual_ptr&; +}; + +template +struct rebind_parameter_registry&> { + using type = const virtual_ptr&; +}; + +template +struct rebind_parameter_registry&&> { + using type = virtual_ptr&&; +}; + +// Say why an overrider did not match a method it otherwise fits. Reached from +// the failure branch of BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD, once the +// relaxed guide has found the method the overrider was aiming at. Pairing the +// parameters instantiates `validate_overrider_parameter`, exactly as calling +// the overrider through its thunk would - so the diagnosis, "registry +// mismatch" with both registries in the instantiation trace, is the one the +// user would have got had the guide not failed first. +template +struct explain_overrider_mismatch; + +template< + typename Id, typename ReturnType, typename... Parameters, class Reg, + typename... OverriderParameters> +struct explain_overrider_mismatch< + method, OverriderParameters...> { + // Instantiating these is the point: each fires its own diagnosis, and the + // one this exists for is `validate_overrider_parameter`'s "registry + // mismatch", which names both registries. The value is incidental - those + // specializations report through `static_assert` and still inherit + // `true_type` - so the fold is a backstop, not the check. + static_assert( + (validate_overrider_parameter::value && + ...), + "BOOST_OPENMETHOD_OVERRIDE: the overrider does not match the method"); +}; + } // namespace detail template< diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index f87df558..887c7b9e 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -22,6 +22,24 @@ struct enable_forwarder< using type = ReturnType; }; +// The same question with the registries taken out of it: every `virtual_ptr` +// parameter is rewritten into the method's own registry before the call is +// tried. An overrider that matches this way, and not `enable_forwarder`, +// differs from the method in nothing but a registry - which is what the guide +// declared alongside the real one in BOOST_OPENMETHOD reports, through +// `explain_overrider_mismatch`. It never finds a method to call. +template +struct enable_guide_ignoring_registry; + +template +struct enable_guide_ignoring_registry< + std::void_t::registry, Parameters>::type>()...))>, + Method, ReturnType, Parameters...> { + using type = ReturnType; +}; + template struct va_args; @@ -111,6 +129,11 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_GUIDE(ID) BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _guide) +// The guide consulted only when BOOST_OPENMETHOD_GUIDE finds nothing, to tell +// a wrong registry from a genuinely missing method. +#define BOOST_OPENMETHOD_DETAIL_GUIDE_ANY_REGISTRY(ID) \ + BOOST_PP_CAT(BOOST_OPENMETHOD_ID(ID), _guide_any_registry) + //! Expand to a core `method` specialization. //! //! Expands to the core @ref boost::openmethod::method specialization created by @@ -238,6 +261,13 @@ inline constexpr bool method_not_found = false; ForwarderParameters...>::type \ BOOST_OPENMETHOD_GUIDE(ID)(ForwarderParameters && ... args); \ template \ + typename ::boost::openmethod::detail::enable_guide_ignoring_registry< \ + void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ + typename BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ + ForwarderParameters...>::type \ + BOOST_OPENMETHOD_DETAIL_GUIDE_ANY_REGISTRY(ID)( \ + ForwarderParameters && ... args); \ + template \ inline auto ID(ForwarderParameters&&... args) -> \ typename ::boost::openmethod::detail::enable_forwarder< \ void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \ @@ -251,13 +281,25 @@ inline constexpr bool method_not_found = false; #define BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(ID, PARAMETERS) \ template \ - struct boost_openmethod_detail_locate_method_aux { \ + struct boost_openmethod_detail_explain_method_aux { \ static_assert( \ ::boost::openmethod::detail::method_not_found, \ "BOOST_OPENMETHOD_OVERRIDE: cannot find '" #ID \ "' method that accepts the same arguments as the overrider"); \ }; \ template \ + struct boost_openmethod_detail_explain_method_aux< \ + void(A...), \ + std::void_t()...))>> : \ + ::boost::openmethod::detail::explain_overrider_mismatch< \ + decltype(BOOST_OPENMETHOD_DETAIL_GUIDE_ANY_REGISTRY(ID)( \ + std::declval()...)), \ + A...> {}; \ + template \ + struct boost_openmethod_detail_locate_method_aux : \ + boost_openmethod_detail_explain_method_aux {}; \ + template \ struct boost_openmethod_detail_locate_method_aux< \ void(A...), \ std::void_t + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; +struct kennel_registry : default_registry {}; + +struct Animal { + virtual ~Animal() = default; + friend auto boost_openmethod_registry(Animal*) -> zoo_registry; +}; + +// Poodle declares an affinity of its own, so `virtual_ptr` is a +// `virtual_ptr` in `kennel_registry` while the method's parameter is one in +// `zoo_registry`, and the two do not convert. Without the guide that ignores +// registries, this reports only that no `poke` accepts these arguments. +struct Poodle : Animal { + friend auto boost_openmethod_registry(Poodle*) -> kennel_registry; +}; + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { +} + +int main() { + return 0; +}