# Type system ¶

libqi does type erasure in similar way to Python. A type erased value is composed of a  void*  and a  TypeInterface*  (see  AnyReferenceBase  ). The  TypeInterface  class is abstract and has a  kind()  method which returns an enum value.

To each kind corresponds a  TypeInterface  , like  ListTypeInterface  ,  ObjectTypeInterface  , etc. A value can’t be of multiple kinds. The list of all kinds is in fwd.hpp and their mapping in signatures is in signature.hpp .

This system allows a value to hold anything, for example a PyObject representing an integer. This PyObject will not be converted to a C++ integer until it is needed. Note that it may be never needed if the value is passed back to Python code.

Type interfaces usually exist as unique instances within the program. This is not enforced though and it may happen that a type interface exist multiple times within a single program.

Type interfaces should be stateless, this is not enforced though and their functions are not marked  const  .

## About kinds ¶

A value of type Unknown cannot be used in a type-erased context. Its only use is when you use an unregistered type to do a type-erased call, if you give a type T and your function takes exactly a type T (the  std::type_info  must be equal), then the type system uses it as an Unknown value, but the call succeeds because the value is not transformed. You have the same behavior if you do  qi::AnyValue::from(someT)  and then  yourvalue.as<T>()  .

A value of type Pointer is a typed pointer, it can be a raw pointer or a shared pointer. If it is a raw pointer, libqi does nothing to manage its lifetime, it’s up to the user to handle that.

A value of type Dynamic is an untyped pointer. There is no such thing in C++, but you can see it as  boost::any  . It is used to type-erase  PyObject  .

You can look at the interfaces of  PointerTypeInterface  and  DynamicTypeInterface  in typeinterface.hpp .  PointerTypeInterface  returns a static type that does not depends on the value (the  pointedType  function does not take the  void*  as argument), but the  DynamicTypeInterface  can return types different for each value through the  get  method.

A value of type Tuple , as the name suggests, is a list of values of different, fixed, types. Same as  std::tuple  in C++. C++ structures are represented as annotated tuples (the annotations correspond to the name of the structure and the name of its fields).

A value of type Raw is a raw buffer, conceptually like a  std::vector<char>  or a  std::pair<char*, size_t>  .  qi::Buffer  is implemented as Raw . This type is different from  std::vector<char>  which is a List of Int8 . A List is something that gets introspected and that can be heterogeneous, a Raw is just a buffer of untyped data.

A value of type VarArgs is a variadic argument. Such object has single type, and any number of objects of that type. You can see it as Java’s variadic function which look like  void f(String... strArray)  . If you need different types, you can use a VarArgs of Dynamic .

## Relation between types and signatures ¶

A type has a signature which is based on its kind and on some static information (functions you can call on the  TypeInterface  without a  void*  pointer). For example, a value that contains a string would have a kind String and a signature  "s"  .

This is not based uniquely on the kind. For example, the signatures  "i"  and  "I"  both represent a kind Integer but one is signed and the other is unsigned. There exist also different signatures for different integer sizes.

Tuples are represented by parenthesis, for example  "(sis)"  is a tuple of String , Integer and String .

Lists are represented by square brackets, for example  "[f]"  is a list of Float . It is invalid to put more than one type inside the brackets.

## Value’s states ¶

A value can be invalid (for example when it is not initialized). An invalid value must never be used. Even though it looks like a Void value, it is not, and lot of functions will throw (or crash?) with an invalid value.

A value can be of Void kind. This corresponds to “no-value”. It has no equivalent in C++ but it works like Python’s None. You can instantiate such a value in C++ though:



qi::AnyValue value(qi::typeOf<void>());



## Implementing type interfaces ¶

Type interfaces must implement functions that receive a  void*  and must do the action on it. For example  IntTypeInterface::get  has the following interface and possible implementation:



int64_t Int64TypeInterface::get(void* storage)
{
return *(int64_t*)ptrFromStorage(&storage);
}



These basic types for c++ are registered in the file registration.cpp . Note that the function may not be that trivial, for example for Python:



int64_t PyLongTypeInterface::get(void* storage)
{
return PyLong_GetLong((PyObject*)ptrFromStorage(&storage));
}



This last function is implemented in pyobjectconvert.cpp .

Consider that  ptrFromStorage(&storage)  returns  storage  . For more information about this function, read the Pointer as pointer and pointer as value section below.

## What functions a type interface must implement ¶

A basic type interface must implement some basic functions. You can see the pure virtual functions in  TypeInterface  in detail/typeinterface.hpp .

These functions include default construction, copying, destruction and a less-than comparison operator. If default construction or copy is not possible, these functions are allowed to throw. In general, any function is allowed to throw if the operation is impossible. For example, lots of  set  throw in our Python type interfaces because Python values are usually immutable.

The  less  function is used to compare objects when they are stored in a container that need such a function to sort the items.

These functions are usually implemented through default implementations. Most (all?) type interfaces declare an alias to a class with default implementation and use a macro to bounce functions to it. For example:



using Methods = qi::DefaultTypeImplMethods<YourType>;

_QI_BOUNCE_TYPE_METHODS(Methods);



There are macros that bounce only some methods so that you can implement the others yourself.

The  DefaultTypeImplMethods  class lies in typeimpl.hxx .

## Pointer as pointer and pointer as value ¶

