XParam - General-Purpose Object Serialization Framework for C++

The XParam Library User's Guide

The Registration Interface

Next: Installing XParam
Previous: The Programmer Interface
Up: Table of Contents

Contents:

  1. General
  2. The Basic Structure
  3. Class Declarations
  4. Inheritance
  5. Constants
  6. Enumerators
  7. Argument Passers
  8. Constructors
  9. Creators
  10. Conversions
  11. Vectors
  12. Output
  13. Weights
  14. Dynamic Loading

General

The most involved and least frequently required interface to the XParam library is the registration interface. In contrast to the user's interface, that is required whenever a program using XParam is invoked, and the programmer's interface, that should be used whenever you write a new program with XParam parameter handling, registration is needed only when you want XParam to learn how to use new classes, or when the interface to an already registered class has changed.

In principle, to handle user inputs such as a=7, where a is, for example, an instance of class Duck, XParam should find the best path from 7 to a Duck variable. If Duck has a non-explicit constructor from int, that can, of course, be used. If, instead, it only has a non-explicit constructor from double, then the 7 integer should be converted to a double, and from there to Duck. If, on the other hand, the only non-explicit constructor Duck has is from char, then the 7 should not be taken as an integer value at all, but as an abbreviation of '7', using XParam's relaxed type matching. This was only a very simple example, but arbitrarily complex implicit conversion paths can also be generated by the user. Consider, for example, the user's command mentioned in the introduction:

~/bin>a.out 'my_shape=[ Circle(Point(50,50),50), Circle(Point(25,75),10), Circle(Point(75,75),10), Arc(Point(25,50), Point(50,25), Point(75,50)) ]'

To parse this line correctly, XParam needs considerable knowledge of the existing classes and the inter-connections between them.

All the information required by XParam exists as part of the classes' interface. Unfortunately, because C++ has no introspection capability, in the registration process the registrator has to make this information explicitly available for XParam's usage. As will shortly be demonstrated, the registration process itself is entirely non-intrusive, enabling third-party registration. The only restriction XParam-registered classes have is that if they are concrete, they should support a public copy constructor and be assignable. Because of this, classes can be developed by one person, entirely unaware of XParam, then registered by a second person and finally used by a third person. For this to be possible, the class interface, developed by the first person, should be available for the registering person, and code developed by the registrator and the programmer should be linked together (though it can be compiled separately) into the running program.

To emphasize this point, consider that in order to run the example quoted above, classes Circle, Arc and Point weren't necessarily even programmed at the time the main program was compiled and even executed. Trying to parse the command-line, XParam can dynamically load both the registration code and the class implementations at run-time.

The Basic Structure

In the next sections, the exact contents of registration files will be elaborated, but the basic structure of all registration files is exactly the same. First, note that XParam's registration files, as was hinted at in the first section, are C++ files. They should be compiled as any C++ file and linked with the rest of your program. This can be confusing, because registration files don't look like they are C++-compilable files. What they do look like, is this:

#include <xparam_extend.h>
#include "point.h"
#include "point_output.h"
using namespace xParam;

PARAM_BEGIN_REG

  PARAM_CLASS(Point);
    param_ctor<Point>(ByVal<int>("x"),ByVal<int>("y"));
    param_output<Point,Point_output_functor>();
    param_vector<Point>();

PARAM_END_REG

To understand this format, let's go over it line by line:

#include <xparam_extend.h>

This file contains the declarations needed for XParam registration.

#include "point.h"

The interface of the registered class should be visible to the registration code.

#include "point_output.h"

Consult the section about Creators to learn about other header files you may want to #include to your registration code.

using namespace xParam;

You don't need this line, of course, but if you don't use it, you will have to add the xParam:: prefix to virtually everything in the registration code, so we highly recommend it. All our examples of registration code will assume the inclusion of this line.

Finally, we reach the registration code itself. It is denoted by a PARAM_BEGIN_REG/PARAM_END_REG block. The purpose of this block is to create an environment in which commands are executed before main() is entered. The two macros set up an anonymous namespace and define a class within that namespace, and a static instance of the class. The commands placed inside a PARAM_BEGIN_REG/PARAM_END_REG block compose the class's constructor, and are therefore executed. All XParam's registration commands are simple function calls and constructors for temporary objects. The PARAM_BEGIN_REG/PARAM_END_REG block allows these function calls to be executed and the registration to take effect before main() is reached.

