Writing a generic type descriptor

October 28, 2021

A type descriptor provides meta data about a user-defined type in the model, such as a class, typedef or type alias. The TargetRTS uses information in the type descriptor to know how to initialize, copy, encode etc. an object of the type. In many cases the model compiler will generate a type descriptor automatically for a type, but in some cases it's necessary to manually write a type descriptor. If you are not familiar with type descriptors already, I recommend reading this article before proceeding further.

Now let's look at the case when the type is generic, i.e. has one or several template parameters. Previously it was necessary to create a typedef or type alias for each concrete instantiation of the template type, and write the type descriptor specifically for that template instantiation. Clearly this was tedious and often led to code duplication. Starting with Model RealTime 11.1 2021.24 it's now possible to write a generic type descriptor that uses the same template parameters as the type. This can save a lot of time and avoid code duplication.

As an example let's assume we want to send a vector with different kinds of elements between two capsules. To start with we define a type alias of std::vector and add a type template parameter for the vector type. In this case we cannot use a typedef since we want a type with template parameters.

Next, we write a type descriptor for the StdVector type alias. By using the template parameter T as the vector type, the type descriptor becomes generic.

Init function

target = new (target) std::vector<T>();

Copy function

target = new(target) std::vector<T>(*source);

Destroy function

target->~StdVector<T>();

These three type descriptor functions are all that we need for our particular example, but let's also implement the Encode function to show how a generic encode implementation could look like:

Encode function

const RTObject_class *elementTypeDescriptor = RTObject_class::fromType<T>();
if (!elementTypeDescriptor)
  return 0; // Element type descriptor not available
int sum = 0;
bool first = true;
sum += coding->write_string(type->name());
sum += coding->write_string("{");
for (auto i = source->begin(); i != source->end(); i++) {
  if (!first)
    sum += coding->write_string(",");
  first = false;
  sum += elementTypeDescriptor->encode(&*i, coding);
}
sum += coding->write_string("}");
return sum;

This implementation uses a new template function from the TargetRTS, RTObject_class::fromType<T>(), for getting the type descriptor of the vector element type. That type descriptor is then used for encoding each vector element.

The TargetRTS provides template specializations of RTObject_class::fromType<T>() for all built-in C++ types, but if you want to use the StdVector also with other types, you need to write similar specializations for them. See the TargetRTS file RTObject_class.h for examples.

The Decode function is usually the most difficult of the type descriptor functions to implement since it requires some parsing. It is beyond the scope of this newsletter to implement it. However, we cannot leave it completely empty as the model compiler then will not generate the type descriptor.

Decode function

// NOT IMPLEMENTED
return 1;

Finally, there are two other code snippets for StdVector that we should provide.

Header Preface

#include <vector>

This ensures that wherever we use the StdVector type, the underlying std::vector type will be available.

Header Ending

template <> const char* RTName_StdVector<int>::name = "StdVector<int>";
template <> const char* RTName_StdVector<char>::name = "StdVector<char>";

These template specializations are not strictly necessary, but can help when debugging. By default the type descriptor name is set to the name of the model type, i.e. StdVector in our case, and the name will be the same for all instantiations of the type descriptor. By providing specializations for the template instantiations that we use, we get a more specific name.

It should be noted that not all compilers allow such specializations to be placed in the header file.

Now let's test our StdVector type descriptor. Create a sample model with two capsules that send a vector of ints from the first to the second capsule, and then a vector of chars from the second to the first capsule. If you don't want to create the model yourself, you can use the attached model. If you build and run that model you will see this output printed:

Reached Cap2 State 1
Reached Cap1 State1 and sending Integer
Success: Received sendInteger event with data at Cap2.
Received: StdVector<int>{1,2,3}
Reached Cap2 State2 and sending Chars
Success: Received sendChar event with data at Cap1.
Received: StdVector<char>{'a','b','c'}
Reached Cap1 State2 and received Chars