CompactSerializable Pattern: template deduction guides to instantiate minimum number of required members with easy syntax

Here I demonstrate an interesting pattern I call “CompactSerializable” which is neat when you want the following:

  • quick and easy to use initialization syntax
  • your object needs to have multiple members
  • your object needs to have have different types of members, which are not known at design time
  • your object must not have members you will not use (occupy minimum memory)

We first declare template classes, each representing a “dimension”. While it might be weird to have multiple dimensions, each a different type, it’s simply the common nomenclature for vector/matrix objects, so not strictly representing an actual dimension. Good names would also have been “Layer” or “MemberNumber”, etc.

In this example we will only be using 3 dimensions.

template <typename T1> struct dimension1 {
  dimension1<T1>() = default;
  dimension1(T1 nx) : x(nx) {}

  T1 x;
};

template <typename T1, typename T2> struct dimension2 : public dimension1<T1> {
  dimension2<T1, T2>() = default;
  dimension2(T1 nx, T2 ny) : dimension1<T1>(nx) , y(ny) {}
  T2 y;
};

template <typename T1, typename T2, typename T3>
struct dimension3 : public dimension2<T1, T2> {
  /*for the sake of verbosity we will leave default
  constructors uninitialized*/
  dimension3<T1, T2, T3>() = default;
  dimension3(T1 nx, T2 ny, T3 nz) : dimension2<T1,T2>(nx,ny) , z(nz) {}
  T3 z;
};

We then declare the object itself. The default constructor is deleted because the goal is to determine member type(s) via constructor initialization, so an null object wouldn’t make sense.


template <typename T> struct CompactSerializable {

  CompactSerializable() = delete;

  template <typename T1> 
  CompactSerializable(T1 nx) : value(nx) {}

  template <typename T1, typename T2> 
  CompactSerializable(T1 nx, T2 ny) {
    value.x = nx;
    value.y = ny;
  }

  template <typename T1, typename T2, typename T3>
  CompactSerializable(T1 nx, T2 ny, T3 nz) {
    value.x = nx;
    value.y = ny;
    value.z = nz;
  }

  T value;
};

Finally, we use template deduction guides to instantiate the object with the right sized dimension.

template <typename T1>
CompactSerializable(T1) -> 
CompactSerializable<dimension1<T1>>;

template <typename T1, typename T2>
CompactSerializable(T1, T2) -> 
CompactSerializable<dimension2<T1, T2>>;

template <typename T1, typename T2, typename T3>
CompactSerializable(T1, T2, T3) -> 
CompactSerializable<dimension3<T1, T2, T3>>;

If we want to quickly serialize some object one after another, we can do it!

Usage:

CompactSeralizable obj1(
	Foo(5), 
	Bar(6, "pretty"));

SeralizeAndSendOverNetwork(obj1);

CompactSeralizable obj2(
	*ptrToComplexObject, 
	AdditionalInfo(100, "test"), 
	Foo(1024));

SeralizeAndSendOverNetwork(obj2);

There would of course have to be functions written for serializing each of the possible object types, as are commonly written anyway.

Leave a Reply

Your email address will not be published. Required fields are marked *