You can, of course, run the registration commands by other means, but it is our opinion that this is the simplest and most straightforward way.

One PARAM_BEGIN_REG/PARAM_END_REG pair is good to put all your registration commands in, but if you want you can separate your registration commands into as many blocks as you want. This can be effective if you want to put ordinary C++ code in your registration files. The Point_output_functor, for example, could have been written directly in the Point registration file, and splitting the file into several registration blocks can help you place the output functor near the registration command relevant to it.

An XParam program can link in as many registration files as it wishes, and as many classes as you wish can be registered in the same registration block.

Ordinary C++ code should not be placed inside a registration block, unless you want it to run prior to your main(). This is rare, but not unheard of. Here's a challenge for an advanced XParam user: Program "param_map()", a function that will allow you, in a single function call, to register any "std::map" class, between any two classes T and S that have already been registered in XParam. Hint: you may want to take a look at how "param_vector" is implemented before going about trying it.

In the next section we will go over all the registration commands supplied by XParam for use in registration blocks.

Class Declarations

The usual way to declare a class for XParam's use is

PARAM_CLASS(my_class);

PARAM_CLASS is a macro. If you rather not use this macro, you can always opt for the longer form:

param_class<my_class>("my_class");

which is what the previous line expands to. This format is usually not used, unless you want your class to be given a different name in its XParam user interface than it does in C++. Though this may seem like a strange idea at first, take into account that fully qualified class names are used much more frequently in XParam than they are in C++ code, so it is convenient to use a shorter form here. Here is one example of such a usage, in XParam's own cpp files:

param_class<std::string>("string");

The STL string, known inside C++ programs as std::string, and sometimes even as std::basic_string<char, std::char_traits<char>, std::allocator<char> > would have been cumbersome to use under that name. Therefore, the XParam name it has been given is simply string. XParam would have been able to handle this fully qualified name. However, no XParam class name can include a modifier, such as "unsigned", "long", "const", "static" or "volatile".

One other reason not to use the macro is when your class-name has a comma in it, such as "pair". If you try to use the PARAM_CLASS macro with this name, it will complain that the macro was invoked with too many parameters, because the macro can not correctly parse this expression. In such a case, use non-macro registration.

Because XParam tries to instantiate its classes, abstract classes can not be registered in this way. They use

PARAM_ABSTRACT_CLASS(my_abstract_class);

which is a macro that expands to

param_abstract_class<my_abstract_class>("my_abstract_class");

Registering abstract classes is useful for using polymorphism, where inheritance relationships are necessary.

The following types have been pre-registered by XParam. The name in parentheses indicates the XParam-name of the class, if it differs from the C++ name.

All the registration commands needed to register these classes are part of the registration interface, and you are welcome to register other types, such as short or std::map<std::string,int>.

Note that it is impossible, in XParam, to register a template, such as std::vector directly. However, it is possible to register template specializations, such as std::vector<int>. For vectors, specifically, XParam even supports special registration commands, to make your life easier. They are elaborated in the Vectors section. Though you can register any template specialization you want, not every template specialization is a legal XParam name, meaning you may not be able to use the class registration macros. Currently, XParam can only handle template specializations which expect class names as their specialization parameters. That is: Matrix<int> is allowed, but Matrix<3,4> isn't.

There are two exceptions to the "no modifiers are allowed in class names" rule: when registering a template specialization, classes that are part of the specialization description can include the modifier "const" and a "*" if they are pointer types, but no other modifier. For example: vector<const string*> is a legal XParam class name, and one can therefore register it directly using the registration macro:

PARAM_CLASS(vector<const string*>);

Technical Note: Class declarations are the only registration commands in XParam that get registered immediately. For all other commands we use a mechanism called "delayed registration". If, for example, you want to register that B is a derived class from A, XParam will wait with this registration command, and only enter it after both class A and class B have been registered.

