diff --git a/CLAUDE.md b/CLAUDE.md index acefc627..548a920c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -632,7 +632,7 @@ interop shapes differ from one another. What each does, with the method on anoth | parameter spelling `R` | decides an unannotated method's registry | checked against a method naming another | |---|---|---| | `virtual_` (ordinary carrier) | yes | yes - `core.hpp`, "the parameter belongs to another registry" | -| `virtual_` (interop) | yes | **no - nothing checks it** (#123) | +| `virtual_` (interop) | yes | yes - the same check, since #123 | | `virtual_any&` | no, abstains | yes - `virtual_any.hpp`, "registry mismatch" | - `interop/std_any.hpp`, `interop/boost_any.hpp`, `interop/boost_type_erasure.hpp` and @@ -640,10 +640,16 @@ interop shapes differ from one another. What each does, with the method on anoth keeps whatever affinity it declares itself. A parameter may still *spell* a registry - a different thing from declaring an affinity, and the reason #116 gave the `validate_method_parameter` specializations the registry parameter `virtual_` gained in #113. - A specialization there must never go back to the bare `virtual_` spelling. Those - specializations accept any `ParamRegistry` without ever comparing it, which is the middle row - above and a bug rather than a decision (#123); `virtual_any`'s are a different shape - (`virtual_any&`) and do compare. None of this is covered in `doc/`. + **Those three headers no longer specialize `validate_method_parameter` at all** (#123). They + used to accept every parameter of their shape unconditionally, which also waved the registry + check through - the middle row above, a bug rather than a decision. They were there only + because the generic rule did not recognize a `virtual_traits::vptr` as a way to reach a + v-table; `detail::has_vptr_source` now names all three routes (the ADL hook, the parameter's + `virtual_traits`, and `is_polymorphic`), so the generic rule covers the interop shapes and + applies its registry check and affinity recheck to them. `interop/boost_type_erasure.hpp` + keeps one specialization, and it is a *rejection*: an owning `any` passed by value. + `virtual_any`'s are a different shape (`virtual_any&`) and do compare. None of + this is covered in `doc/`. - The C++26 `register_classes` still defaults to the macro - its groups may name a namespace, whose classes are only known during the scan that the choice of registry feeds. diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index f3a7c474..27f4552e 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -2316,6 +2316,22 @@ template struct parameter_traits&, Registry> : virtual_traits&, Registry> {}; +// Whether a virtual parameter can yield a v-table pointer at all, by any of +// the three routes `method::vptr` tries, in that order: the ADL hook on the +// class, the parameter's own `virtual_traits` - which is how the interop +// headers dispatch on a type-erased value - and the `rtti` policy being able +// to read the class of an object, which `is_polymorphic` reports. +// +// A trait of its own because a condition spanning the three does not fit the +// indentation, and because each route is worth naming. +template +constexpr bool has_vptr_source = + has_vptr_fn, Registry> || + has_vptr< + virtual_traits, + decltype(virtual_traits::peek(std::declval()))> || + Registry::rtti::template is_polymorphic>; + template struct validate_method_parameter : std::true_type {}; @@ -2329,13 +2345,11 @@ 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::bool_constant> { static_assert( validate_method_parameter::value, - "virtual_<> parameter is not a polymorphic class and no " - "boost_openmethod_vptr is applicable"); + "virtual_<> parameter is not a polymorphic class, and neither a " + "boost_openmethod_vptr nor a virtual_traits vptr is applicable"); // The class is complete here (is_polymorphic needs it), so this is one of // the checkpoints where its affinity is asked again. diff --git a/include/boost/openmethod/interop/boost_any.hpp b/include/boost/openmethod/interop/boost_any.hpp index 8ba304fc..ee1e6f2e 100644 --- a/include/boost/openmethod/interop/boost_any.hpp +++ b/include/boost/openmethod/interop/boost_any.hpp @@ -20,19 +20,6 @@ namespace boost::openmethod { namespace detail { -template -struct validate_method_parameter< - virtual_, Registry, void> : - std::true_type {}; - -template -struct validate_method_parameter< - virtual_, Registry, void> : std::true_type {}; - -template -struct validate_method_parameter< - virtual_, 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 // other policy the lookup key is meaningless, and `type_id` being diff --git a/include/boost/openmethod/interop/boost_type_erasure.hpp b/include/boost/openmethod/interop/boost_type_erasure.hpp index 0cfba56d..452b13fa 100644 --- a/include/boost/openmethod/interop/boost_type_erasure.hpp +++ b/include/boost/openmethod/interop/boost_type_erasure.hpp @@ -94,31 +94,6 @@ constexpr bool te_pass_through = // placeholder of the parameter, so methods and overriders agree on a // single registered root per Concept. -template -struct validate_method_parameter< - virtual_&, ParamRegistry>, Registry, - void> : std::true_type {}; - -template -struct validate_method_parameter< - virtual_&, ParamRegistry>, Registry, void> : - std::true_type {}; - -template -struct validate_method_parameter< - virtual_&&, ParamRegistry>, Registry, void> : - std::true_type {}; - -template -struct validate_method_parameter< - virtual_, ParamRegistry>, Registry, void> : - std::true_type {}; - -template -struct validate_method_parameter< - virtual_, ParamRegistry>, Registry, - void> : std::true_type {}; - template struct validate_method_parameter< virtual_< diff --git a/include/boost/openmethod/interop/std_any.hpp b/include/boost/openmethod/interop/std_any.hpp index f80fe1d2..d8ff4a89 100644 --- a/include/boost/openmethod/interop/std_any.hpp +++ b/include/boost/openmethod/interop/std_any.hpp @@ -20,19 +20,6 @@ namespace boost::openmethod { namespace detail { -template -struct validate_method_parameter< - virtual_, Registry, void> : - std::true_type {}; - -template -struct validate_method_parameter< - virtual_, Registry, void> : std::true_type {}; - -template -struct validate_method_parameter< - virtual_, 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 // other policy the lookup key is meaningless, and `type_id` being diff --git a/test/compile_fail_std_any_registry_mismatch.cpp b/test/compile_fail_std_any_registry_mismatch.cpp new file mode 100644 index 00000000..bbf58697 --- /dev/null +++ b/test/compile_fail_std_any_registry_mismatch.cpp @@ -0,0 +1,33 @@ +// 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 +#include + +#include +#include + +using namespace boost::openmethod; + +struct zoo_registry : default_registry {}; + +// An interop parameter carries a registry exactly as an ordinary one does - +// see compile_fail_virtual_registry_mismatch.cpp - and must agree with its +// method. It used to be accepted silently, while still deciding the registry +// of a method that named none (#123). +BOOST_OPENMETHOD( + speak, (virtual_), std::string, + default_registry); + +int main() { + // See compile_fail_adl_registry_declared_mismatch.cpp for why the call. + std::any animal; + speak(animal); + + return 0; +} diff --git a/test/test_dispatch_std_any.cpp b/test/test_dispatch_std_any.cpp index 0b6545af..32463be5 100644 --- a/test/test_dispatch_std_any.cpp +++ b/test/test_dispatch_std_any.cpp @@ -41,6 +41,22 @@ static_assert(detail::validate_method_parameter< static_assert(detail::validate_method_parameter< virtual_, default_registry, void>::value); +// The parameter's own virtual_traits is what yields its v-table pointer: the +// third way a virtual parameter can have one, next to the ADL hook and a +// class the rtti policy deems polymorphic. Before #123 the interop headers +// said so by accepting every parameter of this shape unconditionally, which +// also waved the registry check through. +static_assert(detail::has_vptr_source); +static_assert(detail::has_vptr_source); +static_assert(detail::has_vptr_source); + +// An interop parameter is an ordinary carrier: it decides the registry of a +// method that names none, and compile_fail_std_any_registry_mismatch.cpp +// covers the other half, a method that names a different one. +static_assert(std::is_same_v< + detail::method_registry)>, + default_registry>); + MAKE_CLASSES(); BOOST_OPENMETHOD(name, (virtual_), std::string);