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
16 changes: 11 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -632,18 +632,24 @@ 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_<Animal&, R>` (ordinary carrier) | yes | yes - `core.hpp`, "the parameter belongs to another registry" |
| `virtual_<const std::any&, R>` (interop) | yes | **no - nothing checks it** (#123) |
| `virtual_<const std::any&, R>` (interop) | yes | yes - the same check, since #123 |
| `virtual_any<A, R>&` | no, abstains | yes - `virtual_any.hpp`, "registry mismatch" |

- `interop/std_any.hpp`, `interop/boost_any.hpp`, `interop/boost_type_erasure.hpp` and
`interop/virtual_any.hpp` declare no affinity for any class, so a class reached through them
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_<T>` 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<Any, Registry>&`) 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<C, _self>` passed by value.
`virtual_any`'s are a different shape (`virtual_any<Any, Registry>&`) 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.

Expand Down
24 changes: 19 additions & 5 deletions include/boost/openmethod/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2316,6 +2316,22 @@ template<class Class, class Registry>
struct parameter_traits<const virtual_ptr<Class, Registry, void>&, Registry> :
virtual_traits<const virtual_ptr<Class, Registry, void>&, 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<typename T, class Registry>
constexpr bool has_vptr_source =
has_vptr_fn<virtual_type<T, Registry>, Registry> ||
has_vptr<
virtual_traits<T, Registry>,
decltype(virtual_traits<T, Registry>::peek(std::declval<T>()))> ||
Registry::rtti::template is_polymorphic<virtual_type<T, Registry>>;

template<typename T, class Registry, typename = void>
struct validate_method_parameter : std::true_type {};

Expand All @@ -2329,13 +2345,11 @@ template<typename T, class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<T, ParamRegistry>, Registry,
std::void_t<typename virtual_traits<T, Registry>::virtual_type>> :
std::bool_constant<
has_vptr_fn<virtual_type<T, Registry>, Registry> ||
Registry::rtti::template is_polymorphic<virtual_type<T, Registry>>> {
std::bool_constant<has_vptr_source<T, Registry>> {
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.
Expand Down
13 changes: 0 additions & 13 deletions include/boost/openmethod/interop/boost_any.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,6 @@ namespace boost::openmethod {

namespace detail {

template<class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<const boost::any&, ParamRegistry>, Registry, void> :
std::true_type {};

template<class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<boost::any&, ParamRegistry>, Registry, void> : std::true_type {};

template<class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<boost::any&&, ParamRegistry>, 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
Expand Down
25 changes: 0 additions & 25 deletions include/boost/openmethod/interop/boost_type_erasure.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<class C, typename T, class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<const boost::type_erasure::any<C, T>&, ParamRegistry>, Registry,
void> : std::true_type {};

template<class C, typename T, class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<boost::type_erasure::any<C, T>&, ParamRegistry>, Registry, void> :
std::true_type {};

template<class C, typename T, class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<boost::type_erasure::any<C, T>&&, ParamRegistry>, Registry, void> :
std::true_type {};

template<class C, typename T, class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<boost::type_erasure::any<C, T&>, ParamRegistry>, Registry, void> :
std::true_type {};

template<class C, typename T, class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<boost::type_erasure::any<C, const T&>, ParamRegistry>, Registry,
void> : std::true_type {};

template<class C, class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<
Expand Down
13 changes: 0 additions & 13 deletions include/boost/openmethod/interop/std_any.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,6 @@ namespace boost::openmethod {

namespace detail {

template<class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<const std::any&, ParamRegistry>, Registry, void> :
std::true_type {};

template<class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<std::any&, ParamRegistry>, Registry, void> : std::true_type {};

template<class ParamRegistry, class Registry>
struct validate_method_parameter<
virtual_<std::any&&, ParamRegistry>, 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
Expand Down
33 changes: 33 additions & 0 deletions test/compile_fail_std_any_registry_mismatch.cpp
Original file line number Diff line number Diff line change
@@ -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 <any>
#include <string>

#include <boost/openmethod.hpp>
#include <boost/openmethod/interop/std_any.hpp>

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_<const std::any&, zoo_registry>), 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;
}
16 changes: 16 additions & 0 deletions test/test_dispatch_std_any.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ static_assert(detail::validate_method_parameter<
static_assert(detail::validate_method_parameter<
virtual_<std::any&&, default_registry>, 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<const std::any&, default_registry>);
static_assert(detail::has_vptr_source<std::any&, default_registry>);
static_assert(detail::has_vptr_source<std::any&&, default_registry>);

// 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<void(virtual_<const std::any&>)>,
default_registry>);

MAKE_CLASSES();

BOOST_OPENMETHOD(name, (virtual_<const std::any&>), std::string);
Expand Down
Loading