For this reason, it is important to check that all your classes are registered properly, or else all your other registration commands may simply be ignored.

Inheritance

Since we already mentioned inheritance, here's the syntax to register it:

param_inheritance(DerivedTag<my_derived_class>(), BaseTag<my_base_class>());

The syntax includes DerivedTag and BaseTag so as to minimize the possibility of confusion in the registration order.

Just as in C++, if A derives from B and B derives from C, then A derives from C. You do not need to register the A-from-C relationship explicitly.

Note: The entire registration process is meant to register class interfaces. If your class has private or protected inheritance from a base class, this is naturally not a part of the class interface, and you should therefore not register it. Another very important detail to note about registering inheritances is that all classes involved in an XParam inheritance relationship must have at least one virtual method (even if it is the destructor). This makes C++ create a virtual method pointer table for the class, enables real-time type information for the class, allows use of dynamic_casts, and, in general, allows XParam to make proper use of the inheritance information.

Constants

Defining a constant, so that it will be recognized in XParam initializations, is very simple. The registration command is:

param_const(name,value);

where name is a string, and value can be a C++ variable of any type which has been registered into XParam. XParam does not insist that your variable will be defined as 'const'. However, the value that this variable had at registration time is the value that XParam will use.

The constant will be of the same type as that of the C++ variable. In XParam initializations, the constant will be recognized by the name given in name.

Usually, the name you will want to give your constant is the same as the name you reference it in your C++ programs. To accomplish this, XParam defines an easy interface using the PARAM_CONST macro.

PARAM_CONST(variable);

expands to

param_const("variable",variable);

Note: It is not recommended to use dynamically loaded constants. Though XParam does support loading constants dynamically, using a constant in a parameter initialization will not trigger dynamic loading. XParam will only recognize the constant if it had already been loaded due to a missing class that had to be dynamically loaded. For this reason, it is safest to have all your constants statically linked to your program.

Enumerators

Enumerators are handled in a very similar way to constants. First, you need to register your enum type. This is done by the macro

PARAM_ENUM(enum_type);

or, alternatively, by an explicit call to the registration function it expands to:

param_enum<enum_type>("enum_type_name_in_XParam");

or, for an ISO-challanged compiler:

param_enum(TypeTag<enum_type>(), "enum_type_name_in_XParam");

The macro naturally assumes that you want to set the name of the enum_type in XParam to be exactly what it is in C++.

To define a certain instance of your enum, all you have to do is use the macro

PARAM_ENUM_VAL(value);

or the function it expands to:

param_enum_val("value_name_in_XParam", value); where the value's name in C++ and the value's name in XParam are assumed to be the same.

If this sounds complex, here is an example that will make it clearer:

enum DOW { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };

PARAM_BEGIN_REG
  PARAM_ENUM(DOW);
  PARAM_ENUM_VAL(Monday);
  PARAM_ENUM_VAL(Tuesday);
  PARAM_ENUM_VAL(Wednesday);
  PARAM_ENUM_VAL(Thursday);
  PARAM_ENUM_VAL(Friday);
  PARAM_ENUM_VAL(Saturday);
  PARAM_ENUM_VAL(Sunday);
PARAM_END_REG

That's all there is to it! The enumerator is now ready to be read from the input as if it were a regular constant. Note, however, that there is a difference between constants and enums in the way they are output: constants are output by their values, so if the programmer was to write "cout << Val(M_PI)", she can expect to get "3.1415" on the output line. Enums, on the other hand, are output by name. Writing "cout << Wednesday", will result in "Wednesday" being output, so that re-reading the enum elsewhere will still give the value of Wednesday, even if the enum is defined by a different integer there.

An exception to this rule are enums that were not declared by the registrator. If, in the registration process, you registered "PARAM_ENUM(DOW);", but did not continue to register its instances -- something we discourage you from wholeheartedly -- then anyone who tries writing "cout << Wednesday" will get "DOW(2)" in the output. Though this still works, and can even be read back using XParam (assuming Wednesday is still represented by the number 2 in the DOW list of the reading program) it is much less clear to a human reader, and may be less portable. For this reason, it is always advisable to register all your enum values.

Argument Passers

