Custom RTTI
Stock policies use the std_rtti
implementation of rtti
. Here is its full
source:
struct std_rtti : rtti {
template<class Class>
static constexpr auto is_polymorphic = std::is_polymorphic_v<Class>;
template<typename T>
static type_id static_type() {
return reinterpret_cast<type_id>(&typeid(T));
}
template<typename T>
static type_id dynamic_type(const T& obj) {
return reinterpret_cast<type_id>(&typeid(obj));
}
template<class Stream>
static void type_name(type_id type, Stream& stream) {
stream << reinterpret_cast<const std::type_info*>(type)->name();
}
static std::type_index type_index(type_id type) {
return std::type_index(*reinterpret_cast<const std::type_info*>(type));
}
template<typename D, typename B>
static D dynamic_cast_ref(B&& obj) {
return dynamic_cast<D>(obj);
}
};
-
is_polymorphic
is used to check if a class is polymorphic. This template is required. -
static_type
is used by class registration, byvirtual_ptr
's "final" constructs, and to format error and trace messages.T
is not restricted to the classes that appear as virtual parameters. This function is required. -
dynamic_type
is used to locate the v-table for an object. This function is usually required. If only thevirtual_ptr
"final" constructs are used, or ifboost_openmethod_vptr
is provided for all the classes in the policy, it can be omitted. -
type_name
writes a representation oftype
tostream
. It is used to format error and trace messages.Stream
is a lighweight version ofstd::ostream
with reduced functionality. It only supports insertion ofconst char*
,std::string_view
, pointers andstd::size_t
. This function is optional; if it is not provided, "type_id(type)" is used. -
type_index
returns an object that uniquely identifies a class. Some forms of RTTI (most notably, C++'stypeid
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
castsobj
to classD
.B&&
is either a lvalue reference (possibly cv-qualified) or a rvalue reference.D
has the same reference category (and cv-qualifier if applicable) asB
. This function is required only in presence of virtual inheritance.
Consider a custom RTTI implementation:
struct Animal {
Animal(unsigned type) : type(type) {
}
virtual ~Animal() = default;
unsigned type;
static constexpr unsigned static_type = 1;
};
struct Cat : Animal {
Cat() : Animal(static_type) {
}
static constexpr unsigned static_type = 2;
};
// ditto for Dog
This scheme has an interesting property: its type ids are monotonically allocated in a small, dense range. Thus, we don’t need to hash them. We can use them as indexes in the table of vptrs.
This time we are going to replace the default policy globally. First we need to
define the custom RTTI facet. We must not include
<boost/openmethod/core.hpp>
or any header that includes it yet.
Here is the facet implementation:
namespace bom = boost::openmethod;
struct custom_rtti : bom::policies::rtti {
template<class Registry>
struct fn : bom::policies::rtti::fn<Registry> {
template<class T>
static constexpr bool is_polymorphic = std::is_base_of_v<Animal, T>;
template<typename T>
static auto static_type() -> bom::type_id {
if constexpr (is_polymorphic<T>) {
return reinterpret_cast<bom::type_id>(T::static_type);
} else {
return nullptr;
}
}
template<typename T>
static auto dynamic_type(const T& obj) -> bom::type_id {
if constexpr (is_polymorphic<T>) {
return reinterpret_cast<bom::type_id>(obj.type);
} else {
return nullptr;
}
}
};
};
This facet is quite minimal. It does not support virtual inheritance. It would not produce good error or trace messages, because types would be represented by their integer ids.
This time we create a policy from scratch. For that we use the basic_policy
CRTP template:
struct custom_policy : bom::registry<custom_rtti, bom::policies::vptr_vector> {
};
#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_policy
Next, we include the main header. Because BOOST_OPENMETHOD_DEFAULT_REGISTRY
is
defined, its value is used for the default policy. Then comes the usual example.
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
using boost::openmethod::virtual_ptr;
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Cat> /*cat*/), void) {
os << "hiss";
}
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Dog> /*dog*/), void) {
os << "bark";
}
BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);
auto main() -> int {
boost::openmethod::initialize();
std::unique_ptr<Animal> a(new Cat);
std::unique_ptr<Animal> b(new Dog);
poke(std::cout, *a); // prints "hiss"
std::cout << "\n";
poke(std::cout, *b); // prints "bark"
std::cout << "\n";
return 0;
}
This programs works even if standard RTTI is disabled.
Deferred RTTI
In the previous example, the RTTI system assigns types id statically. It is more
common to allocate them using a global counter, manipulated by static
constructors. This is a problem, because static_type
is used by class
registration. It may read the custom type ids before they are have been
initialized.
The solution is to add the deferred_static_rtti
facet to the policy; it defers
reading the type information until initialize
is called.
This time let’s support virtual inheritance as well. First the domain classes:
struct custom_type_info {
static unsigned last;
unsigned id = ++last;
};
unsigned custom_type_info::last;
struct Animal {
Animal() {
type = type_info.id;
}
virtual ~Animal() = default;
virtual auto cast_impl(unsigned target) -> void* {
if (type_info.id == target) {
return this;
} else {
return nullptr;
}
}
template<class Class>
auto cast() -> Class* {
return reinterpret_cast<Class*>(cast_impl(Class::type_info.id));
}
static custom_type_info type_info;
unsigned type;
};
custom_type_info Animal::type_info;
struct Cat : virtual Animal {
Cat() {
type = type_info.id;
}
virtual auto cast_impl(unsigned target) -> void* {
if (type_info.id == target) {
return this;
} else {
return Animal::cast_impl(target);
}
}
static custom_type_info type_info;
};
custom_type_info Cat::type_info;
// ditto for Dog
The rtti facet is the same, with one more function:
struct custom_rtti : bom::policies::rtti {
// as before
// to support virtual inheritance:
template<typename Derived, typename Base>
static auto dynamic_cast_ref(Base&& obj) -> Derived {
using base_type = std::remove_reference_t<Base>;
if constexpr (std::is_base_of_v<Animal, base_type>) {
return *obj.template cast<std::remove_reference_t<Derived>>();
} else {
abort(); // not supported
}
}
};
Finally, the policy contains an additional facet - deferred_static_rtti
:
struct custom_policy
: bom::policies::basic_policy<
custom_policy, custom_rtti,
bom::policies::deferred_static_rtti, // <-- additional facet
bom::policies::vptr_vector<custom_policy>> {};
The example is the same as in the previous section.