Introduction

Previous articles introduced the C++ template metaprogramming language (MPL) and type lists. We showed how Boost.mpl helps us to write lists of interesting types, and how those techniques apply to MicroStation element types.

By writing a meta function that tests whether a type is present in a type list, we introduced a way to perform conditional class inheritance. Conditional class inheritance enables either dynamic or static polymorphism.

In this article we apply MicroStation element type lists to enable polymorphic C++ classes that correspond to MicroStation elements. The implemention is static, rather than dynamic. By that we mean that polymorphism is realised through C++ template classes. The templated classes implement a set of interface methods that are determined at compile-time. If you're interested in reading about dynamic polymorphism then skip to the preceding article.

MicroStation Element Classes

A MicroStation DGN file contains one or more models. A model may be 2D or 3D. A model contains graphic objects termed elements. MicroStation elements share some common characteristics, such as element type and element ID, symbology and level. The element ID is a 64-bit number, unique in each DGN file where elements are stored. Graphic elements have geometric attributes, such as origin, length or rotation. They have symbology such as colour, line thickness and level. The element type informs us of the form an element can take: for example, a line, ellipse or text. We model those elements with C++ classes: LineElm, EllipseElm and TextElm.

For the purpose of this discussion we'll mention only a few MicroStation element types. In practise there are about one hundred types. Not all types indicate a graphic element — some types are used as data containers in a MicroStation DGN file. We'll focus on visible graphic elements.

The API that extracts geometric and other data is specific to each type of element. A TextElm's location is determined by a single coordinate, known in MicroStation as the text origin, a 3D data point (stored in struct DPoint3d). A LineElm is described by a vector of 3D data points; an EllipseElm is defined by its two axes.

Modelling MicroStation Elements with Classes

Multiple Class Inheritance

The C++ classes that model MicroStation elements clearly need to be polymorphic: they must expose the distinct characterists of each element type. A TextElm must be able to provide its origin and text content, but we don't want those from a LineElm. A LineElm must be able to give us the 3D coordinates of its segments, but it can't tell us about its text content. And so on: each class that represents a MicroStation element must exhibit traits peculiar to that element.

Elements share some characteristics, such as symbology, but others are unique to that element type. Multiple class inheritance lets us model subtle differences quite well. Using type lists and MPL to control those subtleties via conditional class inheritance lets us automate trait selection. We'll focus on point elements as an example, to illustrate what we want to achieve.

Point Elements

The typelist that defines point elements is PointTypes (see header file ElementTypes.h). We've chosen to include cell elements, shared cell elements, text elements and attribute elements (tags) in that list. The MDL representation of an element is in struct MSElement, which includes a C-style union of most element structs. MSElement also stores MicroStation symbology and level information. Each of those element types has the following traits in common …

The common properties in the Element base class can be handled uniformly, more or less, in that base class. The point element traits provide the same result for each element type, but the API for extracting those data are unique to each element …

Point Element Traits and their MDL Extractor Functions
Element Trait Extractor Function
Cell Origin mdlCell_extract
Cell Rotation mdlCell_extract
Cell Scale mdlCell_extract
Shared Origin mdlSharedCell_extract
Shared Rotation mdlSharedCell_extract
Shared Scale mdlSharedCell_extract
Text Origin mdlText_extractWide
Text Rotation mdlText_extractWide
Text Scale always 1.0
Tag Origin mdlTag_extract
Tag Rotation mdlTag_extract
Tag Scale always 1.0

We need a class hierarchy that abstracts the traits and hands each trait implementation to the appropriate derived class CellHeaderElm, TextElm, etc. There are two approaches we can use: virtual inheritance or the curious recurring template pattern (CRTP).

Static Inheritance from an Interface

Template metaprogramming enables static class polymorphism: a base class defines an interface, and derived classes implement that interface. This is static polymorphism, because the implementation functions are obtained via the class's inheritance chain at compile-time. In other words, something like this …

//  Traits class specifies an interface
template <typename Derived>
struct IPointTraits
{
    DPoint3d Origin ()      {  return [what goes here?]   }
    RotMatrix Rotation ()   {  return [what goes here?]   }
    double Scale ()         {  return [what goes here?]   };
};

Each interface method is specified, but what goes here? Thanks to the curious recurring template pattern, what goes there is the derived class implementation of that interface …