Most of the registration code is composed of registration of constructors. This, in general, looks something like this:

param_ctor<Point>(ByVal<int>("x"),ByVal<int>("y"));

This particular line means: class Point has a constructor from two integers, x and y, both passed by value. As you can see, a major part of the description is composed of argument passers: ByVal<int>("x") is the XParam way of saying "an argument called x, of type int, which is passed by value". In this section we will go over the various argument passers supplied by XParam and how to use them.

In XParam, arguments can be passed by value, by constant reference or by pointer. If it is passed by pointer, an argument can be constant or non-constant, and the responsibility to delete it after the end of the construction call can lie either with the caller, or with the called constructor. This gives four different types of pointers. In addition to this, there are two more argument passers: AsConvertedVal and AsCString. These will be explained later on.

An XParam argument, with the exception of AsConvertedVal and AsCString, takes the following form:

passmode<type>("argname")

for example:

ByVal<int>("mode")

This means: the argument's name is mode, its type is int and it should be passed by value. The argument's name is only used for error reporting and getting help.

Instead of ByVal, you can put ConstRef to indicate an argument passed by constant reference, ClassPtr and CallerPtr to indicate a pointer that is owned by the method it was passed to or one that is owned by the caller and should be deleted by XParam, respectively, and ClassConstPtr and CallerConstPtr, which are the constant pointer equivalents of ClassPtr and CallerPtr.

XParam pointers are all allocated by new, so ClassPtr and ClassConstPtr should be deallocated by using delete in the called method.

One special case is a constructor expecting a C-string (i.e. a null-terminated array of chars). XParam does not, natively, work with C-strings. It uses std::string variables whether you asked for them explicitly or whether you used constant string literals. If you have a constructor that requires a C-string, you will want XParam to convert its relevant std::string to a C-string for the constructor. This is done by AsCString, and is denoted by

AsCString("argname")

Note that even though XParam does not distinguish between native strings and STL strings, it does distinguish between strings and pointers to characters, so if your constructor expects a pointer to a character, and not a C-string, one of the pointer argument passers is the one you want.

Finally, if you need, for some reason, to allow an implicit conversion at pass-time, and your compiler warns you about this, you can use

AsConvertedVal<sourcetype,destinationtype>("argname");

This should hardly ever be needed. However, XParam's pre-registered types use this, because implicit conversions between almost all built-in C types are allowed by the language.

Constructors

Now that we've gone over argument passing, here's the syntax to register constructors:

param_ctor<registered_type>( .. list of argument passers ... );

The list of argument passers can contain between zero and thirteen arguments (XParam does not support methods with more than thirteen arguments, by default. This can be changed. See the installation section for details). The arguments should have the format described in the previous section. Arguments in the list should be separated by commas.

For example:

param_ctor<Complex>(); is the default Complex constructor.
param_ctor<Complex>(ByVal<double>("real")); is the double-to-Complex implicit conversion constructor.
param_ctor<Complex>(ByVal<double>("real"), ByVal<double>("imaginary")); is the Complex-from-two-doubles constructor.

Unfortunately, though this is ISO-C++, many compilers will still balk at this syntax. For this reason, XParam is also willing to accept the following format for registering constructors:

param_ctor(TypeTag<registered_type>(), ... arglist ... );

If your compiler is ISO-challenged, it may give you some trouble when you try to compile XParam itself. In such a case, try compiling with "-DNO_EXPLICIT_TEMPLATE_FUNC_ARGS". This definition tells the compiler not to try and compile the more sophisticated registration flavor.

In XParam, as in C++, constructors can also be declared "explicit", to prevent their usage in implicit conversion paths. To register this, use the single-argument constructor, in either flavor, switching param_ctor with param_explicit_ctor. So, if we do not wish the Complex-from-double constructor to be allowed implicitly, all we need is to register it as

param_explicit_ctor<Complex>(ByVal<double>("real"));
or
param_explicit_ctor(TypeTag<Complex>(), ByVal<double>("real"));

Because XParam requires that all concrete classes have copy constructors, this constructor is registered automatically when you use "param_class" to register a concrete class.

Creators

