TCL_obj

The TCL_obj function creates a set of TCL commands that implement get/set functionality for the class members. For example, with a class definition:

class foo: public TCL_obj_t {int a, b; void foobar(int,char**)} bar;
TCL_obj(&bar,"bar",bar); creates the TCL commands bar.a and bar.b. To set the value of bar.a, use the command bar.a val from TCL. To get the value, use [bar.a].

Also created is the TCL command bar.foobar, which will run respective member function of foo when called from TCL.

Any nonoverloaded member function can be accessed from TCL, provided the arguments and return types can be converted from/to TCL objects. In particular, it is not possible at present to call methods that take nonconstant references.

Overloaded method types in general cannot be called, but it is possible to create variable length argument lists by declaring a method with an (int,char**), or a (TCL_args) signature. Such methods are not easily called from C++, and generally, one needs to define a set of overloaded functions of a different name (eg capitalised) suitable for calling from C++, as well as the variable length argument list for use from TCL. However, as a special case of an accessor (emulating the setting/getting of an member attribute), one may make use of the Accessor class, which is equally callable from C++ as TCL.

Accessor is not easily usable from within the C++98 language standard (see acessor.h in the test directory), but makes much more sense in the C++11 standard. For example, assume that Name() and Name(const string&) have been defined as a getter and setter method of the attribute name, then one may define a member

Accessor<string> name {
    [this](){return this->Name();}, 
    [this](const std::string& x){return this->Name(x);}
};
where the use of lambdas and brace initialisers makes it easy to assign code for the getter and setter components of the accessor. This member will be accessible as an attribute from TCL (just as if name had been defined as a string member), and also callable from C++ as name() or name("someName") as appropriate.

One downside of the Accessor class is that it is not copy constructible, as copying the accessor will copy a reference to the wrong accessed object. Consequently, if copy construction is required for the object being accessed (eg for DCAS), then a custom copy constructor needs to be provided.

As an alternative to (int,char**) arguments for implementing TCL commands, one can also declare the arguments (TCL_args). TCL_args variables can be assigned to numerical variables or strings variables, and the appropriate argument is popped off the argument stack:

int x=args;
double y=args;
assigns the first argument to x and the second to y. This method use the Tcl_Obj structure, so the values needn't be converted to strings at all.

The arguments may also be assigned using the >> operator:

int x; double y;
args >> x >> y;

A third style uses the [] operator:

int x=args[0]; double y=args[1];
The number of remaining arguments is available as TCL_args::count.

If operator>>(istream,T) is defined, then you may also use the >> operator to set a variable of type T, eg:

void foo::bar(TCL_args args)
{
  iarray x;
  args>>x;
}
the assignment operator cannot be used for this purpose, unlike simple types, because nonmember assignment operators are disallowed in the standard. Type conversion operators do not appear to work.

For technical reasons, the name of the TCL command is available as args[-1].

You can create TCL_args objects by using the << operator. This enables the calling of methods taking a TCL_args object from C++, viz:

struct Foo
{
  void bar(TCL_args args) {...}
};

Foo().bar(TCL_args()<<1<<3.5);

The TCL_obj_t data type also implements checkpoint and restart functions, so that any class inheriting from TCL_obj_t also gains this functionality, as well as client-server functionality.

A helper macro that performs the above is make_model, which is used in a declarative sense, which also initialises the checkpoint functor.

Associated with each of these TCL commands, is an object of type member_entry<T>. Pointers to these objects are stored in a hash table called TCL_obj_properties. The STL hash table behaved rather stangely when used for this purpose, so a class wrapper around TCL hash tables was employed instead:

template<class T>
struct TCL_obj_hash
{
  struct entry 
  {
    entry& operator=(const T& x);
    operator T();
  };
  entry operator[](const char*x);
};
So objects of member_entry<T>* can be inserted into the hash table as follows:
member_entry<T>* m; eco_string d;
TCL_obj_properties[d]=m;
but to extract the data, use memberPtrCasted
if (T* m=TCL_obj_properties[d]->memberPtrCasted<T>())
   ... *m
will allow you to access the TCL object d, if it is castable to an object of type T (is a T, or is derived from a T).

A utility macro allows these objects to be accessed simply:

declare(name,typename, tcl_name)
where name is the name of a variable (in reality a reference), of type typename that will refer to the variable having the TCL handle tcl_name. The macro performs error checking to ensure such a variable actually exists, and that it is of the same type as typename.

Objects can be placed into TCL_obj_properties by a several different means:

  1. make_model(x), which places all of the leaf objects of x (which must be derived from TCL_obj_t) into TCL_obj_properties, and also completes the construction of the TCL_obj_t object;
  2. register(x), which places x into TCL_obj_properties, as well as the leaf objects -- can also be called as TCL_obj_register(object,object name);
  3. TCLTYPE(typename), TCLPOLYTYPE(typename, interface), where typename is defined C++ type, and interface is a base class of typename. This creates the TCL command typename, which takes one argument, a variable name for it to be referred to from TCL, and creates an object of that type which it registers in TCL_obj_properties. If TCLPOLYTYPE is used, the base class type is used for registration - so this object can be used wherever a polymorphic type with the specified interface is expected. For example, consider the following code which creates and initialises an object of type distrand and gives it the TCL name PDF (from testdistrand.tcl):
  4. TCLTYPEARGS(typename), TCLPOLYTYPEARGS(typename, interface), is similar to the above, except that the constructor needs to be declared with signature (TCL_args). This enables constructor arguments to be passed in from TCL.

    distrand PDF
    PDF.min -10
    PDF.max 10
    PDF.nsamp 100
    PDF.width 3
    PDF.Init dist
    .....
    PDF.delete
    
    This macro also defines an x.delete procedure for deleting that object, once no longer desired.

A TCL registered object, particularly dynamically created TCLTYPE objects can be assigned to a member of type TCL_obj_ref. This is particularly useful for random number generators:

class Foo: public TCL_obj_t
{
 public:
   TCL_obj_ref<random_gen> rng;

   ...
     rng->rand();
};

Then the member Foo::rng can be assigned an arbitrary random number generator within the TCL script, such as the PDF example above.

distrand PDF
PDF.min -10
...
foo.rng PDF
...

Using TCL_obj_ref also allows that object to be serialised, and to be reconnected after a restart, provided the object has been created prior to the restart.