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
3 changes: 0 additions & 3 deletions doc/modules/ROOT/examples/adl_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class Animal {
virtual ~Animal() = default;

private:
// Animal - and every class derived from it - belongs to zoo_registry
friend auto boost_openmethod_registry(Animal*) -> zoo_registry;
};

Expand All @@ -40,7 +39,6 @@ class Cat : public Animal {};
// tag::methods[]
BOOST_OPENMETHOD_CLASSES(zoo::Animal, zoo::Dog, zoo::Cat, zoo_registry);

// no registry argument: speak follows Animal
BOOST_OPENMETHOD(speak, (virtual_<const zoo::Animal&>), std::string);

BOOST_OPENMETHOD_OVERRIDE(speak, (const zoo::Dog&), std::string) {
Expand All @@ -53,7 +51,6 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (const zoo::Cat&), std::string) {
// end::methods[]

// tag::virtual_ptr[]
// ...and so does virtual_ptr
static_assert(
std::is_same_v<virtual_ptr<zoo::Dog>, virtual_ptr<zoo::Dog, zoo_registry>>);
// end::virtual_ptr[]
Expand Down
14 changes: 8 additions & 6 deletions doc/modules/ROOT/pages/registries_and_policies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ preprocessor symbol
xref:reference:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[BOOST_OPENMETHOD_DEFAULT_REGISTRY]
_before_ including `<boost/openmethod/core.hpp>` (or any header that includes
it, like `<boost/openmethod.hpp>`). The value of the symbol is used as a default
template parameter for `use_classes`, `method`, `virtual_ptr`, and others. Once
it has been included, changing `BOOST_OPENMETHOD_DEFAULT_REGISTRY` has no
effect.
template parameter for `use_classes`, and for `method` and `virtual_ptr` where
the classes involved declare no affinity of their own - see <<Registry
affinity>> below. Once it has been included, changing
`BOOST_OPENMETHOD_DEFAULT_REGISTRY` has no effect.

For a registry the library provides, that is the whole recipe:

Expand Down Expand Up @@ -63,9 +64,10 @@ it needs no definition:
include::example$adl_registry.cpp[tag=affinity]
----

The class then _declares_ an affinity for that registry, and everything that
mentions the class finds it. A method declared without a registry argument takes the
affinity of its virtual parameters:
The class then _declares_ an affinity for that registry - and so does every
class derived from it - and everything that mentions the class finds it. A
method declared without a registry argument takes the affinity of its virtual
parameters:

[source,c++]
----
Expand Down
171 changes: 87 additions & 84 deletions include/boost/openmethod/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
//!
//! The name of the default registry.
//!
//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` is the default value for the `Registry`
//! template parameter of @ref boost::openmethod::method,
//! @ref boost::openmethod::use_classes, @ref boost::openmethod::virtual_ptr,
//! and all the constructs that take a registry as a template argument.
//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` is the registry that a construct taking
//! one as a template argument uses when neither the construct nor the class it
//! is about names another: directly, as @ref boost::openmethod::use_classes
//! does, or as the registry a class has an affinity for when it declares none
//! - which is what @ref boost::openmethod::method and
//! @ref boost::openmethod::virtual_ptr default to. See
//! @ref boost::openmethod::registry_affinity.
//!
//! `BOOST_OPENMETHOD_DEFAULT_REGISTRY` can be defined by a program to change
//! the default registry globally, *before* including
Expand Down Expand Up @@ -1045,8 +1048,9 @@ inline auto final_virtual_ptr(Arg&& obj) {

//! Create a `virtual_ptr` for an object of a known exact class.
//!
//! This is an overload of `final_virtual_ptr` that uses the default
//! registry as the `Registry` template parameter.
//! This is an overload of `final_virtual_ptr` that uses the registry the
//! object's class has an affinity for - see @ref registry_affinity - as the
//! `Registry` template parameter.
//!
//! @par Example
//!
Expand Down Expand Up @@ -1075,9 +1079,9 @@ inline auto final_virtual_ptr(Arg&& obj) {
//! "plain" `virtual_ptr` can be constructed from a smart `virtual_ptr`, but not
//! the other way around.
//!
//! The default value for `Registry` can be customized by defining the
//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY
//! preprocessor symbol.
//! `Registry` defaults to the registry `Class` has an affinity for - see
//! @ref registry_affinity - which is @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY
//! for a class that declares none.
//!
//! @par Requirements
//!
Expand Down Expand Up @@ -2259,6 +2263,76 @@ struct validate_method_parameter<
};
} // namespace detail

namespace detail {

// Every class has an affinity, but only a *declared* one constrains a method.
// A class that never declared `boost_openmethod_registry` has the default
// affinity, and yields to a parameter that declares one - which is what lets a
// method mix the two. A `virtual_ptr` parameter contributes its class's
// affinity, not the registry it names: the class decides, and a registry
// spelled on the parameter has to agree with it (validate_method_parameter).
template<typename Parameter>
struct param_affinity {
using type = default_affinity;
};

template<typename T>
struct param_affinity<virtual_<T>> {
using type = typename registry_affinity_aux<T>::declared;
};

template<class Class, class Registry>
struct param_affinity<virtual_ptr<Class, Registry, void>> {
using type = typename registry_affinity_aux<Class>::declared;
};

template<class Class, class Registry>
struct param_affinity<virtual_ptr<Class, Registry, void>&> {
using type = typename registry_affinity_aux<Class>::declared;
};

template<class Class, class Registry>
struct param_affinity<const virtual_ptr<Class, Registry, void>&> {
using type = typename registry_affinity_aux<Class>::declared;
};

// The first affinity in the parameter list wins; every other one must agree.
template<typename...>
struct agreed_affinity {
using type = default_affinity;
};

template<typename Affinity, typename... More>
struct agreed_affinity<Affinity, More...> {
using rest = typename agreed_affinity<More...>::type;
static_assert(
std::is_same_v<Affinity, default_affinity> ||
std::is_same_v<rest, default_affinity> ||
std::is_same_v<Affinity, rest>,
"virtual parameters have conflicting registry affinities");
using type = std::conditional_t<
std::is_same_v<Affinity, default_affinity>, rest, Affinity>;
};

// The registry a method takes when its declaration does not name one.
template<typename Fn>
struct method_registry_aux {
using type = macro_default_registry;
};

template<typename ReturnType, typename... Parameters>
struct method_registry_aux<ReturnType(Parameters...)> {
using found = typename agreed_affinity<
typename param_affinity<Parameters>::type...>::type;
using type = std::conditional_t<
std::is_same_v<found, default_affinity>, macro_default_registry, found>;
};

template<typename Fn>
using method_registry = typename method_registry_aux<Fn>::type;

} // namespace detail

//! Implement a method
//!
//! Methods are created by specializing the `method` class template with an
Expand All @@ -2277,11 +2351,10 @@ struct validate_method_parameter<
//! acquire a v-table pointer for an object, how to report errors, whether to
//! perform sanity checks, etc.
//!
//! The default value for `Registry` is @ref default_registry, but it can be
//! overridden by defining the preprocessor symbol
//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, *before* including
//! `<boost/openmethod/core.hpp>` (or any header that includes it, like
//! `<boost/openmethod.hpp>`). Setting the symbol afterwards has no effect.
//! `Registry` defaults to the registry the virtual parameters of `Fn` have an
//! affinity for - see @ref registry_affinity - and to
//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when none of them declares one.
//! Parameters that declare different registries are an error.
//!
//! Specializations of `method` have a single instance: the static member `fn`,
//! which has an `operator()` that forwards to the appropriate overrider. It is
Expand Down Expand Up @@ -2335,76 +2408,6 @@ struct validate_method_parameter<
//! selected is not specified, but it is the same across calls with the
//! same arguments types.
//!
namespace detail {

// Every class has an affinity, but only a *declared* one constrains a method.
// A class that never declared `boost_openmethod_registry` has the default
// affinity, and yields to a parameter that declares one - which is what lets a
// method mix the two. A `virtual_ptr` parameter contributes its class's
// affinity, not the registry it names: the class decides, and a registry
// spelled on the parameter has to agree with it (validate_method_parameter).
template<typename Parameter>
struct param_affinity {
using type = default_affinity;
};

template<typename T>
struct param_affinity<virtual_<T>> {
using type = typename registry_affinity_aux<T>::declared;
};

template<class Class, class Registry>
struct param_affinity<virtual_ptr<Class, Registry, void>> {
using type = typename registry_affinity_aux<Class>::declared;
};

template<class Class, class Registry>
struct param_affinity<virtual_ptr<Class, Registry, void>&> {
using type = typename registry_affinity_aux<Class>::declared;
};

template<class Class, class Registry>
struct param_affinity<const virtual_ptr<Class, Registry, void>&> {
using type = typename registry_affinity_aux<Class>::declared;
};

// The first affinity in the parameter list wins; every other one must agree.
template<typename...>
struct agreed_affinity {
using type = default_affinity;
};

template<typename Affinity, typename... More>
struct agreed_affinity<Affinity, More...> {
using rest = typename agreed_affinity<More...>::type;
static_assert(
std::is_same_v<Affinity, default_affinity> ||
std::is_same_v<rest, default_affinity> ||
std::is_same_v<Affinity, rest>,
"virtual parameters have conflicting registry affinities");
using type = std::conditional_t<
std::is_same_v<Affinity, default_affinity>, rest, Affinity>;
};

// The registry a method takes when its declaration does not name one.
template<typename Fn>
struct method_registry_aux {
using type = macro_default_registry;
};

template<typename ReturnType, typename... Parameters>
struct method_registry_aux<ReturnType(Parameters...)> {
using found = typename agreed_affinity<
typename param_affinity<Parameters>::type...>::type;
using type = std::conditional_t<
std::is_same_v<found, default_affinity>, macro_default_registry, found>;
};

template<typename Fn>
using method_registry = typename method_registry_aux<Fn>::type;

} // namespace detail

//! @tparam Id A type
//! @tparam Fn A function type
//! @tparam Registry The registry in which the method is defined. Defaults to
Expand Down
49 changes: 25 additions & 24 deletions include/boost/openmethod/macros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,25 @@ struct enable_forwarder<
template<class...>
struct va_args;

// `registry_for` is an alias template, not a typedef, so that the scan for an
// affinity among the virtual parameters does not run for a declaration that
// names a registry. `registry` is retained: it is the registry a declaration
// *names*, which is no longer the same question.
// `method_type` names the method rather than yielding a registry for
// BOOST_OPENMETHOD_TYPE to plug in, so that the macro spells the parameter
// list once. It is an alias template, so the specialization that omits the
// registry leaves `method`'s own default to scan the parameters for an
// affinity, and the one that names a registry never triggers that scan.
template<class ReturnType>
struct va_args<ReturnType> {
using return_type = ReturnType;
using registry = macro_default_registry;

template<class Fn>
using registry_for = method_registry<Fn>;
template<class Id, class Fn>
using method_type = method<Id, Fn>;
};

template<class ReturnType, class Registry>
struct va_args<ReturnType, Registry> {
using return_type = ReturnType;
using registry = Registry;

template<class Fn>
using registry_for = Registry;
template<class Id, class Fn>
using method_type = method<Id, Fn, Registry>;
};

template<typename...>
Expand Down Expand Up @@ -125,13 +124,10 @@ inline constexpr bool method_not_found = false;
//!
//! @see [Core API](xref:ROOT:core_api.adoc)
#define BOOST_OPENMETHOD_TYPE(ID, PARAMETERS, ...) \
::boost::openmethod::method< \
::boost::openmethod::detail::va_args<__VA_ARGS__>::method_type< \
BOOST_OPENMETHOD_ID(ID), \
::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type \
PARAMETERS, \
::boost::openmethod::detail::va_args<__VA_ARGS__>::registry_for< \
::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type \
PARAMETERS>>
PARAMETERS>

//! Declare a method.
//!
Expand Down Expand Up @@ -187,11 +183,13 @@ inline constexpr bool method_not_found = false;
//!
//! @note `ID` must be an *identifier*. Qualified names are not allowed.
//!
//! @note The default registry is the value of
//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY at the point
//! `<boost/openmethod/core.hpp>` is included, directly or through a header
//! like `<boost/openmethod.hpp>`. Changing the value of this symbol has no
//! effect after that point.
//! @note A declaration that does not name a registry takes the one its virtual
//! parameters have an affinity for - see
//! @ref boost::openmethod::registry_affinity - and
//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when none of them declares one. That
//! symbol is read at the point `<boost/openmethod/core.hpp>` is included,
//! directly or through a header like `<boost/openmethod.hpp>`; changing its
//! value has no effect after that point.
//!
//! @par Example
//!
Expand Down Expand Up @@ -550,10 +548,13 @@ inline constexpr bool method_not_found = false;
//! This macro is a wrapper around @ref boost::openmethod::use_classes; see its
//! documentation for more details.
//!
//! @note The default registry is the value of
//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY when `<boost/openmethod/core.hpp>`
//! is included, directly or through a header like `<boost/openmethod.hpp>`.
//! Subsequently changing it has no retroactive effect.
//! @note Unlike a method declaration, this macro does not consult the classes'
//! registry affinities: without a registry in the list it registers into
//! @ref BOOST_OPENMETHOD_DEFAULT_REGISTRY, whatever the classes declare. List
//! the registry last when they declare one. The symbol is read when
//! `<boost/openmethod/core.hpp>` is included, directly or through a header
//! like `<boost/openmethod.hpp>`; subsequently changing it has no retroactive
//! effect.
//!
//! @par Examples
//!
Expand Down
2 changes: 1 addition & 1 deletion test/test_adl_registry_inplace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// `inplace_vptr_base` declares the affinity itself, as a hidden friend. Since
// the hook is now the library's own, a method over such a class needs neither a
// registry argument nor a BOOST_OPENMETHOD_DEFAULT_REGISTRY override.
// registry argument nor an override of the default-registry macro.

#include <string>

Expand Down
2 changes: 1 addition & 1 deletion test/test_adl_registry_static_rtti.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)

// The ADL twin of test_static_rtti.cpp: the same registry, selected by an
// affinity instead of by BOOST_OPENMETHOD_DEFAULT_REGISTRY. Worth its own test
// affinity instead of by the default-registry macro. Worth its own test
// because a `static_rtti` registry has no `vptr` policy, so every `virtual_ptr`
// has to be created where the exact class is known.

Expand Down
Loading