Not always is everything handed to us on a silver platter. One of the many surprises life can have in store for you is that the class you wish to register does not support the interface you need.

How can that be? you ask. Well, many classes don't allow themselves to be set up completely at construction-time. You build them up in a certain way, and then need a little more tweaking to get it exactly right. Consider, for example, the std::vector. If I wanted a vector of integers to be filled with the numbers one through five, in C++ it would have looked like this:

std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);

In XParam, however, you want to do everything on a single construction line. The person who wrote the C++ interface could have omitted the possibility for complete construction on a single line because she didn't think it too important, or because (as in the case of std::vector) the appropriate constructor isn't possible to program in C++. Either way, XParam may have a solution for you.

What you need to do is to program a creator. A creator is a functor class supporting the following interface:

class creator_name {
  public:
    static created_class_name* create( ... arglist ... );
};

where arglist is the list of arguments you want to send to the creator functor. It is composed of normal C++ arguments, not XParam argument passers.

Here's an example: a friend of mine programmed the following struct:

struct Point {
  int x,y;
};

In C++, using this struct is no problem: you take a "Point" variable and assign whatever value you want to it. However, in XParam you want the initialization to be done right on the object definition line. You therefore want to register the following creation functor:

class Point_creator {
  public:
    static Point* create(const int _x, const int _y) {
      Point* rc=new Point;
      rc->x=_x;
      rc->y=_y;
      return rc;
    }
};

Make sure that in your creator the pointer to your created type is allocated using new.

Once the functor is set up, all you need to do is to register it:

param_creator<Point,Point_creator> (ByVal<int>("_x"),ByVal<int>("_y"));

or, for an ISO-challenged compiler:

param_creator(TypeTag<Point>(),TypeTag<Point_creator>(), ByVal<int>("_x"),ByVal<int>("_y"));

This example was perhaps a little contrived, but was given here for simplicity. Here is a real life usage:

We want to register a creator that would allow the creation of an std::map<std::string,int> from an explicit listing of its contents in the form of an std::vector<std::pair<std::string,int> >, what we need to do is to program the following class:

class map_creator {
  public:
    static std::map<std::string,int>*
     create(const std::vector<std::pair<std::string,int> >& v);
};

with the method implementation being, for example, this:

using namespace std;

static map<string,int>*
map_creator::create(const vector<pair<string,int> >& v) {
      typedef map<string,int> maptype;
      typedef vector<pair<string,int> > vectype;

      maptype* rc=new maptype();
      for(vectype::const_iterator i=v.begin();i!=v.end();++i) {
        (*rc)[i->first]=i->second;
      }
      return rc;
}

Armed with this, all you need is the following registration command (assuming the rest of map, vector and pair have already been registered):

using namespace std;
param_creator<map<string,int>, map_creator>(ConstRef<vector<pair<string,int> > >("v"));

Or, for an ISO challenged compiler:

using namespace std;
param_creator(TypeTag<map<string,int> >(), TypeTag<map_creator>(), ConstRef<vector<pair<string,int> > >("v"));

As constructors, XParam creators, too, can be declared to be "explicit". This is done by switching param_creator with param_explicit_creator

In general, the syntax for creator registration is the following:

param_creator<my_class,my_class_creator>(... arglist ...);
or
param_creator(TypeTag<my_class>(), TypeTag<my_class_creator>(),... arglist ...);
or
param_explicit_creator<my_class, my_class_creator>(arg);
or
param_explicit_creator(TypeTag<my_class>(), TypeTag<my_class_creator>(),arg);

The arg and arglist, of course, are the XParam argument-passers list that matches the argument list given in the functor.

When registering a creator, an output function, or any other functional object, in XParam, the registered class, or at least its interface, must be visible to the registration code. For this reason, if you separate the functors from the registration code, you will want to #include their header files in the registration file.

In the Programmer Interface section, we mentioned the fact that, unlike in C++, XParam does not allow you to register two constructors for ClassA, one from a pointer to ClassB, the other from a constant reference to ClassB. This is because XParam uses the same syntax to signify both. If you encounter this problem, or any other reason why XParam does not allow you to register the constructors that you want to use, you may want to use creators as a workaround. Consider, for example, this creator functional object:

