Boost logo

Boost :

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.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk