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 metafunction that tests whether a type is present in a type list, we introduced a way to write a C++ class having conditional inheritance. Conditional inheritance leaves us free to choose whether a class implements either dynamic or static polymorphism.

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. In the examples we discuss in these articles, we map MicroStation element types to C++ classes: for example, 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 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.

In this article we show how to apply MicroStation element type lists to enable polymorphic C++ classes that model MicroStation element types.

Modelling MicroStation Elements with C++ Classes

Multiple Class Inheritance

The C++ classes that model MicroStation elements clearly need to be polymorphic: they must expose the distinct characteristics 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.

MicroStation elements share some characteristics, such as symbology, but other characteristics are unique to a particular element type. Multiple class inheritance lets us model subtle differences quite well. Using type lists and MPL to control those traits via conditional inheritance lets us automate trait selection. We'll focus on MicroStation 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. 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).

Virtual Inheritance from an Interface

The classic C++ approach is through virtual methods: a base class defines a virtual pure interface, and derived classes implement that interface using the appropriate API in the above table. This is dynamic polymorphism, because the implementation functions are obtained via the class's vtable at run-time. In other words, something like this …

//  base class specifies an interface
struct IPointTraits
{
	virtual DPoint3d Origin () = 0;
	virtual RotMatrix Rotation () = 0;
	virtual double Scale () = 0;
};
//	derived text class implements the interface
struct TextElm : public IPointTraits
{
	DPoint3d	Origin ()
	{
		DPoint3d origin;
		mdlText_extractWide (&origin,  …);
		return origin;
	}
	... etc
};
//	derived tag class implements the interface
struct TagElm : public IPointTraits
{
	DPoint3d	Origin ()
	{
		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.
};
//	derived text class implements the interface
struct TextElm : public IPointTraitsCRTP<TextElm>
{
    DPoint3d    OriginImpl ()
    {
		DPoint3d origin;
		mdlText_extractWide (&origin,  …);
		return origin;
    }
	...etc.
};
//	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 classes, such as AreaTypes
    • Each traits class specifies an interface to be implemented. For example, the Area() method
    • Each traits class specifies a unique set of interface methods. That is, no two traits classes specify the same 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

Element class

The Element base class is a single root interface for all elements. It is not templated, so that an Element* or smart pointer equivalent is available for use in collections. Any element class that derives, directly or indirectly, from that Element base class can be referenced by an Element* or smart pointer.

Element provides a small number of methods common to all derived classes. That includes element type, element ID, symbology and level. It excludes all interfaces that deal with geometry, which are specific to each derived element class.

TypedElement Intermediate Class

Typed Element class

The TypedElement templated class is, firstly, a specialised class that represents a MicroStation element type. Secondly it provides the framework for a flexible set of inherited traits classes. The traits classes define the interface for a particular set of element types. Traits are selected by the meta template programming (MPL) techniques described in Type Lists and the MPL.

Traits Intermediate Classes

Traits classes

The traits classes define the interface for a particular set of element types. Traits are selected by the meta template programming (MPL) techniques described in Type Lists and the MPL.

Each traits class defines one or more methods required for that trait. For example, the TextTypes trait defines a single method Text(). The AreaTypes trait defines several methods: Area(), Perimeter() and Centroid(). The interfaces defined by each trait class do not intersect: that is, no two traits define the same method. However, a given element class is likely to inherit multiple traits classes: for example, a TextElm yields its Origin and Text values, which are supplied by different traits classes.

Concrete Element Classes

Concrete element class

There is a concrete class for each MicroStation element type. For example, EllipseElm models a MicroStation ellipse element. The concrete class inherits common methods, applicable to all elements, from the Element base class. It is obliged to implement methods defined by the interfaces that are introduced by the various traits classes in its inheritance chain.

Dynamic versus Static Despatching

The polymorphism enabled by these metaprogramming techniques is not prescribed. That is, we can choose to use either dynamic or static polymorphic classes …

It's easier to understand a dynamic implementation if you're unfamiliar with template metaprogramming. The virtual classes are easy to distinguish from the metaprogramming, making it simpler to decipher what's going on.

Static despatch means that we have two sets of metaprogramming techniques going on simultaneously: one to handle the multiple traits selection, and another to implement the CRTP. Consequently, you may feel mentally overloaded with too many templates, or possibly with <too many angle brackets>, also known as chevron fatigue.

ElementPolymorphicDynamic.h

This header file provides the declaration and implementation of the Element, traits, TypedElement and some concrete classes. The traits define virtual pure interface methods that are implemented by the concrete classes.

ElementPolymorphicStatic.h

This header file provides the declaration and implementation of the Element, traits, TypedElement and some concrete classes. The traits define interface methods that call the actual implementation in the concrete class using the curious recurring template pattern (CRTP).


Related Articles about metaprogramming

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.