Metatype's discriminated unions

For the readers not knowing, Metatype is a data-type definition language and a C++ code generator. Metatype may be used for the specification and implementation complex data protocols. Compared to other data definition languages, Metatype has one speciality: It supports the declaration and generation of discriminated unions. Update: included example for constructing unions.

Discriminated unions is one essential feature in Metatype. Using these unions, it is possible to define complex data structures for communication protocols or abstract syntax trees (in fact, in the latest revision, Metatype’s AST for its input files is generated by Metatype). Metatype does even more, it optionally creates serializers/deserializers for all data-types and – in the latest version – supports Metatype-Scheme, a definition language for Meta-Information describing the exact layout of the generated data structures (this is similar to Reflections in Java). BTW: Metatype-Scheme is also modelled in Metatype.

But back to the topic for this article: the unions. So here is an example of a very simple type named VarType. VarType is either a string, an integer or an extended type (a name/value) pair. In Metatype the declaration looks like the following:


struct Extended
{ string name; string value;
};

union VarType
{ string; int; Extended;
};

Like in C++ we declare a struct Extended to be used later in the VarType union. Then we define the union type to contain the string, the integer and the extended type. It is as simple as that. Unions need no name to distinguish their subtypes, so it is not allowed to use the same type twice in one union.

Let’s view what we get generated by the Metatype’s code-generator for C++ (mtcpp):

#ifndef __example_hpp
#define __example_hpp

struct VarType;

struct Extended
{ Extended() {} Extended(const std::string& name, const std::string& value) : name(name) , value(value) {}

std::string name; std::string value; };

struct VarType
{ VarType() : s(Svoid) {} VarType(const std::string& r) : s(Sstring), u(new std::string®) {} VarType(const int r) : s(Sint), u(new int®) {} VarType(const Extended& r) : s(SExtended), u(new Extended®) {} ~VarType() { free(); }

VarType(const VarType& r) : s(r.s), u(alloc®) {} VarType& operator = (const VarType& r) { if (this != &r) {U lu = alloc®; free(); s = r.s; u = lu;} return *this; } template typename D::Result operator >> (D& d) const { switch(s) { case Svoid: throw; case Sstring: return d(*u.ustring); case Sint: return d(*u.uint); case SExtended: return d(*u.uExtended); } throw; } enum S { Svoid, Sstring, Sint, SExtended }; S s;

private: union U { U() : uvoid(0) {} U(std::string* p) : ustring(p) {} U(int* p) : uint(p) {} U(Extended* p) : uExtended(p) {}

void* uvoid; std::string* ustring; int* uint; Extended* uExtended; }; U u; static U alloc(const VarType& r); void free(); };

#endif // __example_hpp

Metatype tries to be as helpful as possible, it creates the include-guards, a default-constructor, a full-featured constructor for the Extended type and the complete descriminated union type, including implicit constructors to create the union by one of its subtypes.

The construction of our new type is straight forward:

VarType vtInt = 10;
VarType vtString = string("Welcome");
VarType vtAnotherString = VarType("Uses implicit constructor of string");
VarType vtExtended = Extended("Name", "Value");

Additionally Metatype creates a dispatching operator >, to support dispatching this union’s subtype.

For example, to dump this union, a small dispatcher may be generated:


struct Dump
{ typedef void Result;

void operator () (const Extended& e) { cout << e.name << “: “ << e.value << endl; } void operator () (const string& s) { cout << “\”“ << s << “\”“ << endl; } void operator () (int i) { cout << i << endl; } };

void dump(const VarType& vt)
{ Dump d; vt << d;
}

The dispatching operator >> always expects one function operator for each subtype to be declared in the dispatcher-class. Additionally the Result type must be declared to specify the return type of the function operators.

Using Metatype’s unions, their dispatchers and the automatically generated serializers and deserializers, it is possible to replace complex RPC protocols with one data-source for the server and the client applications.

Metatype is used to implement the Paramatrix 3D client/server protocol.

Metatype is part of Libsmart.