$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: Ruben Perez (rubenperez038_at_[hidden])
Date: 2025-05-06 13:51:21
<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_at_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.