class workaround {
  public:
    static ClassA* create(const ClassB* const b, const Dummy&) {
      return new ClassA(b);
    }
};

If you register it, you will be able to use the Class-A-from-ClassB-pointer constructor by invoking this creator, which receives an extra "Dummy" parameter. If you already have the ClassA-from-const-ClassB-reference constructor and class "Dummy" registered, this workaround allows you to use the user syntax a=ClassB(...) to signify the construction of ClassA from a ClassB const reference (assuming a is a paramter of type ClassA), and to use a=ClassA(ClassB(...),Dummy()) to signify a ClassA-from-ClassB-pointer construction. Such workarounds are hardly ever needed, but it is useful to know they can be used in the unlikely case you're going to need them.

Note: there is a more elegant way of working around this problem, and it doesn't utilize creators at all. Programming in C++, you may sometimes find yourself adding a dummy class to your program in order to solve an ambiguity. For example, you may want to add the class "Length" to your program, simply to differentiate between Vector(7) and Vector(Length(7)). The same solution works in XParam: program and register class WorkaroundPtr that has an explicit constructor from a "const ClassB* const" and a conversion operator to a "ClassA". Using it, you'll be able to differentiate between

a=ClassA(ClassB(...))

indicating a construction from a "ClassB" constant reference, and

