How to Create Custom Variant Types

Apart from the built-in types, Variants are capable of handling instances of arbitrary types as long as they inherit from VariantDataClass. To create a custom Variant type, four steps need to be taken:

  • Write an Ice interface with the basic type description
  • Add a Variant type id for the new type
  • Write a C++ implementation of the type
  • Add the created type to the object factory registry

Ice Interface

The first part of a custom Variant type is an Ice interface that allows using the new type in Ice communication. The interface should inherit from VariantDataClass and define the intended data structure.

In this example, we implement a custom Variant type for complex numbers, defined by a real and an imaginary part, both stored as floats.

module armarx
class ComplexFloatBase extends VariantDataClass
float real;
float imag;

Type Id

Add a VariantTypeId declaration somewhere in the variant's header.

const VariantTypeId TestComplexFloat = Variant::addTypeName("::armarx::ComplexFloatBase");

Note: armarx::ComplexFloatBase must be unique in all ArmarX projects.

C++ Implementation

The second part of a custom Variant data type is the C++ implementation of the defined Ice interface ComplexFloatBase. There are three types of methods that need to be present in the implementation:

  • Custom data type interface
    • Parameterized constructors
    • getReal, getImag for accessing the data value
  • Standard data type interface
    • ice_clone
    • clone
    • output for printing the data value
    • getType for retrieving the Id of the new type
    • validate for checking the data's sanity
  • Serialization -
    • serialize, deserialize for serializing to JSON for MemoryX
class TestComplexFloat : virtual public ComplexFloatBase
template <class BaseClass, class VariantClass>
friend class GenericFactory;
real = 0;
imag = 0;
TestComplexFloat(float real, float imag)
this->real = real;
this->imag = imag;
float getReal()
return real;
float getImag()
return imag;
Ice::ObjectPtr ice_clone() const override
return this->clone();
VariantDataClassPtr clone(const Ice::Current& c = Ice::emptyCurrent) const override
return new TestComplexFloat(*this);
std::string output(const Ice::Current& c = Ice::emptyCurrent) const override
std::stringstream s;
s << "(" << real << " + " << imag << "i)";
return s.str();
VariantTypeId getType(const Ice::Current& c = Ice::emptyCurrent) const override
return armarx::VariantType::TestComplexFloat;
bool validate(const Ice::Current& c = Ice::emptyCurrent) override
return true;
void serialize(const armarx::ObjectSerializerBasePtr& serializer, const ::Ice::Current& = Ice::emptyCurrent) const override
AbstractObjectSerializerPtr obj = AbstractObjectSerializerPtr::dynamicCast(serializer);
obj->setFloat("real", real);
obj->setFloat("imag", imag);
void deserialize(const armarx::ObjectSerializerBasePtr& serializer, const ::Ice::Current& = Ice::emptyCurrent) override
AbstractObjectSerializerPtr obj = AbstractObjectSerializerPtr::dynamicCast(serializer);
real = obj->getFloat("real");
imag = obj->getFloat("imag");

The next step is to define a pointer type for the new data type:

using TestComplexFloatPtr = IceInternal::Handle<TestComplexFloat>;

Object Factory

Finally, the new type needs to be registered to allow it to be used in Ice communication. The factory tells ArmarX, which exact type to instantiate when the respective interface is used in communication. In this case, whenever a ComplexFloatBase is transferred via Ice, it is translated into a TestComplexFloat on reception.

class TestFactories : public FactoryCollectionBase
ObjectFactoryMap getFactories() override
add<ComplexFloatBase, TestComplexFloat>(map);
return map;
static const FactoryCollectionBaseCleanUp TestFactoriesVar;

The variable needs to be initalized in the the cpp-file of the Object Factory like this:

const FactoryCollectionBaseCleanUp TestFactories::TestFactoriesVar = FactoryCollectionBase::addToPreregistration(new armarx::ObjectFactories::TestFactories());


The new data type can be used as if it was one of the types already included in ArmarX:

Variant varComplex(new TestComplexFloat(1, 2));

If you want to receive Variants via Ice (e.g. implement an interface that uses variants), the ObjectFactory-header of the library, where the variant is implemented, needs to be included and the library needs to be linked.

@note If you want to use the variant in the statechart editor, see How to add your own data type to a statechart

constexpr T c
Definition: UnscentedKalmanFilterTest.cpp:43
std::map< std::string, Ice::ValueFactoryPtr > ObjectFactoryMap
Definition: FactoryCollectionBase.h:61
Definition: forward_declarations.h:8
static FactoryCollectionBaseCleanUp addToPreregistration(FactoryCollectionBasePtr factoryCollection)
Definition: FactoryCollectionBase.cpp:41
Definition: ChannelRef.h:160
Ice::Int VariantTypeId
Definition: Variant.h:44
void validate(const std::vector< WaypointTarget > &path)
Definition: ice_conversions.h:70
IceInternal::Handle< AbstractObjectSerializer > AbstractObjectSerializerPtr
Definition: AbstractObjectSerializer.h:45
Definition: CoreObjectFactories.h:54
std::shared_ptr< Object > ObjectPtr
Definition: Object.h:36
double s(double t, double s0, double v0, double a0, double j)
Definition: CtrlUtil.h:33
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28
static VariantTypeId addTypeName(const std::string &typeName)
Register a new type for the use in a Variant.
Definition: Variant.cpp:751