The original motivation for the policy mechanism is to make it possible to interface OpenMethod with custom RTTI systems.

Stock registries use the std_rtti implementation of rtti. Here is its full source:

struct std_rtti : rtti {
    template<class Registry>
    struct fn {
        template<class Class>
        static constexpr bool is_polymorphic = std::is_polymorphic_v<Class>;

        template<class Class>
        static auto static_type() -> type_id {
            return &typeid(Class);
        }

        template<class Class>
        static auto dynamic_type(const Class& obj) -> type_id {
            return &typeid(obj);
        }

        template<typename Stream>
        static auto type_name(type_id type, Stream& stream) -> void {
            stream << boost::core::demangle(
                reinterpret_cast<const std::type_info*>(type)->name());
        }

        static auto type_index(type_id type) -> std::type_index {
            return std::type_index(
                *reinterpret_cast<const std::type_info*>(type));
        }

        template<typename D, typename B>
        static auto dynamic_cast_ref(B&& obj) -> D {
            return dynamic_cast<D>(obj);
        }
    };
};
  • is_polymorphic is used to check if a class is polymorphic. This template is required.

  • static_type is called during class registration, by virtual_ptrs "final" constructs. It is also called to set bad_call::method. This function is required.

  • dynamic_type is used to read the dynamic type of a virtual argument. If only the virtual_ptr "final" constructs are used, or if boost_openmethod_vptr is provided for all the classes in the registry, this function can be omitted.

  • type_name writes a representation of a type to a LightweightOutputStream. It is used to format error and trace messages. This function is optional; if it is not provided, type is rendered as "type_id(type)".

  • type_index returns an object that uniquely identifies a class. Some forms of RTTI (most notably, C++'s typeid operator) do not guarantee that the type information object for a class is unique within the same program. This function is optional; if not provided, type is assumed to be unique, and used as is.

  • dynamic_cast_ref casts an object to a class. It takes a reference, and returns a reference of the same category (and cv-qualifier if applicable) as B. This function is required only in presence of virtual inheritance.

Let’s rewrite the Node example using several variations of custom RTTIs.

In the first example, we use a "static" scheme, where the type ids are compile-time constants:

struct Node {
    virtual ~Node() {}
    virtual int value() const = 0;
    // our custom RTTI:
    Node(unsigned type) : type(type) {}
    unsigned type;
    static constexpr unsigned static_type = 1;
};

struct Variable : Node {
    static constexpr unsigned static_type = 2;
    Variable(int value) : Node(static_type), v(value) {}
    int value() const override { return v; }
    int v;
};

struct Plus : Node {
    static constexpr unsigned static_type = 3;
    Plus(const Node& left, const Node& right)
        : Node(static_type), left(left), right(right) {}
    int value() const override { return left.value() + right.value(); }
    const Node& left; const Node& right;
};

struct Times : Node {
    static constexpr unsigned static_type = 4;
    Times(const Node& left, const Node& right)
        : Node(static_type), left(left), right(right) {}
    int value() const override { return left.value() * right.value(); }
    const Node& left; const Node& right;
};

Let’s define a rtti policy for this scheme. We are going to replace the default registry globally, so we do not include <boost/openmethod/core.hpp> or <boost/openmethod.hpp> - only what we need to implement the policy:

#include <boost/openmethod/preamble.hpp>
#include <boost/openmethod/policies/vptr_vector.hpp>

struct custom_rtti : boost::openmethod::policies::rtti {
    template<class Registry>
    struct fn : defaults {
        template<class T>
        static constexpr bool is_polymorphic = std::is_base_of_v<Node, T>;

        using type_id = boost::openmethod::type_id; // for brevity

        template<typename T>
        static auto static_type() {
            if constexpr (is_polymorphic<T>) {
                return reinterpret_cast<type_id>(T::static_type);
            } else {
                return reinterpret_cast<type_id>(0);
            }
        }

        template<typename T>
        static auto dynamic_type(const T& obj) {
            if constexpr (is_polymorphic<T>) {
                return reinterpret_cast<type_id>(obj.type);
            } else {
                return reinterpret_cast<type_id>(0);
            }
        }
    };
};

The policy’s notion of "polymorphic" is different from C++'s. Here, a class is "polymorphic" if it derives from Node, meaning that dynamic_type can extract the runtime type of a virtual argument. In fact, this policy does not require Node to be polymorphic in the C++ sense. If we remove the virtual destructor and implement value as an open-method as well, the program will still work.

The policy is quite minimal. It does not support virtual inheritance, because it does not provide a dynamic_cast_ref function. It would not produce good error or trace messages, because it does not provide a type_name function. Instead, it relies on the type_name inherited from rtti::defaults. It renders types as adorned integers, e.g. "type_id(2)". All non-"polymorphic" types would be rendered the same way, as "type_id(0)".

rtti::defaults also provides a default implementation for type_index, which simply returns its argument.

Now we need a policy to get a v-table pointer from an object. Our RTTI system has an interesting property: its type ids are monotonically allocated in a small, dense range. It means that we can use them as indexes in a vector. vptr_vector is perfect for that. So here is our registry:

struct custom_registry : boost::openmethod::registry<
        custom_rtti, boost::openmethod::policies::vptr_vector> {};

#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry

Defining macro BOOST_OPENMETHOD_DEFAULT_REGISTRY sets the default registry used by all library components that need one.

Next, we include the main header. Because BOOST_OPENMETHOD_DEFAULT_REGISTRY is defined, its value is used for the default registry.

The rest of the example is unchanged.

Deferred RTTI

In the previous example, the RTTI system assigns type ids statically. Another popular approach is to allocate type ids using a global counter, manipulated by static constructors. Like so:

struct Node {
    Node(unsigned type) : type(type) {}
    unsigned type;
    static unsigned last_type;
    static unsigned static_type;
};

struct Variable : Node {
    static unsigned static_type;
    Variable(int value) : Node(static_type), v(value) {}
    int v;
};

struct Plus : Node {
    static unsigned static_type;
    Plus(const Node& left, const Node& right)
        : Node(static_type), left(left), right(right) {}
    const Node& left; const Node& right;
};

struct Times : Node {
    static unsigned static_type;
    Times(const Node& left, const Node& right)
        : Node(static_type), left(left), right(right) {}
    const Node& left; const Node& right;
};

The type ids are assigned in another translation unit:

unsigned Node::last_type = 0;
unsigned Node::static_type = ++Node::last_type;
unsigned Variable::static_type = ++Node::last_type;
unsigned Plus::static_type = ++Node::last_type;
unsigned Times::static_type = ++Node::last_type;

This is a problem, because static_type is called by BOOST_OPENMETHOD_CLASSES, also during static construction. There is no guarantee regarding the order of execution of static constructors across several translation units. BOOST_OPENMETHOD_CLASSES risks reading the type ids before they have been assigned.

The solution is to derive the rtti policy from deferred_static_rtti. Doing so postpones reading the type ids until initialize is called:

#include <boost/openmethod/preamble.hpp>
#include <boost/openmethod/policies/vptr_vector.hpp>

//                                          note: vvvvvvvvvvvvvvvv
struct custom_rtti : boost::openmethod::policies::deferred_static_rtti {
    template<class Registry>
    struct fn : defaults {
        template<class T>
        static constexpr bool is_polymorphic = std::is_base_of_v<Node, T>;

        using type_id = boost::openmethod::type_id; // for brevity

        template<typename T>
        static auto static_type() {
            if constexpr (is_polymorphic<T>) {
                return reinterpret_cast<type_id>(T::static_type);
            } else {
                return reinterpret_cast<type_id>(0);
            }
        }

        template<typename T>
        static auto dynamic_type(const T& obj) {
            if constexpr (is_polymorphic<T>) {
                return reinterpret_cast<type_id>(obj.type);
            } else {
                return reinterpret_cast<type_id>(0);
            }
        }
    };
};