How to get compatible symbol names and runtime typeid names for templated classes?
H. S. Teoh
hsteoh at quickfur.ath.cx
Tue May 3 15:08:53 UTC 2022
On Tue, May 03, 2022 at 04:38:53PM +0200, Arafel via Digitalmars-d-learn wrote:
> On 3/5/22 15:57, Adam D Ruppe wrote:
> > So doing things yourself gives you some control.
>
> Yes, it is indeed possible (I acknowledged it), but I think it's much
> more cumbersome than it should, and puts the load on the user.
>
> If templated this worked in static context (ideally everywhere else
> too), then we'd be able to implement RTTI in a 100% "pay as you go"
> way: just inherit from SerializableObject, or perhaps add a mixin to
> your own root class, and that'd be it.
>
> Actually, it would be cool to do it through an interface, although I
> don't think an interface's static constructors are invoked by the
> implementing classes... it would be cool, though.
The way I did it in my own serialization code is to use CRTP with static
ctors in templated wrapper structs.
Namely, replace:
class Base { ... }
class Derived : Base { ... }
with:
class Base : Serializable!(Base) { ... }
class Derived : Serializable!(Base, Derived) { ... }
That's the only thing user code classes need to do. The rest is done in
the Serializable proxy base class using compile-time introspection. In a
nutshell, what Serializable does is to inject serialize() and
deserialize() methods into the class hierarchy. Here's a brief sketch of
what it looks like:
class Serializable(Base, Derived = Base) : Base
{
static if (is(Base == Derived)) // this is the base of the hierarchy
{
// Base class declarations
void serialize(...) {
... // use introspection to extract data members
}
void deserialize(...) {
... // use introspection to reconstitute data members
}
}
else // this is a derived class in the hierarchy
{
override void serialize(...) {
... // use introspection to extract data members
}
override void deserialize(...) {
... // use introspection to reconstitute data members
}
}
// How does the deserializer recreate an instance of
// Derived? By registering the string name of the class
// into a global hash:
static struct Proxy // N.B.: this is instantiated for each Derived class
{
// This static this gets instantiated per
// Derived class, and uses compile-time
// knowledge about Derived to generate code for
// reconstructing an instance of Derived.
static this() {
deserializers[Derived.stringof] = {
auto obj = new Derived();
obj.deserialize(...);
return obj;
};
}
}
}
// This is module-global.
/*shared*/ static Object delegate(...)[string] deserializers;
// Global deserialize method that returns an instance of the
// class hierarchy.
Object deserialize(...) {
// Obtain class name from serialized data
string classname = ...;
// Dispatch to the correct method registered by Proxy's
// static this, that recreates the class of the required
// type.
return deserializers[classname](...);
}
The nice thing about this approach is that you have full compile-time
information about the target type `Derived`, in both the serialization
and deserialization methods. So you can use introspection to automate
away most of the boilerplate associated with serialization code. E.g.,
iterate over __traits(allMembers) to extract data fields, inspect UDAs
that allow user classes to specify how the serialization should proceed,
etc..
T
--
Doubtless it is a good thing to have an open mind, but a truly open mind should be open at both ends, like the food-pipe, with the capacity for excretion as well as absorption. -- Northrop Frye
More information about the Digitalmars-d-learn
mailing list