//	Most-derived text class implements the interface
struct TextElm : public IPointTraits <TextElm>
{
    DPoint3d	OriginImpl ()
    {
        DPoint3d origin;
        mdlText_extractWide (&origin,  …);
        return origin;
    }
     … etc
};
//	Most-derived tag class implements the interface
struct TagElm : public IPointTraits <TagElm>
{
    DPoint3d	OriginImpl ()
    {
        DPoint3d origin;
        mdlTag_extract (&origin,  …);
        return origin;
    }
     … etc
};

Curious Recurring Template Pattern

The curious recurring template pattern (CRTP) provides static polymorphism. Template programming lets us define the implementation statically (at compile time). I'm not going to attempt to explain CRTP here, but I'll show what we can do. Here's the CRTP implementation of the IPointTraitsCRTP interface …

//  base template class specifies an interface
template<typename Derived>
struct IPointTraitsCRTP
{
    //	Convenience function
    Derived& derived ()
    {
        return static_cast<Derived&>(*this);
    }
    //	public interface hands implementation to the Derived class
    DPoint3d Origin ()
    {
        return derived ().OriginImpl ();
    }

	...etc.
};
//	Most-derived text class implements the interface
struct TextElm : public IPointTraitsCRTP<TextElm>
{
    DPoint3d    OriginImpl ()
    {
        DPoint3d origin;
        mdlText_extractWide (&origin,  …);
        return origin;
    }
    ...etc.
};
//	Most-derived tag class implements the interface
struct TagElm : public IPointTraitsCRTP<TagElm>
{
    DPoint3d    OriginImpl ()
    {
        DPoint3d origin;
        mdlTag_extract (&origin,  …);
        return origin;
    }
    ...etc.
};

Summary of Element Class Hierarchy

The element class hierarchy has three levels …

  1. Element base class
  2. TypedElement inherits from Element and is characterised by its MicroStation element type
    • TypedElement also inherits from one or more traits template classes, such as AreaTypes. Those classes specify an interface to be inherited. For example the Area() method
  3. Concrete element type classes, for example LineStringElm, inherit from TypedElement
    • Concrete classes implement an element-specific interface. A TextElm, for example, provides the Text() method

Element Base Class

The MicroStation software developers kit (SDK) delivers the MicroStation Development Library (MDL) and the MicroStationAPI. MDL is a library of C-style functions; the MicroStationAPI provides a C++ interface. If you want more information about MicroStation or its SDK, contact Bentley Systems.

The Element base class implements methods common to all MicroStation element types, such as Element ID, element type and symbology. It owns an EditElemHandle (from the MicroStationAPI), which we expect to be initialised with an ElementRef and DgnModelRef when the class is constructed.

Type MSElementTypes appears frequently in these articles. It is an enum introduced by MDL header file <mselems.h>.

class Element
{
private:
    //  No copy or assignment, which are disallowed in EditElemHandle
    Element (Element const&);
    Element& operator=(Element const&);

protected:
    Bentley::Ustn::Element::EditElemHandle  eeh_;

public:
    //  Construction
    Element         (MSElementTypes     MetaType)
    :   MetaType_   (MetaType)
    {
    }
    Element         (ElementRef         elRef,
                    DgnModelRefP        modelRef,
                    MSElementTypes      metaType)
    :   eeh_        (elRef, modelRef),
        MetaType_   (metaType)
    {
    }
    Element         (MSElementCP        el,
                    DgnModelRefP        modelRef,
                    MSElementTypes      metaType)
    :   eeh_        (el,    modelRef),
        MetaType_   (metaType)
    {
    }
    Element         (MSElementDescrP    descr,
                    bool                owned,
                    bool                isUnmodified,
                    MSElementTypes      metaType)
    :   eeh_        (descr,  owned, isUnmodified),
        MetaType_   (metaType)
    {
    }

    virtual ~Element () {}
    //  Implementation common to all elements
    const MSElementTypes   MetaType_;
    MSElementTypes      MetaType        ()          { return MetaType_; }

