freepooma-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

domain architecture diagram


From: Allan Stokes
Subject: domain architecture diagram
Date: Fri, 29 Jun 2001 04:45:38 -0700

Hello,

I spent a number of hours looking at relationships within the Domain sources
without becoming entirely conscious how many different boxes I was visiting.

As an experiment I decided to fork my own implementation of the Interval<>
class to see how much I could change without breaking higher level
dependencies.  The first thing I discovered is that I needed a diagram.

I mocked up a quasi-UML diagram which I prepared with the Dome UML editor.
This is available from http://www.src.honeywell.com/dome/ for Windows/Linux.
This diagram is attached as a GIF.

The first phase of the experiment was simply to clone Interval<> as
Interval2<> and succeed in running domaintest.cpp with the identical clone
Interval2<> substituted for all occurrences of Interval<>.

This required creating four new source files: Interval2.h,
DomainTraits.Interval2.h, NewDomain2.h, and DomainArithOpsTraits2.h

DomainTraits.Interval2.h and Interval2.h are just copies of the reference
implementation with all instances of Interval replaced by Interval2.

NewDomain2.h is my cloned copy of NewDomain.h which defines ten instances of
POOOMA_NEWDOMAIN_XXXX macro expansions.  These grant Interval2 the same
standing as Interval in new domain expressions.

DomainArithOpsTraits2.h defines a number of specializations of class
DomainArithOpTraits<> on the type Interval2<Dim>.

I have a number of comments about the relationships present in this diagram
which I could discuss, but I don't wish to duplicate material which I'm
already developing for the conceptual documentation.

I'll make a few comments to express some relationships I didn't fully
capture in the diagram and leave the full discussion for later.

The traits classes in the top right of the diagram are better thought of as
implementation classes.  DomainTraits<> is functioning as a catalog and
Interval<Dim> is functioning as a lookup tag.  Because Interval<> is using
itself to bind types to its own base classes, Interval<Dim> will be an
incomplete type in the context of any of the traits classes.  I haven't
determined yet whether the traits classes depend upon the lookup tag
actually being the same as the name of the Interval class being defined,
e.g. in a cast expression (can you do much else with an incomplete type?)

The top left shows the Interval classes and base classes.  Only the
relationship is not that of a base class.  It makes more sense to think of
these boxes as representing different interfaces to a single
implementation.

The key to understanding the structure is the declaration for the class
state.

template <> struct DomainTraits< Interval<1> >::Storage_t;

This type encapsulates the runtime state of any Interval<1> object.  The
N-dimensional case looks like this (paraphrased):

template <int Dim> struct DomainTraits< Interval<Dim> > {
  typedef UninitializedVector<Interval<1>,Dim,Element_t> Storage_t;
};

OK, that's interesting.  Interval<N> defines its state in terms of its own
specialization Interval<1>.

The only object with state is DomainBase which contains a member of this
type Storage_t.  All Interval<> objects inherit their state from this base
class via this idiom:

struct foo : public base<foo>;

It's important to note that polymorphism does not exist in this construct.
A unique downcast is always implied.  If the derived class contains no
state, the downcast becomes a change of interface and nothing more.

template <class DT>
class DomainBase {
  // unwrap this object back to its derived-class type
  inline
  Domain_t &unwrap() { return *static_cast<Domain_t *>(this); }

  // a const version of unwrap
  const Domain_t &unwrap() const {
    return *static_cast<Domain_t *>(const_cast<DomainBase<DT> *>(this));
  }
};

I'm curious why the second version is not written:

  static_cast<const Domain_t *>(this);

Is the two-step conversion required by the language, or was this shaped by
one of the compilers being used at the time?  I don't know the exact rules
of casting.  Is static_cast defined to leave constness unchanged?

In Pooma as it stands, these casts are essentially null-operations.
However, if a class derived from Interval<> employed virtual functions or
multiple inheritance, this would no longer be true.  However, this unwrap()
downcast would remain legal even if non-trivial.

I'm not certain that the unwrapped object can be used without restriction
within the DomainBase context; difficulties might arise involving incomplete
definition.  Mostly DomainBase delegates the Storage_t member to functions
in the traits class implementation (which defines Storage_t in the first
place, so the circularity evaporates).

There are two steps down in the downcast direction: Domain<Dim,Inteval<Dim>>
and Interval<Dim>.  These classes are effectively the same thing.
Interval<Dim> obtains its entire implementation from
Domain<Dim,Interval<Dim>>, excepting constructors and the assignment
operator.  In a sense, Interval<> implements a notation, Domain<> implements
the semantics.

The Domain<> layer has two distinct functions.  The obvious purpose is to
factor out commonalities between different domain components (Range,
Interval, Grid, etc.)

The less obvious purpose is that Domain<> is used in the higher layers of
Pooma to specialize function arguments to "all things domain" as a class.

For example, from DynamicLayout.h

template <class DT>
inline DynamicLayoutViewData(const Layout_t &layout,
                            const Domain<1,DT> &dom);

This template declaration matches any one-dimensional object of generic type
Domain.  You catch this object as a base class.  With a compile-time
downcast you recover the (uniquely typed) derived object.

>From the point of view of writing the conceptual documentation, none of the
relationships expressed in this graph as inheritance relationships are
functioning as runtime polymorphism.  The labels on the lines are deceptive.

In practical terms it's not easy to formulate conceptual documentation
unless you can envision multiple ways to implement a compliant object.  The
technique of matching on Domain<N,T> type patterns makes it tricky to depart
from the existing framework, and perhaps the ArithOps/NewDomain accessories
also mandate elements of this framework (I haven't looked at this closely).

One of the questions I'll be asking myself of the next couple of days (as I
flesh out the formal documentation) is whether placing the Storage_t member
variable at the bottom of the structure (DomainBase) is a vital aspect of
the unwrap() magic, or is entirely independent as an optimization to avoid
empty-base-class size penalities, and to what extent it complicates the code
to preserve this as such.

Allan

Attachment: interval.gif
Description: GIF image


reply via email to

[Prev in Thread] Current Thread [Next in Thread]