Custom RTTI
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;
};
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::default. 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::default 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 straight 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 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);
}
}
};
};