
<CC-ing the mailing list, since you just replied me, and answering inline> On Tue, 6 May 2025 at 01:35, Jean-Louis Leroy <jl@leroy.nyc> wrote:
Your example is very similar to https://jll63.github.io/Boost.OpenMethod/#tutorials_custom_rtti.
virtual_ptr does not try to use boost_openmethod_vptr in its constructors. That is an oversight. It is a recent invention. YOMM2 tests for the presence of a boost_openmethod_vptr instance variable in the object directly. In that context, the function is very simple and inlinable, but if it is user-defined, there is no reason to assume that. So I will make virtual_ptr consider it as well.
Regardless, the easiest way to handle your example is to put the vptr acquisition in the policy. dynamic_type returns a type_id, not a vptr. The function for that is dynamic_vptr. It is not part of a facet, eventhough facets can provide one. The dispatch mechanism is described here: https://jll63.github.io/Boost.OpenMethod/#ref_description_17
WARNING: if you try that code with the "review" branch, make sure to pull the latest changes, because they fix a bug I introduced while re-establishing checks for non-polymorphic classes.
Thus:
// ------------------------ // you
#include <cstdint>
enum class kind : std::uintptr_t { unknown, n1, n2 };
class base_node { kind k_;
protected: base_node(kind k) noexcept : k_(k) { }
public: kind getKind() const { return k_; } };
class node1 : public base_node { public: node1() noexcept : base_node(kind::n1) { } };
class node2 : public base_node { public: node2() noexcept : base_node(kind::n2) { } };
// ------------------------ // me
// Get kind from static type, as you RTTI system does not provide it as a // static member.
template<typename T> struct kind_of { static constexpr kind value = kind::unknown; };
template<> struct kind_of<node1> { static constexpr kind value = kind::n1; };
template<> struct kind_of<node2> { static constexpr kind value = kind::n2; };
#include <boost/openmethod/policies/basic_policy.hpp>
struct custom_rtti : boost::openmethod::policies::rtti { template<class Node> static constexpr bool is_polymorphic = std::is_base_of_v<base_node, Node>;
using type_id = boost::openmethod::type_id;
template<typename T> static auto static_type() { return type_id(kind_of<T>::value); }
template<typename T> static auto dynamic_type(const T& obj) { if constexpr (is_polymorphic<T>) { return type_id(obj.getKind()); } else { return kind::unknown; } } };
struct custom_policy : boost::openmethod::policies::basic_policy<custom_policy, custom_rtti> {
static auto dynamic_vptr(const base_node& b) { switch (b.getKind()) { case kind::n1: return custom_policy::static_vptr<node1>; case kind::n2: return custom_policy::static_vptr<node2>; default: return custom_policy::static_vptr<base_node>; } } };
#define BOOST_OPENMETHOD_DEFAULT_POLICY custom_policy
#include <iostream>
#include <boost/openmethod.hpp> #include <boost/openmethod/compiler.hpp>
BOOST_OPENMETHOD(process, (std::ostream&, virtual_ptr<base_node>), void);
BOOST_OPENMETHOD_OVERRIDE( process, (std::ostream & os, virtual_ptr<node1> node1), void) { os << "process node1\n"; }
BOOST_OPENMETHOD_OVERRIDE( process, (std::ostream & os, virtual_ptr<node2> node2), void) { os << "process node2\n"; }
BOOST_OPENMETHOD_CLASSES(base_node, node1, node2);
auto main() -> int { boost::openmethod::initialize();
auto a = std::make_unique<node1>(); auto b = std::make_unique<node2>();
process(std::cout, *a); process(std::cout, *b);
return 0; }
We could also take advantage of the fact that type_ids fall in a short, compact range. We can use the vptr_vector facet, but there is no need to hash the type_ids, we can use them as straight indexes. So we don't use a type_hash facet:
struct custom_policy : boost::openmethod::policies::basic_policy< custom_policy, custom_rtti, boost::openmethod::policies::vptr_vector<custom_policy>> {};
This time dynamic_vptr() is provided by a facet. Everything else stays the same.
For the reference, this is what I ended up doing: enum class kind : std::uintptr_t { unknown, n1, n2 }; class base_node { kind k_; protected: base_node(kind k) noexcept : k_(k) { } public: kind getKind() const { return k_; } }; class node1 : public base_node { public: static constexpr kind node_kind = kind::n1; node1() noexcept : base_node(node_kind) { } }; class node2 : public base_node { public: static constexpr kind node_kind = kind::n2; node2() noexcept : base_node(node_kind) { } }; constexpr const char* kind_to_string(kind k) { switch (k) { case kind::n1: return "node1"; case kind::n2: return "node2"; default: return "<unknown node type>"; } } struct custom_rtti : boost::openmethod::policies::rtti { template<class T> static constexpr bool is_polymorphic = std::is_base_of_v<base_node, T>; template<typename T> static auto static_type() -> std::uintptr_t { if constexpr ( std::is_base_of_v<base_node, T> && !std::is_same_v<base_node, T>) { return static_cast<std::uintptr_t>(T::node_kind); } else { return static_cast<std::uintptr_t>(kind::unknown); } } template<typename T> static auto dynamic_type(const T& obj) -> std::uintptr_t { if constexpr (is_polymorphic<T>) { return static_cast<std::uintptr_t>(obj.getKind()); } else { return static_cast<std::uintptr_t>(kind::unknown); } } template<class Stream> static void type_name(std::uintptr_t type, Stream& stream) { stream << kind_to_string(static_cast<kind>(type)); } }; struct custom_policy : boost::openmethod::policies::basic_policy< custom_policy, custom_rtti, boost::openmethod::policies::runtime_checks, boost::openmethod::policies::basic_error_output<custom_policy>, boost::openmethod::policies::vptr_vector<custom_policy>> {}; BOOST_OPENMETHOD_CLASSES(base_node, node1, node2, custom_policy) BOOST_OPENMETHOD(print, (virtual_<base_node>), void, custom_policy); BOOST_OPENMETHOD_OVERRIDE(print, (base_node&), void) { /* whatever */ } I've just realized that I've disabled hashing by accident, and this happened to work by accident. I think that this implicit dependency between the vptr_vector and the hashing policy is a problem - I prefer explicit dependencies.
As you see in this example, we don't have to fork policies, we can just construct them from scratch. Forking was introduced with the idea of tuning default_policy, without having to replicate it from scratch. Also the with/without setters are less verbose than the add/replace/remove ones.
Elsewhere I said that facets categories are a closed set. That is not correct. The dispatcher and the compiler look only at a closed set of facets (using if constexpr (Policy::has_facet>)). But some facets, like vptr_vector and error_handler, look for other facets in the policy, which are not necessarily referenced directly by the compiler or the dispatcher. In that sense, the set of facets is actually open.
I am going to think about your suggested alternative to the facet system. Seriously, thus it will take me a few days. In the meantime, how would you see the two example policies above, expressed with your system?
This is what it'd look like with my proposal: struct custom_rtti : openmethod::policies::rtti { template<class T> static constexpr bool is_polymorphic = std::is_base_of_v<base_node, T>; template<typename T> auto static_type() const -> std::uintptr_t { /* same definition */ } template<typename T> auto dynamic_type(const T& obj) const -> std::uintptr_t { /* same definition */ } template<class Stream> void type_name(std::uintptr_t type, Stream& stream) const { /* same definition */ } }; struct custom_policy : boost::openmethod::policies::abstract_policy { // enable runtime checks openmethod::policies::runtime_checks runtime_checks; // enable the default error output openmethod::policies::basic_error_output<> error_output; // pass the hashing policy to vptr_vector explicitly, rather than implicitly via the entire Policy // disable_hashing should be either a marker type to tell vptr_vector "I don't have any hashing available" // or implement the type_hash facet and be a no-op (I guess the 1st option is better) openmethod::policies::vptr_vector<openmethod::policies::disable_hashing> extern_vptr; }; // Now register the classes. See below for the definition of registry BOOST_OPENMETHOD_CLASSES(base_node, node1, node2, openmethod::registry<custom_policy>) BOOST_OPENMETHOD(print, (virtual_<base_node>), void, openmethod::registry<custom_policy>); BOOST_OPENMETHOD_OVERRIDE(print, (base_node&), void) { /* whatever */ } // This type needs to be provided by the library, and is in charge of creating the required static storage namespace boost::openmethod { template <class Policy> struct registry { using policy_type = Policy; static Policy policy; }; } There are likely things I'm not seeing now that will probably complicate this. If the review is favorable, I can write a PR with the proposed changes and see if it's viable.
I would love to ship a fully functional clang RTTI policy right off the bat; it is an important use-case.
J-L
Thanks, Ruben.