Crush -- CORBA for hush
Anton Eliëns
Vrije Universiteit, Department of Mathematics and Computer Science,
De Boelelaan 1081, 1081 HV Amsterdam, The Netherlands
eliens@cs.vu.nl,
fax: +31.20.4447653
abstract
This paper describes how the hush toolkit
has been extended with CORBA functionality.
The major problem that arises when extending
a given toolkit or framework, such as hush,
with CORBA IDL interfaces and classes to
implement these interfaces is to provide for
a seamless integration of the already existing
code with the CORBA-based extensions.
In crush we have included facilities
for object creation and access, as well as client-side
adaptors for the hush CORBA objects,
to resolve the type clash between the original hush
class hierarchy and the hush CORBA object classes.
Introduction
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
The hush toolkit has been developed at the
Vrije Universiteit, Amsterdam, as part of the DejaVu project.
It provides a C++ interface to Tcl/Tk as well as
packages for developing distributed Web-aware
hypermedia applications.
It includes the functionality needed for business process
model simulations, real sound-synthesis
and TCP/IP based client/server programming.
With the availability of industrial strength
distributed object technology such as Orbix/CORBA,
it seems natural to extend hush
with the functionality needed to develop distributed
CORBA-based hush components.
However, extending a given framework is not as straightforward
as it may seem.
First of all, one has to decide which interfaces
may become public, that is may be exported as
IDL interfaces.
Secondly,
one has to decide how object references become known
to clients, and what rights clients have
to create objects within a particular server.
The most important problem, however, concerns
the type clash between the CORBA classes
implementing the IDL interfaces
and the 'native' class hierarchy
offered by the framework itself.
This paper gives an overview of the effort of
extending hush with CORBA.
In section 2,
the various issues involved in extending a given
framework or toolkit will be discussed in somewhat
more detail.
In section 3,
the IDL interfaces for hush and the widgets
will be described.
In section 4, some examples are given of how
to use hush in a CORBA environment.
Finally, in section 5,
we will discuss what further research is needed
to make hush fully 'CORBA-compliant'.
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
The legacy problem -- integrating CORBA
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
CORBA technology is well-suited to develop
distributed applications.
For new projects, the restrictions imposed by
CORBA can be taken into consideration from the start.
For projects that carry the legacy of existing code,
a decision must be made to what extent the
CORBA functionality is integrated with the legacy code.
On one side of the spectrum,
CORBA technology can be used simply for wrapping the legacy
code.
For example a database may be embedded
in a CORBA server, without affecting the database itself.
However, for an object-oriented framework such as hush
such a solution is not very satisfying.
Instead, one would like to have the basic
interfaces of hush available to develop
distributed components of arbitrary granularity.
In this section, we will discuss some of the issues
that arise when extending a given (GUI or hypermedia)
framework with CORBA IDL interfaces reflecting
the functionality of the original framework.
Object creation and access
The CORBA Naming Service may be used to provide access to
an object residing somewhere on a server.
Alternatively, the server may export a reference to
a factory object that allows the client to create objects within the
server.
For giving access to objects within a particular hush
component, we have provided dots (distributed object tables)
for both hush and widgets components.
Using the dot the client can access an object of a given type by
the name it is given by the server.
The object must already exist in the server.
In case clients are allowed to create objects within
the server, a factory is provided for creating hush
or widget objects.
Client-side adaptors
The intermediary between clients and servers in a CORBA-based system
are the CORBA IDL classes generated by the idl compiler
from the IDL interfaces.
These classes (using the C++ language binding)
inherit directly from the CORBA::Object class and hence
do not fit within the given class hierarchy.
To allow clients the use of CORBA IDL classes wherever one
of the original hush classes is expected,
client-side adaptors have been provided for each of the hush
or widgets IDL classes.
An additional advantage of client-side adpators is that
they allow for overcoming the
'weaknesses' of IDL with respect to
overloading member functions,
parametrized types and operator definitions.
Typically, client-side adaptors have their corresponding hush class
as a base class and simply delegate method invocations
to the CORBA object they encapsulate.
Events versus object method invocation
Since GUI components are in some way typically
event-driven, one may be inclined to limit
the communication between such components to exchanging events.
The CORBA Event Service would suffice for such communications.
Nevertheless, in our opinion events should be used in a very restricted manner.
Events tend to break the 'crisp' object interfaces that
are one of the benefits of an object-oriented
approach to design.
For the hush CORBA extensions, we have chosen for retaining the original hush
object interfaces.
Note however that the IDL interfaces are somewhat more
abstract than the corresponding C++ interfaces.
Nevertheless, the event interface is part
of the hush module.
Together with the dispatch function of
the handler interface incoming events resulting from user
actions may be dispatched directly to
remote components. See the [Canvas] example.
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
The IDL interfaces reflect to a large extent the functionality of
the original hush and widgets interfaces.
See Hush.
In this section a partial listing of the interfaces will be given.
Note that, in comparison with the corresponding C++ classes,
the IDL interfaces are much more abstract in the sense of omitting
many member functions that are required
for the implementation of the actual hush framework.
The hush module
The hush module contains interfaces corresponding to
the basic hush classes,
handler, event, kit, widget
and item, as well as the auxiliary classes
for iterators and containers.
interface handler {
event dispatch( in event data );
};
slide: handler
The handler interface provides only a method
for dispatching events. It may be extended in the
future though.
In hush almost every class is derived from handler.
This is directly reflected in the hush IDL interfaces.
interface event : handler {
attribute long type;
attribute long x;
attribute long y;
};
slide: event
The event interface offers attributes to
determine the type of event and its location.
Also the event interface will very likely be extended in
the future, to allow for a greater variety of events.
interface kit : handler {
void source(in string file);
void eval(in string command);
term result();
widget root();
};
slide: kit
In hush, a kit provides an interface to
some embedded interpreter, such as a Tcl interpreter
or a logic engine as provided by BinProlog.
The kit gives also access to the underlying window environment,
in particular it may be asked to provide a reference to
the root window.
interface widget : handler {
string path();
void eval( in string cmd );
void configure( in string options );
void pack( in string options );
};
slide: widget
A widget is a user interface gadget.
The widget interface collects the functions that
all these gadgets have in common.
interface item : handler {
void move( in long x, in long y );
};
slide: item
An item is obtained when creating a graphical
object for a canvas. Subsequently, the item reference
suffices to manipulate such objects.
Also the item interface will very likely
be extended in the future.
Iterators and lists
As an alternative for
CORBA arrays and sequences, the hush
module offers interfaces for iterators, containers and
syntactic objects called terms.
interface iterator {
Object next();
};
slide: iterator
From a client's perspective, an iterator
is a data generator.
To deal with typed iterators, the hush
C++ library offers template client-side adaptor classes
encapsulating the untyped CORBA iterators.
See section [Items] and the code
in [Iter].
interface container {
long length();
Object first();
Object next();
Object current();
iterator walk();
};
slide: container
The container interface offers access
to the famous hush list class.
It offers functions for cursor-based list traversal
as well as the walk function that may be used to obtain an iterator.
interface term {
string next();
};
slide: term
Currently, the term interface offers only the
functionality to use a term as an iterator
for strings.
The idea is that one can obtain strings from a term
as long as the term, for example the result of a logic query,
still contains information.
Factories and distributed object tables
To obtain references to objects, clients may use
either factory object or distributed object tables.
interface factory {
hush::kit kit(in string name);
hush::event event(in long type);
};
slide: factory
The factory interface allows only for creating
a kit and for creating an event.
Note that handler objects may not be created directly.
interface dot {
hush::kit kit(in string name);
hush::container container(in string name);
hush::iterator iterator(in string name);
hush::factory hush(in string name);
};
slide: dot
Apart from giving access to a hush factory,
the dot interface allows for getting access to
a kit, a container and
an iterator.
When obtaining references through a dot object,
these objects are assumed to exist within the server.
The widgets module
The widgets module provides the actual user interface
gadgets for hush.
Below we have included only the (partial) interfaces for
a canvas and a message widget.
module widgets {
interface canvas : hush::widget {
widgets::canvas create( in hush::widget anc, in string path );
hush::item circle( in long x, in long y, in long radius, in string options );
// other items ...
};
interface message : hush::widget {
message create( in hush::widget anc, in string path );
void text(in string txt);
};
interface factory : hush::factory {
widgets::canvas canvas(in string name, in string options);
widgets::message message(in string name, in string options);
};
interface dot : hush::dot {
widgets::canvas canvas(in string name);
widgets::message message(in string name);
widgets::factory widgets(in string name);
};
};
slide: module widgets
Note that each widget type has a method create,
with which an actual widget of that type can be created.
In effect this means that each widget object may act as a factory
for widget objects of that type.
(The server may however refuse to create such objects!)
In addition to the specific gadget interfaces, the widgets module
provides a factory and dot interface,
extending the respective hush interfaces.
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
The hush CORBA extensions may be used in a number of ways.
For example, the client does not need to be linked with hush
when only the server side is given a graphical user interface.
In case also the client has a graphical user interface,
the client side may dispatch incoming events to the server,
as illustrated in the [Canvas] example.
The communication between server and clients can be arbitrarily complex.
The final example shows how to employ iterators and containers
to give clients access to collections of information.
A remote interpreter kit
This example uses a remote kit. It shows how to get access to a message widget and a kit via a dot
(which is a distributed object table). The client access the kit and the message widget by using a name
(hello for the message widget and tk for the kit).
The client can send Tcl/Tk interpreter commands to the
kit.
A tk client
hush::dot* hush; // (distributed) object tables
widgets::dot* widgets; // widgets contains hush
hush::kit* tk; // remote objects, these must exist!
widgets::message* banner;
try {
hush = widgets = widgets::dot::_bind (DOT, argv[1]);
tk = hush->kit("tk");
banner = widgets->message("hello"); // must exist
} catch (...) {
cerr << "Unexpected exception ..." << endl;
return -1;
}
while (1) {
char text = readtext(); // from stdin
banner->text( text ); // display text
tk->eval(text);
}
slide: A tk client
This fragment shows how a distributed object table is obtained
via the bind function.
From this table, the client obtains a kit and a message area.
Queries are read in from standard input, displayed in the message area and
evaluated.
Queries may be arbitrary Tcl/Tk commands.
In this way the client may even construct a complete user interface
through Tk commands.
The tk server
class application : public session {
public:
application(int argc, char* argv[]) : session(argc,argv,"hello") {
}
void corba();
int main() {
tk->trace();
kit::declare("tk",tk);
message* m = new hello(".hello");
m->pack();
message::declare("hello",m);
corba(); // make yourself available as a server
return OK;
}
};
slide: The tk server
The server is realized as a standard hush program,
except for the call to corba (for which the code is given below).
Note that the calls to declare for both the kit
and message objects is needed to make these objects
accessible via the dot.
A tk application
void application::corba() {
widgets::dot* dw = new widgets_dot_srv(); // create dot for widgets
try {
CORBA::Orbix.registerIOCallback(it_orbix_fd_open, FD_OPEN_CALLBACK);
CORBA::Orbix.registerIOCallback(it_orbix_fd_close, FD_CLOSE_CALLBACK);
CORBA::Orbix.impl_is_ready(DOT,0);
CORBA::Orbix.processEvents(0);
}
catch (...) {
cout << "apparently something went wrong" << endl;
}
slide: A tk application
In application::corba() a distributed object table is created.
This object is exported as a server by a call to
Orbix.impl_is_ready(DOT,0),
where DOT is a macro defining the name of the server.
Calling registerIOCallback is needed to
merge the CORBA server event loop with the window event loop
for hush.
Evaluating logical queries
With a few minor changes, the client program can be adapted
for accessing a logical query evaluator.
Evaluating logical queries
try {
tk = hush->kit("bp"); // A kit for BinProlog
tk->eval("consult(facts)");
}
catch(...) {
cout << "An exception ... " << endl;
}
while (1) {
char* text = readtext();
tk->eval(text);
hush::term* t = tk->result();
char* q = 0;
while ( (q = t->next()) )
cout << "Result: " << q << endl;
}
slide: Evaluating logical queries
This fragment show how to obtain a kit for BinProlog and
consult a facts database.
Since queries may produce multiple answers
the client must iterate over the term resulting
from the query.
A remote canvas
This example shows how a client canvas can be used to draw on a remote canvas. For the Orbix 1.3 version
there are still several bugs. Events do not work as they should, and
quitting the remote application also
does not work.
A canvas client
class draw_clt : public canvas { [2]
public:
void plug(widgets::canvas* x) { draw = x; }
int operator()() {
hush::event* e = hush->event(_event->type());
cerr << "Getting event " << e->type() << endl;
e->x(_event->x()+10);
e->y(_event->y()+10);
//hush::event::_duplicate(e); // CORBA 2.0
e->_duplicate();
hush::event* res = draw->dispatch(e);
return canvas::operator()();
}
draw_clt(const widget* w, char* path ) : canvas(w,path) {
configure("-background white");
geometry(200,100);
self()->bind(this);
dragging = 0;
}
draw_clt(char* path ) : canvas(path) {
configure("-background white");
geometry(200,100);
self()->bind(this);
dragging = 0;
}
void press( event& ) { dragging = 1; }
void motion( event& e) {
if (dragging) {
self()->circle(e.x(),e.y(),2,"-fill black");
draw->circle(e.x(),e.y(),3,"-fill yellow");
}
}
void release( event& ) { dragging = 0; }
protected:
int dragging;
widgets::canvas* draw;
};
slide: A (remote) canvas client
This fragmemt shows the implementation of
a canvas which is simultaneously the client side of a remote canvas.
The method plug allows for declaring the remote canvas,
which is accessed via the instance variable draw in
both the operator method and
the motion method (whent dragging).
In the operator() an event is created which is dispatched to
the remote canvas.
Note that this is possible since a canvas is a handler.
In the motion method, a large yellow dot is drawn
on the remote canvas, whereas the local canvas draws a black dot.
Combined, the actions on the remote canvas result in
drawing parallel yellow and black dots.
The server
A canvas server
class draw_srv : public canvas {
public:
draw_srv( const widget* w, char* path ) : canvas(w,path) { (a)
geometry(200,100);
self()->bind(this);
dragging = 0;
}
void press( event& ) { dragging = 1; }
void motion( event& e) {
if (dragging) circle(e.x(),e.y(),10,"-fill black");
}
void release( event& ) { dragging = 0; }
protected:
int dragging;
};
slide: A canvas server
The canvas implementation on the server side straightforwardly
implements a hush canvas.
It is embedded in a CORBA server when an object reference
is given to it via the distributed object table.
Moving items
This example is similar to the [Canvas] example, but shows some additional features, such as how to
manipulate a list of items.
Moving items
list<hush::item>* rlist = new list<hush::item>;
item* it = draw->circle(40,40,10,"-fill yellow");
hush::item* rit = new item_srv(it);
rlist->insert(rit);
it = draw->circle(30,30,10,"-fill red");
rit = new item_srv(it);
rlist->insert(rit);
hush::container* rx = new list_srv<hush::item>(rlist);
list<hush::item>::declare("items",rx); // store server
iter<hush::item>* riter = rlist->walk();
iter<hush::item>::declare("riter",riter);
slide: Moving items
The fragment above illustrates the creation of a list
of items.
In addition it shows how to obtain an iterator and
how the iterator may be declared to make it accessible
via the distributed object table.
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
Conclusions
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
Extending hush with CORBA was an
an excellent exercise to get acquainted with
Orbix/CORBA technology.
From a designer's point of view,
the hush framework proved to be
sufficiently abstract to allow for
recapturing its design by means of IDL interfaces.
Lacking in the current realization of crush though,
are proper exceptions to indicate possible error conditions.
For making hush CORBA-compliant, we have made a
first step.
To complete the job, however, we need to
explore in what way crush components can be employed
in actual applications.
Evidently, the contribution of this work is
that it shows how to integrate CORBA functionality
with an already existing framework.
In particular the need for client-side adaptors
for resolving the type clash between the 'native' classes
and the CORBA IDL classes has been amply demonstrated.
Enriching hush with CORBA makes crush
a potential competitor of Fresco, the CORBA based GUI toolkit
derived from the Interviews library.
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
References
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
Technology, Tools and Applications,
Computer Networks and ISDN systems,
Vol 27. No 6, Elsevier, 1995, pp. 1105-1110,
Also at
www.igd.fhg.de/www/www95/proceedings/papers/48/main.html
Hush -- a C++ API for Tcl/Tk,
Bradford, 17-18 April 1996, Britisch Computer Society
Music in Time-based Hypermedia,
pp. 224-227
The Web revolution,
World Wide Web Journal, O'Reilly and Assoc., Inc., pp. 309-314,
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
slide: Appendix
Common definitions
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
The macros in common.h have been introduced
to allow for multiple CORBA implementations.
Common definitions
#ifndef _orb_common_h
#define _orb_common_h
#ifndef ENV
#define ENV CORBA::Environment&
#endif
#ifndef _sk
#define _sk(X) X##BOAImpl
#endif
#ifndef _msk
#define _msk(M,X) M::_sk(X)
#endif
#ifndef _crob
#define _crob(M,X) public virtual _msk(M,X)
#endif
#endif
slide: Common definitions
These macros as well as a number of other technical solutions have been
inspired by the work of John Caspers as described in Isis.
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix
This section presents selected samples of the code
implementing server-side code for the CORBA IDL
classes as well as the client-side adaptors for integrating these classes
in the hush class hierarchy.
Handlers
The handler class may be considered the most important
class of hush since almost every other class, including
the event, widget and item classes
are derived from handler. See Hush.
Server code - handler
class handler_srv : _crob(hush,handler) {
public:
handler_srv(const handler* x = 0) : _bdy((handler*)x) { }
protected:
void set_body(const handler* x) { _bdy = (handler*) x; }
handler* _body() const {
return (handler*) _bdy;
}
virtual hush::event* dispatch(hush::event* e, ENV);
private:
handler* _bdy;
};
slide: Server code
Client adaptor - handler
class handler_clt : public handler {
protected:
handler_clt(handler_srv* x) : _bdy(x) { }
void set_body(void* x) { _bdy = x; }
handler* _body() {
if (!_bdy) throw "handler has no body";
return (handler*) _bdy;
}
private:
void* _bdy;
};
slide: Client adaptor
Events
The implementation of the event_srv class
uses the functionality of handler_srv
of which this class is derived.
The event_clt (adaptor) class on the other hand
is directly derived from the hush event class.
Server code -- event
class event_srv : public handler_srv, _crob(hush,event) {
public:
event_srv(event* e) : handler_srv(e) { }
event_srv() { _type = 0; _x = 0; _y = 0; }
virtual void type( long t, ENV ) { _type = t; }
virtual long type( ENV ) { return _type; }
virtual void x( long t, ENV ) { _x = t; }
virtual long x( ENV ) { return _x; }
virtual void y( long t, ENV ) { _y = t; }
virtual long y( ENV ) { return _y; }
protected:
event* _body() { return (event*) handler_srv::_body(); }
long _type, _x, _y;
};
slide: Server code - event
Client adaptor - event
class event_clt : public event {
public:
event_clt(hush::event* x) : _bdy(x), event(this) { }
hush::event* operator->() { return _body(); }
virtual int type() const { return (int) _body()->type(); }
virtual int x() const { return (int) _body()->x(); }
virtual int y() const { return (int) _body()->y(); }
virtual void type(int n) { _body()->type( (long) n ); }
virtual void x(int n) { _body()->x( (long) n ); }
virtual void y(int n) { _body()->y( (long) n ); }
/* Orbix 2.0
virtual void type(int n) { _body()->type( (CORBA::Long) n ); }
virtual void x(int n) { _body()->x( (CORBA::Long) n ); }
virtual void y(int n) { _body()->y( (CORBA::Long) n ); }
*/
private:
void* _bdy;
hush::event* _body() const { return (hush::event*) _bdy; }
};
slide: Client adaptor - event
The kit
Both the server and client-side implementations of
kit delegate the calls to their embedded objects,
respectively a hush kit object and a CORBA IDL
kit object.
Server code - kit
class kit_srv : public handler_srv, _crob(hush,kit) {
public:
kit_srv(kit* tk) : handler_srv(tk) { }
virtual void source( const char* s, ENV ) {
char* p = new char[strlen(s)+1]; strcpy(p,s);
_body()->source(p);
}
virtual void eval( const char* s, ENV ) {
_body()->eval(s);
}
virtual hush::term* result( ENV ) {
term* x = (term*) _body()->result();
hush::term* res = new term_srv(x);
res->_duplicate(); // orbix 1.3
return res;
}
virtual void update( ENV ) { _body()->update(); }
virtual hush::widget* root( ENV );
protected:
kit* _body() { return (kit*) handler_srv::_body(); }
};
slide: Server code - kit
Client adaptor
class kit_clt : public kit {
public:
kit_clt(hush::kit* x) : _bdy(x) { }
virtual int eval( const char* s ) { _body()->eval(s); return 0; }
virtual widget* root( ) const;
private:
void* _bdy;
hush::kit* _body() const { return (hush::kit*) _bdy; }
};
slide: Client-adaptor - kit
Lists
The implementation of lists and iterators as template
C++ classes are taken from the work by John Caspers,
reported in Isis.
Server code -- list
template<class T>
class list_srv : _crob(hush,container) {
public:
list_srv(list<T>* it = 0) : _bdy(it) {
this->_duplicate();
}
hush::iterator* walk( ENV ) {
iter<void>* it = ((list<void>*) _body())->walk();
iter_srv* x = new iter_srv(it);
x->_duplicate();
return x;
}
long length( ENV ) {
return (long) _body()->length();
}
CORBA::Object* first( ENV ) {
T* x = _body()->first();
//dummy->_duplicate(x); // Orbix 2.0
x->_duplicate();
return x;
}
CORBA::Object* current( ENV ) {
T* x = _body()->current();
x->_duplicate();
return x;
}
CORBA::Object* next( ENV ) {
T* x = _body()->next();
x->_duplicate();
return x;
}
protected:
T* dummy; // for duplication in 2.0
list<T>* _bdy;
list<T>* _body() const { return (list<T>*) _bdy; }
};
slide: Server code -- list
Client adaptor - list
template<class T>
class list_clt : public list<T> {
public:
list_clt(hush::container* it = 0, T* = 0) : _bdy(it) { }
list_clt<T>* operator->() { return this; }
void operator=(hush::container* it) { _bdy = it; }
T* first() {
CORBA::Object* x = _body()->first();
if (x)
return (T*) dummy->_narrow(x);
else return 0;
}
T* next() {
CORBA::Object* x = _body()->next();
if (x)
return (T*) dummy->_narrow(x);
else return 0;
}
protected:
hush::container* _bdy;
hush::container* _body() const { return (hush::container*) _bdy; }
private:
static T* dummy;
};
};
slide: Client adaptor - list
Iterators
The implementation of the iter class follows
the same schema as the list implementation.
In particular for narrowing CORBA Objects to the actual
type T of the template client adaptor a static instance variable
dummy has been introduced.
This solution is again due to John Caspers.
Server code - iter
class iter_srv : _crob(hush,iterator) {
public:
iter_srv(void* it) : _bdy(it) { }
CORBA::Object* next( ENV ) {
return _body()->operator()();
}
protected:
void* _bdy;
iter<CORBA::Object>* _body() const { return (iter<CORBA::Object>*) _bdy; }
};
slide: Server code - iter
Client adaptor - iter
class iter_clt : public iter<T> {
public:
iter_clt(hush::iterator* it = 0, T* = 0) : _bdy(it) { }
hush::iterator* operator->() { return _body(); }
void operator=(hush::iterator* it) { _bdy = it; }
T* operator()() {
CORBA::Object* x = _body()->next();
if (x)
return (T*) dummy._narrow(x);
else return 0;
}
protected:
hush::iterator* _bdy;
hush::iterator* _body() const { return (hush::iterator*) _bdy; }
private:
static T dummy;
};
slide: Client adaptor - iter
[]
introduction,
legacy,
interfaces,
examples,
conclusions,
references,
appendix