    MSElementTypes      Type            ()
    {
        const MSElementTypes&   t   = IsValid ()? static_cast <MSElementTypes> (elementRef_getElemType (eeh_.GetElemRef ()))
                                                : static_cast<MSElementTypes>(0);
        return t;
    }
    bool                IsValid         ()          {   return eeh_.IsValid ();         }
    //	Check IsValid before calling any of the following …
    MSElementDescrP     GetElemDescrP   ()          {   return eeh_.GetElemDescrP ();   }
    MSElementP          GetElementP     ()          {   return eeh_.GetElementP ();     }
    ElementRef          GetElementRef   ()          {   return eeh_.GetElemRef ();      }
    ElementID           GetElementID    ()          {   return mdlElement_getID (eeh_.GetElementP ()); }
    DgnModelRefP        GetModelRef     ()          {   return eeh_.GetModelRef ();     }
    std::wstring        Describe        ()
    {
        if (IsValid ())
        {
            using namespace Bentley::Ustn::Element;
            Handler&           handler = eeh_.GetHandler ();
            Bentley::WString   wcDescr;
            enum StringLength
            {
               DesiredLength = 128,   //  See MicroStationAPI documentation
            };
            handler.GetDescription (eeh_, wcDescr, DesiredLength);
            return std::wstring (wcDescr.c_str ());
        }
        //else
        return std::wstring (L"Element not initialised");
    }
};

TypedElement Intermediate Class

Typed Element Class

The TypedElement class provides the inheritance framework. It inherits from all the interface classes (ILinear etc.). However, the inheritance of a set of meaningful interface methods takes place only if the element type (elemType) enables that interface. It the element does not match the interface type list then TypedElement inherits an empty class, which the C++ optimiser will magically erase.

As discussed in previous articles, the conditional inheritance is a compile-time decision. Once an element class, such as LineStringElm, is instantiated all its interface methods are defined. As we are using template classes to implement polymorphism using curious recurring template pattern (CRTP), those interface methods are chosen at compile-time. There are no virtual methods and the class does not have a vtable.

template <typename elemType, typename Derived>
class TypedElement : public Element,
                     public elemType,
                     public ILinear<elemType, Derived>,
                     public IArea<elemType,   Derived>,
                     public IPoint<elemType,  Derived>,
                     public IText<elemType,   Derived>
{
    static const MSElementTypes MetaType_ = static_cast<MSElementTypes>(elemType::value);
public:

    //  Construction
    TypedElement        ()
    :   Element         (MetaType_)
    {
    }
    TypedElement        (ElementRef         elRef,
                        DgnModelRefP        modelRef)
    :   Element         (elRef, modelRef, MetaType_)
    {
    }
    TypedElement        (MSElementCP        el,
                        DgnModelRefP        modelRef)
    :   Element         (el,    modelRef, MetaType_)
    {
    }
    TypedElement        (MSElementDescrP    descr,
                        bool                owned,
                        bool                isUnmodified)
    :   Element         (descr,  owned, isUnmodified, MetaType_)
    {
    }
    virtual ~TypedElement () {}
    //  Implementation

    //	The following static bool functions provide run-time
    //	access to facts established at compile-time 
    //  Complex elements
    static bool                 IsComplex   ()
    {
        using namespace LASolutions::ElementTypes;
        return IsTypeInList<ComplexTypes, elemType>::value;
    }
    //  Linear elements
    static bool                 IsLinear    ()
    {
        using namespace LASolutions::ElementTypes;
        return IsTypeInList<LinearTypes, elemType>::value;
    }
    //  Point elements
    static bool                 IsPoint     ()
    {
        using namespace LASolutions::ElementTypes;
        return IsTypeInList<PointTypes, elemType>::value;
    }
    //  Area elements
    static bool                 HasArea     ()
    {
        using namespace LASolutions::ElementTypes;
        return IsTypeInList<AreaTypes, elemType>::value;
    }
    //  Text and tag elements
    static bool                 HasText     ()
    {
        using namespace LASolutions::ElementTypes;
        return IsTypeInList<TextTypes, elemType>::value;
    }
};

Interface Traits Classes

Element Class Hierarchy

There are a number of interface traits classes. Each interface class focusses on a particular trait exhibited by MicroStation elements. For example, the IPoint class defines the set of methods to be provided by MicroStation elements that are 'point' objects. That term includes those elements that need a single point (DPoint3d) coordinate to determine their location in a DGN model. For example, text elements, cell elements and zero-length lines are point objects.

Other interfaces define methods to be implemented by other element classes. We won't discuss them all here because of their repetitive nature, at least for the purpose of understanding the C++ technicalities of each interface. However, no two interfaces define the same method. For example, IPoint defines the Origin method, but no other interface defines an Origin method.

Each interface class is a template class. There are three parts to each interface template …

  1. generic template A generic template. The generic template is never chosen
  2. specialised template A template class, containing method definitions, that is specialised for each element type in the type list of that interface
  3. empty template An empty template class specialised for each element type that is not in the type list of that interface
Traits Template Class

