CRTP + compile-time introspection + static ctors = WIN

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Jan 18 17:15:51 UTC 2021


On Mon, Jan 18, 2021 at 05:49:03PM +0100, Jacob Carlborg via Digitalmars-d wrote:
> On 2021-01-15 19:31, H. S. Teoh wrote:
> 
> > Yesterday, I hit upon a nice solution: use CRTP (the
> > Curiously-Recursive Template Pattern) to inject these methods into
> > each derived class:
> > 
> > 	class Saveable(Derived, Base) : Base {
> > 		static if (is(Base == Object)) {
> > 			// Top-level virtual function
> > 			void save() { ... }
> > 		} else {
> > 			// Derived class override
> > 			override void save() { ... }
> > 		}
> > 	}
> > 
> > 	class Base : Saveable!(Base, Object) { ...  }
> > 
> > 	class Derived1 : Saveable!(Derived1, Base) { ... }
> > 
> > 	class Derived2 : Saveable!(Derived1, Base) { ... }
> > 
> 
> That's an interesting idea. Although it's a bit intrusive since it
> requires changing what you're serializing.

True, but I was looking for a maximally-automated, minimal-boilerplate
solution.


> In my serialization library Orange [1] I solved this by registering
> subclasses that are going to be serialized through a base class
> reference [2]. If they're not serialized through a base class
> reference, no registration is required.
> 
> [1] https://github.com/jacob-carlborg/orange
> [2] https://github.com/jacob-carlborg/orange/blob/1c4b1ab989fc36e6fae91131ba6951acf074f383/tests/BaseClass.d#L73
[...]

I also considered this approach, but rejected it because forgetting to
register a derived class would result in incorrect serialization. I felt
that was too fragile for my needs.

My current serialization need is quite specific in scope: I have a bunch
of arrays, AA's, structs, and classes, all of which are data-only (i.e.,
public data fields only, no special semantics via setters/getters).  A
small number of types may require special serialization/deserialization
treatment; for this the serialization code detects the existence of
custom .save/.load methods.  Other than that, serialization is automated
from the root object.  Since root objects are very general, and as
development goes on the exact combination of contained types may change,
so a solution that does not require explicit registration of types is
ideal.

Using my solution above, the only thing I need to check is that the
derived class derives from Saveable, which can be done the first time I
declare it. It's highly visible, so accidental omission can be
immediately noticed.  Nothing else needs to be done, as the rest of the
mechanisms are fully automated from that point on.


T

-- 
In theory, there is no difference between theory and practice.


More information about the Digitalmars-d mailing list