Before retrieving data from RTTR you have register your custom Type
and the following tutorial will show how to do this.
The register process has to be done manually and can be separated in two steps:
RTTR uses an own, in ISO C++ implemented, alternative to the build in RTTI mechanism of C++. The reason for that are problems when using typeid across shared boundaries and the execution speed. In order to use this mechanism you have to register them with a macro named RTTR_DECLARE_TYPE.
Suppose we have a struct named MyStruct
Place the macro inside the header of the struct or class, just below the declaration. The macro will create a register function and is also responsible for returning the corresponding type object for this type.
The registration process itself is now done at runtime.
When MyStruct
is in a namespace, make sure you put the macro outside the namespace, otherwise the type class cannot access the Type
.
Also note, that when your are working with pointers of your custom type, then these needs to be registered too.
Instead of writing tree times RTTR_DECLARE_TYPE you can use the shortcut macro RTTR_DECLARE_STANDARD_TYPE_VARIANTS
This will declare for a given type Type
, the following types: Type
, Type*
, const Type*
When you use class hierarchies, you have to put a certain macro inside every class. Otherwise you are not able to retrieve the information about the most derived type of your current instance. The macro you have to insert is called RTTR_ENABLE
Suppose we have a base struct called Base
.
Place the macro RTTR_ENABLE() somewhere in the class, it doesn't matter if its under the public, protected or private class accessor section.
Into the derived class you put the same macro, but now as argument the name of the parent class. Which is in this case Base
.
When you use multiple inheritance you simply separate every class with a comma.
Remark that the order in which you declare here the multiple inheritance, has an impact later when retrieving properties of a class. So it is best practice to use the same order like in your class. RTTR supports to register even virtual base classes.
When you have successfully registered your custom type, you can use type to check whether the current instance is a certain type or not.
There are three static template member functions for retrieving the type:
This function just expects the name of the type. This is useful when you know only the name of the type and cannot include the type itself into the source code. The name of the type is the same like you have registered with RTTR_DECLARE_TYPE but as string literal. When you have used a typedef then you need to provide this typedef also as string literal.
This function just expects one template argument. Use it to check against a known type.
This function is a universal reference and returns from every given object the corresponding type object.
When this function is called for a glvalue expression whose type is a polymorphic class type, then the result refers to a type object representing the type of the most derived object.
Any top level
cv-qualifier of the given type T
will be removed.
Providing an own function for dynamic_cast
completes the package as an RTTI
alternative.
The function rttr_cast<T>(Arg) allows the client to cast between class hierarchies up and down, cross casts between unrelated classes and even class hierarchies with virtual inheritance. The target type T
can be also in the middle of the hierarchy.
An rttr::type object knows from which parent class it is derived. Assumed this information is given via RTTR_ENABLE. From the functionality it is similar to dynamic_cast
.
T
can only be a pointer type.Let's start with the traditional hello world example.
When you need to reflect a property, or like in this case a free function, you need to include first #include <rttr/reflect>
. This will include everything necessary for creating the reflection information. The macro RTTR_REGISTER must be placed outside of any function or class, just place directly into in your cpp file. This macro executes the register process before main is called, that has the advantage that this reflection information is directly available when main is called. Remark that this macro can only be placed one-time in a source file, otherwise you will get an compile error.
The shortest way to invoke the function f()
is to call type::invoke(). It requires the exact name of the free function and a vector of arguments. You can check whether the call was successful with checking the return value. When the returned variant is valid the call was successful, otherwise not.
In RTTR is the most important class the type class. With that class you get access to everything else.
As already mentioned to bind global methods to RTTR use the function rttr::method_(). It has following synopsis:
For example when you want to register the byte string to integer conversion function: int atoi (const char * str);
Do it in the following way:
When you want to register a function which is overloaded (same name, different signature), you have to explicitly provide the signature. Otherwise C++ can not deduce which function you refer to. For example when you have two function float sin (float x);
and double sin (double x);
:
That is the general syntax for function pointers:
Invoking a method with RTTR can be done in two ways.
The first option needs less typing, but it is slower when you need to invoke the function several times. For the second option you need more code to write, but it invokes the method faster.
The following example demonstrates the possibilities to invoke a method:
The type::invoke() function will internally try to find a function based on the given name and the given types of the arguments. So finding the correct function when overloaded function are registered is automatically done. Notice that you have to provide the arguments as a vector pack. Therefore an initializer list is quite handy to reduce typing. The argument must match 100%, there is at the moment no conversion done. That means, when an integer argument is needed and you forward a double value the function will not be called. The arguments will not be copied, instead they will be wrapped in an internal class and forwarded to the underlying function pointer. This class is called detail::argument
do not create this class on your own. The class will implicit wrap your argument value.
The return value of type::invoke() is a variant object. It indicates whether the function was called or not. RTTR does not use the exception mechanism of C++, therefore you have to check the return values when you are interested in a successfull call. The variant object is valid when it was successfully called. When the function has a return value, then this value is also contained in the variant object.
When you prefer to hold a handle to the function use the getter type::get_global_method() to retrieve a method object. This has the advantage that you do not need to search for the function every time you want to invoke it. Additionally this class provides functions to invoke a function without the need to create a vector of arguments. The method object is very lightweight and can be simply copied around different locations. The object stays valid till end of the main()
function.
For binding a property to RTTR you can use following functions: rttr::property_() and rttr::property_readonly_().
They have following synopsis:
It is also possible to use function pointers for the property as getter and setter functions. Therefore the rttr::property_() function is overloaded.
It has following synposis:
The following example shows how to use these register functions:
There can be not two global properties with the same name. The later registered property with an already existing name will be discarded.
For setting and getting a property with RTTR you have two options like with methods:
The static type::set_property_value() function calls directly a global property with the given name. This function has a bool value as return value, indicating whether the property was invoked or not. For retrieving a property value use the static function type::get_property_value(). The returned variant object contains the property value and also indicates whether the call to retrieve the property was successful or not. When the variant is not valid then the call could not be done.
Another option is to retrieve a handle to the property via type::get_global_property(). This is the preferred option, because then you directly set/get the value without searching every time for the property. The property object is very lightweight and can be simply copied around different locations. The object stays valid till end of the main()
function.
RTTR allows also to bind enumerated constants (enums). Therefore use the function enumeration_().
It has following synposis:
You don't need to provide a name for the enum during the enumeration binding, because this is already done with the registration process for types. The key is a std::string and the value is the enum value. The class enumeration contains several meta information about an enum with conversion functions between the value representation and its literal representation.
How to use the enumeration class shows following example:
The variant class acts as return value container for properties and methods. This class allows to store data of any type and convert between these types transparently. It can hold one value at a time (using containers you can hold multiple types e.g. std::vector<int>
). Remark that the content is copied into the variant class. Even raw arrays (e.g. int[10]
) are copied. When you would like to avoid copies, use pointer types or wrap your type in a std::reference_wrapper<T>
A typical usage is the following example:
For registering classes in RTTR you use a class called class_. Its name is supposed to resemble the C++ keyword, to make it look more intuitive. It has member functions for register constructors, properties, methods and enums. These functions have the same interface and work in the same way like register the global symbols. Every call to these member functions, will return a reference to this, in order to chain more registration calls. The name of the class does not have to be provided, because this is already done with the registration process for types.
Let's start with a simple example. Consider the following C++ class:
The registration process is now done at global scope in the cpp file.
This will register the class test_class
with a constructor that takes an integer as argument, a member function with the name print_value
and a property called value
.
The property name has be unique for this class type but derived classes can register another property with the same name again. Member functions can be overloaded, so a method can be registered with an already existing name multiple times. However when there exist a method with the same name and signature, then this function will not be registered and discarded.
When binding a overloaded member function, you have to disambiguate the member function pointer you pass to method. To do this, you can use static_cast
or an ordinary C-style cast, to cast it to the right overload.
The syntax for member function pointers if following:
Here's an example illlustrating this:
This first overload of the function f is binded with a C++ cast. The second overload is binded with a C style cast.
For registering these constructors you now have to specify every parameter as template parameter in the member function :class_::constructor().
When a constructor is registered a destructor is registered automatically.
Register a public property can be easily done, consider following class:
This class is registered like this:
With the property() member function you will bind the member variable Foo::value
with read and write access. When you want a bind a property with read-only
access, then this is also possible with property_readonly() member function.
When you have a class and the property is declared in private scope, then you can still register this property when you insert the macro: RTTR_REGISTER_FRIEND inside the class.
This will make this class a friend to the registration system.
You can also register getter and setter functions and make them look as if they were a public data member. Consider the following class:
This is the registration code:
This way, accessing the property will now call these functions, instead the property directly. Remark that the getter function needs the be const
.
The following sub sections will now show how to retrieve these informations for creating, invoking and setting properties of an instance of this class.
There are two options for creating/destroying a class. Option 1, use just the type interface or option 2 retrieve a constructor and destructor object from the type class.
The objects which are constructed are created on the heap and stored as pointer in the variant object.
Invoking a member function works in the same way like invoking global function. The only difference is, that you have to provide the instance of the class.
The invoke function also except to use variants. So when you create the object via the type constructor you can use the returned variant to invoke to method:
Properties can be also set an get in two ways.
In difference to the global properties, a valid type object and an instance (object) of the class is now needed to set and get the value. It doesn't matter in what hierarchy level the object is. Or if its a pointer, an object on the stack or wrapped inside a variant. RTTR will try to cast the given object to the class type where the property was registered to.
Adding additional meta information to properties or methods can be very useful. So for instance, it allows to add ToolTips or the information what kind of editor should be created in the GUI. You can also tag certain properties to make only those available in a scripting engine which has a certain key set.
The metadata consists of a key, which can be a std::string or an integer and a value which is a variant.
Please take a look at following example:
This will register a global property named "value"
with two metadata informations. Both keys use the integer as data role, because the enum value will be implicit converted to an integer.
And the following snippet shows, how to retrieve this information:
Every property, method, enumeration and constructor can have metadata.
Sometimes it is necessary to adjust the default binding behaviour RTTR. Therefore policies were introduced. At the moment only return value policies are implemented. The default binding behaviour of RTTR is to return all values by copying the content in a variant.
Currently implemented policies:
The motivation for this policy is to avoid expensive copies when returning a property. When you have primitive data types like integer or doubles you are good to go with the standard binding behaviour. But when you have big arrays, it would be a waste to always copy the content when retrieving or setting the value, therefore this policy was introduced.
Example:
The motivation for this policy is the same like the bind_property_as_ptr policy. When you really need to get a reference to the return value of a method call you have to use this policy, otherwise the returned reference will be copied into the variant.
Example:
Sometimes it is necessary that the client caller should ignore the return value of a method call. Therefore this policies was introduced.
Example:
A closing hint: you can of course build your own policies in that way, that you build wrappers around your properties or methods and then bind the wrapper instead of the original accessor.