a=ClassA(WorkaroundPtr(ClassB(...))

indicating a construction from a "ClassB*" pointer.

Conversions

In C++, one can define a conversion either in the form of a conversion constructor, or in the form of a conversion operator. Both because these have slightly different behaviors, and because we wanted to keep the conceptual difference that the C++ language makes, XParam also allows the registration of conversion operators. These have the following format:

param_conversion_operator(SourceTag<source_type>(), TargetTag<target_type>());

Vectors

As was mentioned before, templates can not be registered in XParam, but template specializations can. So, for example, one can not register the template std::vector, but one can register any of its specializations, such as std::vector<int> and std::vector<Duck>.

Since the standard vector is a highly useful template, we created a single registration command that executes all the registration commands needed to register a new vector specialization. The command has the following form:

param_vector<my_class>();

This is all you need to do, after you've registered my_class, to register std::vector<my_class>. For your convenience, XParam registers this class under the name vector<my_class>, omitting the std:: prefix.

Similarly, XParam also provides the following registration commands:

param_ptr_vector<my_class>();
and
param_const_ptr_vector<my_class>();

for registering std::vector<my_class*> and std::vector<const my_class*>, respectively. There's no reason not to add all three of these immediately, whenever you register a class, because they are very useful.

Note: One possibility to construct a vector in XParam is by explicitly listing its elements in list form. (see The User Interface for details.) Any vector registered by one of the param_vector variants will support this construction method, but you don't have any equivalent registration command at your disposal for your own classes. However, taking advantage of the fact that vector supports this construction method should be just as good for you: all you need to do is to register a constructor or creator that expects an std::vector. Entering a list instead of a vector will cause an implicit conversion, and is just as effective. For example, the registration of a creator for a map given in the Creators section allows you to use

my_map=[ pair("goodbye",2), pair("hello",1) ]

to initialize an std::map on a single line. The value-list is converted into an std::vector, and this converted to an std::map.

Output

All the sections so far, have dealt with input, i.e. with the question of how user-input, whether from the command-line, from file or piped-in from another program, should be parsed and made into real, live, working objects. This section is different, because here you specify how you want XParam classes to serialize themselves back to a streamable output form, so that you can save them in a file, print them or e-mail them to your congress member.

The XParam output registration command is very simple:

param_output<my_class, my_class_output_functor>();

It is very similar to the registration of creators, but differs in the fact that there is no argument list, and that there is no need for a variation with TypeTags. Any modern C++ compiler should be able to handle this form. (This is because it doesn't use explicit template function arguments. It is simply a constructor call.)

This command registers an output functor for your class. However, you still need to supply the functor itself. To understand what this functor is and what it does better, recall that XParam outputs its variables in such a way that they are readable by XParam, in case you want to read them again from a different program or in a different time. So, XParam must be able to output a Triangle, for example, in the following format:

Triangle(Point(5,6),Point(7,8),Point(10,1))

Naturally, this calls for a recursive approach. The output functor of class Triangle should tell XParam which three points should be output in order to describe this triangle, at which point XParam will recursively have to find out how to output a Point and finally how to output an integer. Neither of the latter two should be supplied by the Triangle class. It only needs to tell XParam which three Point objects compose it. Here's how this is done:

class Triangle_output_functor {
  public:
    static ValueList sub_objects(const Triangle& t) {
      ValueList vl;
      return vl << Val(t.p1) << Val(t.p2) << Val(t.p3);
    }
};

The interface of the output functor is always the same: it must support a public static method called sub_objects that receives a constant reference to the output variable and returns a variable of type ValueList. The way to fill this ValueList with the correct information is to construct it with a default constructor, and then to use

ValueList& operator<<(ValueList&,const Handle<ValueSource>&);

to append to it the sub-objects that compose the object to be printed.

After you register an output functor, it's a good idea to go back and check that you really do have a constructor that matches the output function, so that variables that have been serialized and then deserialized will return exactly to their original state. It can be very confusing if they don't.

We recommend supplying output capability to all classes you register. This will make the debugging process much more painless for both you and the programmer, and will make class usage much more convenient to the user (because class output is used in much of XParam's help-giving and error-handling mechanisms). If you don't, any attempt to serialize the class and output it will result in

classname(NO OUTPUT FUNCTION)

Which is not readable as input to another program using XParam parameter handling.

Weights

The registration syntax described up until now is all the syntax you're ever going to need for registering classes in XParam. However, if you ever find yourself trying to register a basic C type, a non-class, non-struct type, you might need a bit more (though not a whole lot more).

The only real difference, as far as XParam is concerned, between native types and non-native types, is that conversion between native types is considered more "attractive" than user-defined conversions. Consider, for example, the following situation:

class A {
  public:
    A(int);
};

class B {
  public:
    B(double);
    B(const A&);
};

void main(void) {
  B b(7);
}

Variable b can either be constructed from class A or from a double. C++ will choose the latter option, because converting an int to a double is more attractive than converting it to an A.

If you want to register types that will make use of this difference in conversion weights, here's how you do it.

XParam, like C++, knows two conversion weights other than user-defined conversions. These are "standard conversion" and "promotion". For details regarding these weights and their behavior in C++, consult the ISO-C++ standard near you. To register constructors and creators that make use of these weights in XParam, use param_weighted_ctor and param_weighted_creator instead of param_ctor and param_creator, respectively, and add, as a third and last parameter to the registration command, either STANDARD_CONVERSION or PROMOTION. You can also add a weight to the registration of a param_conversion_operator as a third and last parameter. For conversion operators, no other change is needed.

As was implied above, conversion weights can only be used in conversions, that is, in conversion constructors, conversion creators and conversion operators. If your constructor or creator has more than one argument, weights can not be used.

Dynamic Loading

We have already mentioned that XParam can load classes, including their registration information, dynamically.

In order to do this, XParam must have the information of what to load when a certain class is needed. This information should be supplied in the form of an XPN file. An XPN file is a file with an xpn extension which contains one or more

[class1, class2] => [file1, file2]

sequences. The particular line in the example states that if either class1 or class2 is needed, file1 and file2 should both be dynamically loaded. Any amount of white-space can be added in the XPN files, except in the middle of a class name, a file name, or the => symbol. If a list of classes contains only one class, the brackets around it may be omitted. Likewise if the file list contains only a single file, the brackets around its name may be omitted. XPN files may also contain single line comments, these being lines beginning with the character '#'.

You may have as many XPN files as you want, and place them in any directory you want (as long as it is accessible to XParam). However, the full list of all directories containing XPN files should be available in the environment variable XPARAM_CLASSPATH.

In the Usage Examples section, a program using dynamic loading is demonstrated.

Note: XParam currently does not support dynamic loading under Windows. You must link in your classes and their registration commands statically, instead.

Next: Installing XParam
Previous: The Programmer Interface
Up: Table of Contents