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_polymorphictells whetherdynamic_typecan read the class of an object of a class, which is what makes the class usable asvirtual_<T&>and lets avirtual_ptrbe built from a reference or a pointer. It need not agree with C++: see the example below. This template is required. -
static_typeis called during class registration, byvirtual_ptrs "final" constructs, and to name a class in an error or a trace. It is called for every type the library has to name, not only registered classes: a method, and each argument of abad_call, including the arguments that are not classes at all, such as anintor astd::ostream&. This function is required. -
dynamic_typereads the class of a virtual argument. The library calls it for a class thatis_polymorphicdeems polymorphic, and for no other, so a policy that deems no class polymorphic - likepolicies::static_rtti- may omit it. Providingis_polymorphicastruewithout it is an error, and is diagnosed as one. -
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 identifies a class across modules. 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: there is typically one per module. The key must compare equal for all of them, and differ for two different classes - including two classes with the same name in different translation units, such as classes defined in anonymous namespaces, so a name alone is not enough. The key is kept in an ordered and in a hashed container, so it must support==,<andstd::hash. 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 {
static const char id = 0;
return reinterpret_cast<type_id>(&id);
}
}
template<typename T>
static auto dynamic_type(const T& obj) {
static_assert(is_polymorphic<T>);
return reinterpret_cast<type_id>(obj.type);
}
};
};
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.
Note that a non-"polymorphic" type still gets an id of its own - the address of
a function-local static, as in the library’s own policies::static_rtti.
It is tempting to return a single placeholder for all of them, since they never
take part in dispatch, but static_type is also what identifies a method: a
method<…> specialization is not derived from Node, so it takes the
non-"polymorphic" branch. Hand every method the same id and initialize
merges them into one, pools their overriders, and the first call fails as an
ambiguous call. With a single method in the program nothing goes wrong, which
is what makes the mistake easy to ship.
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)" for a node, and an
unwieldy address for everything else.
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: the ids it gives to the classes that take part in
dispatch 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 {
static const char id = 0;
return reinterpret_cast<type_id>(&id);
}
}
template<typename T>
static auto dynamic_type(const T& obj) {
static_assert(is_polymorphic<T>);
return reinterpret_cast<type_id>(obj.type);
}
};
};
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> {};