Using the reflective type system

The types ValueHandle and Meta:BaseType provide support for reflection. The former type is a handle to a value of an SPL type. The latter type is a meta-type that represents an SPL type.

The ValueHandle can be used to inspect and modify SPL typed values.

Assume that there is an SPL tuple of type tuple<rstring name, list<rstring> locations, tuple<rstring type, float32 value, int32 level> info> and you want to print the location strings that are contained in the locations attribute, which is a list, and thelevel information that is contained in the info attribute, which is a nested tuple. The following is an example of a code segment.

void process(Tuple & tuple, ...) {
  TupleIterator iter = tuple.findAttribute("locations");
  assert(iter!=tuple.getEndIterator());
  TupleAttribute locsAttrb = *iter;
  ValueHandle locsHandle = locsAttrb.getValue();
  assert(locsHandle.getMetaType()==Meta::Type::LIST);
  List & locations = locsHandle;
  for(ListIterator listIter=locations.getBeginIterator();
        listIter!=locations.getEndIterator(); ++listIter) {
      ValueHandle locHandle = *listIter;
      assert(locHandle.getMetaType()==Meta::Type::RSTRING);
      rstring & location = locHandle;
      cerr << "location: " << location << endl;
  }
  iter = tuple.findAttribute("info");
  TupleAttribute infoAttrb = *iter;
  ValueHandle infoHandle = infoAttrb.getValue();
  assert(infoHandle.getMetaType()==Meta::Type::TUPLE);
  Tuple & info = infoHandle;
  ValueHandle levelHandle = info.getAttributeValue("level");
  int32 & level = levelHandle;
  cerr << "level: " << level;
}

A value handle can be queried for its meta-type through the getMetaType function, which returns an enumeration of type SPL::Meta::Type. In the example, this enumeration is used to make sure that the value handle that corresponds to the locations attribute is a list. When the meta-type is determined, the value handle can be cast to a more specific type, as in List & locations = handle; and rstring & location = locHandle;. When a value handle is cast to a reference type, any changes that are performed through the reference affect the actual value that is stored behind the value handle. For instance, location="NY"; would change the value of the item that is stored in the locations list in the example. SPL also provides a ConstValueHandle type for handles that do not allow modification of the values.

When the concrete type of a value is not known, sometimes discovering the type structure by using value handles and incrementally removing the type layers as shown so far is impractical. For instance, consider a value of type map<list<list<rstring[12]>[4]>,int32>. If this map is empty, then it is not possible to discover the type structure by just traversing its value. For such cases, SPL provides a function BaseType::makeType that takes any SPL typed value or a value handle, and returns a value of BaseType that represents the type of the SPL value that is passed in. BaseType sits at the top of a class hierarchy that represents all SPL types, such as ListType, TupleType. This example illustrates this concept.

using namespace SPL::Meta;
Map & mp = ...; // map<list<list<rstring[12]>[4]>,int32>

BaseType const & m0_btp = BaseType::makeType(mp);
assert(m0_btp.getMetaType()==Type::MAP);
MapType const & m0_mtp = dynamic_cast<MapType const &>(m0_btp);

BaseType const & m1_btp = m0_mtp.getKeyType();
assert(m1_btp.getMetaType()==Type::LIST);
ListType const & m1_ltp = dynamic_cast<ListType const &>(m1_btp);

BaseType const & m2_btp = m1_ltp.getElementType();
assert(m2_btp.getMetaType()==Type::BLIST);
BListType const & m2_bltp = dynamic_cast<BListType const &>(m2_btp);

BaseType const & m3_btp = m2_bltp.getElementType();
assert(m3_btp.getMetaType()==Type::BSTRING);
BStringType const & m3_bstp = dynamic_cast<BStringType const&>(m3_btp);

BaseType const & m4_btp = m0_mtp.getValueType();
assert(m4_btp.getMetaType()==Type::INT32);
SimplePrimitiveType const & m4_nptp =
     dynamic_cast<SimplePrimitiveType const &>(m4_btp);

The code is greatly simplified through assertions. If a type is unknown, various switch or case statements are needed to truly handle all possibilities.