The generic template is never instantiated, because the specialisations are always a better choice for the compiler. The template specialisation that defines interface methods is matched when the element type is found in the list of element types for that interface. For example, the IPoint interface matches an element type in the PointTypes list. If the element type is not in the list, then the second specialisation is instantiated. But the unmatched specialisation is an empty class, and so it defines no interface methods.

generic templateGeneric Interface Class

Here's the unimpressive generic template class. It doesn't need a body because it's never instantiated …

template<typename elemType, typename Derived, typename enabler = void>
struct IPoint;

specialised templateMatched Interface Class

Here's the class template specialisation for an element type that is found in the element type list …

template<typename elemType, typename Derived>
struct IPoint
<
    elemType, Derived
    typename boost::enable_if
    < typename
        LASolutions::ElementTypes::IsTypeInList
        <
            LASolutions::ElementTypes::PointTypes, elemType
        >::type
    >::type
>
{
    //	Helper method
    Derived& derived ()
    {
       return static_cast<Derived&>(*this);
    }

    //	Interface methods
    DPoint3d        Origin    ()   {  return derived ().OriginImpl ();  }
};

Working from innermost to outermost, essential parts of that template are …

The class body contains the interface methods (in this example there is only one). It also contains the derived() helper function, for internal use only …

The derived() function yields a reference to the most-derived class instance. The most-derived class implements the interface methods, which the traits class calls directly (i.e. there are no virtual functions).

empty templateNon-Matched Interface Class

Here's the class template specialisation for an element type that is not found in the element type list …

template<typename elemType, typename Derived>
struct IPoint
<
    elemType, Derived
    typename boost::enable_if
    < typename boost::mpl::not_
        <
            LASolutions::ElementTypes::IsTypeInList
            <
                LASolutions::ElementTypes::PointTypes, elemType
            >
        >::type
    >::type
>
{
    //	No interface methods!
};

Working from innermost to outermost, essential parts of that template are …

The empty class body contains no interface methods.

Concrete Element Class

Element Class

A concrete class models a MicroStation element. For example, LineStringElm and TextElm represent a MicroStation line-string element and text element respectively. The concrete class is the end product of a chain of inheritance that includes …

Note that the class TextElm is passed as a template argument to TypedElement, which in turn passes it to each interface template class. This is the curious recurring template pattern.

class TextElm : public TypedElement<LASolutions::ElementTypes::TextType, TextElm>
{
public:
    //  Construction
    TextElm             ()  {}
    TextElm             (ElementRef         elRef,
                        DgnModelRefP        modelRef)
    :   TypedElement    (elRef, modelRef)
    {
    }
    TextElm             (MSElementCP        el,
                        DgnModelRefP        modelRef)
    :   TypedElement    (el,    modelRef)
    {
    }
    TextElm             (MSElementDescrP    descr,
                        bool                owned,
                        bool                isUnmodified)
    :   TypedElement    (descr,  owned, isUnmodified)
    {
    }

    virtual ~TextElm () {}
    //	Implementation
    //	Origin() is defined by the IPoint interface and calls this implementation method
    DPoint3d    OriginImpl ()
    {
        DPoint3d    origin  = { 0.0, 0.0, 0.0 };
        //	MDL methods not shown
        return origin;
    }
    //	Text() is defined by the IText interface and calls this implementation method
    std::wstring    TextImpl    ()
    {
        //	MDL methods not shown
        return std::wstring (L"Write this code!");
    }
};

Smart Pointer types

The header file provides several type definitions. These include smart pointer types, anticipating the use of these classes in standard C++ collection classes and their manufacture by a class factory …

typedef boost::shared_ptr<Element>          ElementPtr;
typedef boost::shared_ptr<TextElm>          TextElmPtr;
typedef boost::shared_ptr<LineStringElm>    LineStringElmPtr;
typedef boost::shared_ptr<EllipseElm>       EllipseElmPtr;
typedef boost::shared_ptr<CellHeaderElm>    CellHeaderElmPtr;
typedef boost::shared_ptr<LineElm>          LineElmPtr;

Related Articles about Meta Programming

Index
MPL: type lists Type Lists and the MPL
Object Inheritance Conditional Inheritance and the MPL
Polymorphic Element Classes Polymorphic Classes for MicroStation Elements
ElementPolymorphicDynamic.h Dynamic Polymorphic Element header file overview
ElementPolymorphicStatic.h Static Polymorphic Element header file overview
Element Factory Element Factory
Development Tool Versions

Questions

Post questions about MicroStation programming to the MicroStation Programming Forum.