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 classes that model 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 inheritance. Conditional inheritance lets a class inherit from base classes that meet specified criteria. It enables either dynamic or static polymorphism.

In this article we review a polymorphic element class that implements dynamic polymorphism. That is, we have interface classes that declare pure virtual methods that the concrete element class must implement. If you're interested in reading about static polymorphism then skip to the following article.

Multiple Class Inheritance

The element class is declared and implemented in a header file ElementPolymorphicDynamic.h. That header file includes …

  1. The Element base class
  2. Multiple traits interface classes (e.g. ILinear, IPoint) that provide specialisation
  3. The TypedElement specialisation class
  4. A concrete element class (e.g. LineStringElm) for each MicroStation element type. Each concrete class inherits from the Element, TypedElement and one or more interface classes

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.

template <typename elemType>
class TypedElement : public Element,
                     public elemType,
                     public ILinear<elemType>,
                     public IArea<elemType>,
                     public IPoint<elemType>,
                     public IText<elemType>
{
    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 declarations, 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 declares 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 specifies 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 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>
struct IPoint
<
    elemType,
    typename boost::enable_if
    < typename
        LASolutions::ElementTypes::IsTypeInList
        <
            LASolutions::ElementTypes::PointTypes, elemType
        >::type
    >::type
>
{
    //	Virtual pure interface methods
    virtual DPoint3d        Origin      ()     = 0;
};

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) …

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>
struct IPoint
<
    elemType,
    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 …

class TextElm : public TypedElement<LASolutions::ElementTypes::TextType>
{
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 specified by the IPoint interface
    DPoint3d    Origin ()
    {
        DPoint3d    origin  = { 0.0, 0.0, 0.0 };
        //	MDL methods not shown
        return origin;
    }
    //	Text() is specified by the IText interface
    std::wstring    Text    ()
    {
        //	MDL methods not shown
        return std::wstring (L"Write this code!");
    }
};

Smart Pointer types

The header file defines 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.