Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions include/boost/openmethod/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<class Method>
struct method_parts;

template<typename Id, typename ReturnType, typename... Parameters, class Reg>
struct method_parts<method<Id, ReturnType(Parameters...), Reg>> {
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<class Registry, typename Parameter>
struct rebind_parameter_registry {
using type = Parameter;
};

template<class Registry, class Class, class Other>
struct rebind_parameter_registry<Registry, virtual_ptr<Class, Other>> {
using type = virtual_ptr<Class, Registry>;
};

template<class Registry, class Class, class Other>
struct rebind_parameter_registry<Registry, virtual_ptr<Class, Other>&> {
using type = virtual_ptr<Class, Registry>&;
};

template<class Registry, class Class, class Other>
struct rebind_parameter_registry<Registry, const virtual_ptr<Class, Other>&> {
using type = const virtual_ptr<Class, Registry>&;
};

template<class Registry, class Class, class Other>
struct rebind_parameter_registry<Registry, virtual_ptr<Class, Other>&&> {
using type = virtual_ptr<Class, Registry>&&;
};

// 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<class Method, typename... OverriderParameters>
struct explain_overrider_mismatch;

template<
typename Id, typename ReturnType, typename... Parameters, class Reg,
typename... OverriderParameters>
struct explain_overrider_mismatch<
method<Id, ReturnType(Parameters...), Reg>, 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<Parameters, OverriderParameters>::value &&
...),
"BOOST_OPENMETHOD_OVERRIDE: the overrider does not match the method");
};

} // namespace detail

template<
Expand Down
44 changes: 43 additions & 1 deletion include/boost/openmethod/macros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<typename, class Method, typename ReturnType, typename... Parameters>
struct enable_guide_ignoring_registry;

template<class Method, typename ReturnType, typename... Parameters>
struct enable_guide_ignoring_registry<
std::void_t<decltype(Method::fn(
std::declval<typename rebind_parameter_registry<
typename method_parts<Method>::registry, Parameters>::type>()...))>,
Method, ReturnType, Parameters...> {
using type = ReturnType;
};

template<class...>
struct va_args;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -238,6 +261,13 @@ inline constexpr bool method_not_found = false;
ForwarderParameters...>::type \
BOOST_OPENMETHOD_GUIDE(ID)(ForwarderParameters && ... args); \
template<typename... ForwarderParameters> \
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<typename... ForwarderParameters> \
inline auto ID(ForwarderParameters&&... args) -> \
typename ::boost::openmethod::detail::enable_forwarder< \
void, BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, __VA_ARGS__), \
Expand All @@ -251,13 +281,25 @@ inline constexpr bool method_not_found = false;

#define BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(ID, PARAMETERS) \
template<typename T, typename = void> \
struct boost_openmethod_detail_locate_method_aux { \
struct boost_openmethod_detail_explain_method_aux { \
static_assert( \
::boost::openmethod::detail::method_not_found<T>, \
"BOOST_OPENMETHOD_OVERRIDE: cannot find '" #ID \
"' method that accepts the same arguments as the overrider"); \
}; \
template<typename... A> \
struct boost_openmethod_detail_explain_method_aux< \
void(A...), \
std::void_t<decltype(BOOST_OPENMETHOD_DETAIL_GUIDE_ANY_REGISTRY(ID)( \
std::declval<A>()...))>> : \
::boost::openmethod::detail::explain_overrider_mismatch< \
decltype(BOOST_OPENMETHOD_DETAIL_GUIDE_ANY_REGISTRY(ID)( \
std::declval<A>()...)), \
A...> {}; \
template<typename T, typename = void> \
struct boost_openmethod_detail_locate_method_aux : \
boost_openmethod_detail_explain_method_aux<T> {}; \
template<typename... A> \
struct boost_openmethod_detail_locate_method_aux< \
void(A...), \
std::void_t<decltype(BOOST_OPENMETHOD_GUIDE(ID)( \
Expand Down
36 changes: 36 additions & 0 deletions test/compile_fail_overrider_registry_mismatch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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: registry mismatch

#include <boost/openmethod.hpp>

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<Poodle>` 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<Animal>), void);

BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr<Poodle>), void) {
}

int main() {
return 0;
}
Loading