It is possible to apply an optimization so that when the type is small enough, instead of storing a pointer to it in the  void*  storage, you store the value itself. This is commonly called small buffer optimization . For example, you would have a value with an  IntTypeInterface  and a  void*  that would not be a  int*  pointing to a value of 42, but a  void*  that holds the value 42 itself (and must not be dereferenced of course).

This optimization is actually not enabled for integers and is usually not a good idea. This means that the following code would not have the expected behavior:



int i = 42;
auto ref = qi::AnyReference::from(i);
ref.setInt(10);
assert(i == 10);



You can see the code used to implement this optimization commented out in type.hxx , in the class  TypeImplMethodsBySize  . This class is actually only used for the C++  IntTypeInterface  in inttypeinterface.hxx .

It is enabled though on all pointer types. It seems safe enough and this optimization relied upon in function type erasure, implemented in anyfunctionfactory.hxx .

Anyway, the behavior of your type interface depends on the behavior of the  ptrFromStorage  function. This function must return a  void*  . The only two possible implementation (that make sense) are the following:



void* ptrFromStorage(void** storage)
{
return *storage; // use value as value
// OR
return storage; // use pointer as value
}



As said before, any function accessing the  void* storage  or  void** storage  must call this function. This is also why setter functions receive a  void**  instead of a  void*  , it allows the setter to change the pointer itself.

You can have a default implementation for this method too through the  DefaultTypeImplMethods  class. You configure its behavior through the second template argument you give it, which defaults to  TypeByPointer<T>  and is the expected behavior. If you want to use the other behavior, use  TypeByValue<T>  .

## Construction, destruction, cloning ¶

It is possible to change the behavior of the  ptrFromStorage  method, with the default construction method, cloning method, etc, by changing the second argument you give to  TypeByPointer  , for types that are non-copyable or non-default constructible. You can specify that for  TypeByValue  , the type will have default construction and default copy.

The second argument to  TypeByPointer  is a  TypeManager  which has an implementation for these functions. This  TypeManager  can be specialized and has a default implementation which allows the user to specify what is possible to do with their types thanks to some macros.

By default, the  TypeManager  makes default-constructible and copyable types for PODs, and non-default-constructible and non-copyable types for the rest, as can be seen in typeimpl.hxx .

It is possible to use the macros declared in typeinterface.hpp to specialize  TypeManager  to declare that a type (that may or may not be known to the type system, see Unknown kinds above) is or is not default-constructible or copyable.

If an attempt is made to default-construct a type that is non-default-constructible or to copy a type that is non-copyable, the corresponding function will just throw.

Finally, there is the  TypeByPointerPOD  access which forces default-constructibility and copyability.

## How types are retrieved ¶

You will reach a moment when the type system will receive a C++ object and will need to know its  TypeInterface  . This happens for example:

• when a struct is registered, the type system needs to know the type interfaces of the underlying fields,
• when an object’s method is registered, libqi needs the type interface of the parameters and return value
• when you use qi::typeOf<T>()
• when you use  qi::AnyValue::from  , which uses qi::TypeOf<T>

It will first search through a static map to find if the type has been registered at runtime. This part is done in typeinterface.cpp in the function  getType  .

If the type is unknown to the type system yet, it will instantiate  TypeImpl<T>  and use that. This part happens in type.hxx , in the function  typeOfBackend  .

## How types are registered ¶

This section is tightly tied to the previous one, be sure to read it first.

To register something in the global map, you usually use the function  qi::registerType  in typeinterface.cpp .

The other way to “register” a type is to specialize the  qi::TypeImpl  class. This may be useful to register full classes of types with C++ partial specialization. For example:



template <typename T>
class StdVectorTypeInterface : public ListTypeInterface
{
// ...
};

template <typename T>
class TypeImpl<std::vector<T>> : public StdVectorTypeInterface
{};



The type interface of std::vector is implemented in listtypeinterface.hxx .

If you need to register a template class, this is the only way to make it work for any type T.

The default implementation of  TypeImpl<T>  is a type interface of Unknown kind which uses  DefaultTypeImplMethods  with default values to implement its methods. This means that it is configurable through the macros described in Construction, destruction, cloning .

## About type_info comparison ¶

To know if two types are equal, the type system uses simple  type_info  comparison. This feature is very weakly defined by C++, especially when shared libraries are used.

It is very important that the symbols of the type interfaces are exported in shared libraries so that the  type_info  themselves are exported and the runtime library can identify that two same types from different binaries are in fact the same.

Mac’s compiler has a very strict policy about that, so there is a hack where  type_info  are compared by mangled string representation instead of by value under Apple systems. This hack is found in typeinterface.cpp .

If someday, libqi stops using plain  std::type_info  and uses Boost.TypeIndex, this by-string-comparison behavior will be enforced on all platforms.

## AnyValue and AnyReference ¶

 AnyValue  has value semantic and  AnyReference  has pointer semantic.  AnyReference  never frees its value (unless  destroy()  is explicitly called).  AnyValue  usually owns its value and frees it automatically at destruction. It doesn’t own it sometimes, depending on the arguments given at its construction.

Users should always use  AnyValue  ,  AnyReference  is usually for internal use.

Be careful when using  AnyReference  , do not create reference to temporaries. Your compiler won’t be able to detect it, you will not get a warning.



int i;
auto ref = qi::AnyReference::from(i); // good

ref = qi::AnyReference::from(boost::python::object(i)); // BAD
auto value = ref.toInt(); // undefined behavior