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_polymorphicis used to check if a class is polymorphic. This template is required. -
static_typeis called during class registration, byvirtual_ptrs "final" constructs. It is also called to setbad_call::method. This function is required. -
dynamic_typeis used to read the dynamic type of a virtual argument. If only thevirtual_ptr"final" constructs are used, or ifboost_openmethod_vptris provided for all the classes in the registry, this function can be omitted. -
type_namewrites a representation of a type to aLightweightOutputStream. 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_indexreturns an object that uniquely identifies a class. Some forms of RTTI (most notably, C++'stypeidoperator) do not guarantee that the type information object for a class is unique within the same program. This function is optional; if not provided,typeis assumed to be unique, and used as is. -
dynamic_cast_refcasts an object to a class. It takes a reference, and returns a reference of the same category (and cv-qualifier if applicable) asB. 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;
};
We are going to replace the default registry for the whole program. That takes
two lines before the library is included: a declaration of the registry class,
and a definition of
BOOST_OPENMETHOD_DEFAULT_REGISTRY
naming it. The registry itself comes further down; only its name has to be
known when <boost/openmethod/core.hpp> is parsed - which any header that
includes it, <boost/openmethod.hpp> among them, does for you.
struct custom_registry;
#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry
#include <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>
Now we can define a rtti policy for our scheme. Everything it needs is
already in scope: the rtti category and its rtti::defaults came in
with <boost/openmethod.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> {};
This is the definition promised by the declaration shown above. It must appear
before the first BOOST_OPENMETHOD, BOOST_OPENMETHOD_OVERRIDE or
BOOST_OPENMETHOD_CLASSES: those instantiate the registry, and an incomplete
type will not do.
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:
// 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);
}
}
};
};
The setup is the same as before - the registry is declared, named as the default, and defined once the library is in scope:
struct custom_registry;
#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry
#include <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>
struct custom_registry : boost::openmethod::registry<
custom_rtti, boost::openmethod::policies::vptr_vector> {};