Conditional Inheritance: Introduction

This is the second in a series of articles about C++ metaprogramming. The first article introduces the Boost Meta Programming Language (MPL) and type lists. This article covers another topic of C++ metaprogramming: classes and conditional inheritance.

The C++ language continues to evolve. Two evolutionary steps were the introduction of templates and Alexander Stepanov's invention of the Standard Template Library (STL). The STL is incorporated into the C++ language standard.

An unexpected consequence of the introduction of templates was template metaprogramming. Template metaprogramming provides a mechanism to write a compile-time program. In other words, abstract application logic in a way that lets the C++ compiler check its correctness.

Template metaprogramming leads to new areas of program capability. In this article, we'll examine conditional inheritance. We'll be using concepts from the C++ Standard Library, the Boost Libraries and in particular the Boost Metaprogramming Library (MPL).

C++ Standard Library

The Element conditional inheritance and future examples assume use of the C++ Standard Library. In other words, for MicroStation developers, this is C++ stuff for which MDL (old-style C programming) is not suitable.

Boost libraries

The Element conditional inheritance use the Boost libraries, particularly the Meta Programming Library (boost::mpl).

C++ 11

C++ 11 has changed the playing field. The addition of variadic templates with their associated parameter packs added a compile-time list of types structure directly into the language. With C++ 11, type lists are as easy as …

// C++11
template<class... T> struct type_list {};

C++ Inheritance

If you're an experienced C++ developer, you know about inheritance. Skip to the next section. If you're not an experienced C++ developer, later sections of this article may seem obtuse.

Here's a C++ base class …

struct base
{
    int Foo () { return 42; }
};

Invoke that class …

int main (int argc, char* argv [])
{
    base b;
    int i = b.Foo (); // i takes value 42
}

Here's a derived class that inherits from base …

struct derived : public base
{
    int Foo2 () { return 53; }
}

Invoke that class …

int main (int argc, char* argv [])
{
    base b;
    int i = b.Foo ();  // i takes value 42

    derived d;
    int j = d.Foo2 (); // j takes value 53
    int k = d.Foo ();  // k takes value 42 from base class
}

C++ Interface Classes

Interface classes contain no data members and provide virtual pure function declarations but no function definitions. You design an interface class with the intention that it should be inherited. An interface class is useless by itself.

Here's a C++ class intended to be inherited …

struct interface
{
    //    virtual means "You should override this method"
    //    = 0 means "You must implement this method in your inheriting class
    virtual int Foo3 () = 0;
};

Derive from our interface class …

struct class_with_interface : public interface
{
    //    We must implement the interface.Foo3 method
    int Foo3 ()    { return 64; }
};

Invoke that class-with-interface …

int main (int argc, char* argv [])
{
    class_with_interface cwi;
    int i = cwi.Foo3 (); // i takes value 64
}

Conditional Inheritance: the Challenge

Suppose we want to inherit a class only in certain circumstances. We're modelling animals. If the animal is a mammal, it has four legs and can walk. If it's not a mammal, it may not be able to walk. But if it's a bird, it has two legs and can walk so deciding that an animal can't walk just because it's not a mammal is not a sound conclusion. Equally, just because an animal has two legs doesn't imply that it can fly (that would be over-generous to we humans). We need a mechanism that reveals certain traits so that we can design classes accordingly. The traits inform a derived class whether an animal can walk or fly (or even both, which is true for most birds but not the ostrich).

Here's a first attempt …

//  A class designed, as an interface, to be inherited
struct animal
{
    virtual int  nLegs  () = 0;
    virtual int  nWings () = 0;
    virtual void Walk   () = 0;
    virtual void Fly    () = 0;
};

struct mammal : public animal
{
    int  nLegs          ()  { return 4; }
    int  nWings         ()  { return 0; /* Unnecessary */ }
    void Walk           ()  { /* OK */ }
    void Fly            ()  { /* What? */ }
};

struct bird : public animal
{
    int  nLegs          ()  { return 2; }
    int  nWings         ()  { return 2; }
    void Walk           ()  { /* OK */ }
    void Fly            ()  { /* OK, but not ostrich */ }
};

Because our interface class has pure virtual methods, we have to implement each of them, even when, as in the case of mammals, it doesn't make sense (OK — bats can fly and they are mammals, but I'm writing for programmers, not biologists). We can make interface methods non-pure, so it's not obligatory to implement them, but that means the implementation lies in the programmer's forgetful domain.

Multiple Simplistic Interfaces

The mammal and bird classes cover too broad a spectrum of possibilities. It's too easy to find exceptions to the rules provided by those possibilities. We need a finer-grained mechanism to let us define animals and birds. One solution might be to use C++ multiple inheritance, then write very simple interface classes that define a single trait. Using multiple inheritance, we can derive a class having multiple traits …

struct IAnimalHasLegs
{
    virtual int nLegs   () = 0;
};

struct IAnimalHasWings
{
    virtual int nWings  () = 0;
};

struct IAnimalThatWalks
{
    virtual void Walk   () = 0;
};

struct IAnimalThatFlies
{
    virtual void Fly    () = 0;
};

Now we can design an animal class that doesn't have anomalies …

struct Rabbit : public IAnimalHasLegs, IAnimalThatWalks
{
    int  nLegs  ()  { return 4; }
    void Walk   ()  { /* OK */ }
};

struct Duck : public IAnimalHasLegs, IAnimalThatWalks, IAnimalHasWings, IAnimalThatFlies
{
    int  nLegs  ()  { return 2; }
    int  nWings ()  { return 2; }
    void Fly    ()  { /* OK */ }
    void Walk   ()  { /* OK, but should be 'Waddle' */ }
};

struct Bat : public IAnimalHasLegs, IAnimalThatWalks, IAnimalHasWings, IAnimalThatFlies
{
    int  nLegs  ()  { return 2; }
    int  nWings ()  { return 2; }
    //    Although a bat is a mammal, it can both walk and fly
    void Walk   ()  { /* OK */ }
    void Fly    ()  { /* OK */ }
};

struct Ostrich : public IAnimalHasLegs, IAnimalThatWalks, IAnimalHasWings
{
    int  nLegs  ()  { return 2; }
    int  nWings ()  { return 2; }
    void Walk   ()  { /* OK */ }
    //    Although an ostrich is a bird and has wings, it can't fly,
    //    so we omit interface IAnimalThatFlies
};

Automating Inheritance

The interface classes that define only a single trait let us fine-tune our derived classes. We can create derived classes at the expense of a myriad of traits classes, which as programmers we have to select carefully. Humans who are charged with selecting things carefully tend to make sloppy selections. We end up with elephants that live in burrows, or rabbits with tusks.

What we need is a way to automate the inheritance of interfaces. That is, abstract the traits of various groups of animals and provide a mechanism to inherit or not inherit them in a deterministic manner.

We already have such a mechanism: type lists. Read the previous article to learn more about type lists and Boost.MPL.

If we convert animal characteristic to traits classes and put those in a type list, we can make compile-time decisions by evaluating each type list in the context of a required characteristic. Here are some animal type definitions …

C++ 11

enum AnimalTypes { Eagle, Ostrich, Bat, Cat, Dog, Blackbird, };

using EagleType     = boost::mpl::int_<Eagle>
using BlackbirdType = boost::mpl::int_<Blackbird>
using OstrichType   = boost::mpl::int_<Ostrich>
using BatType       = boost::mpl::int_<Bat>
using CatType       = boost::mpl::int_<Cat>
using DogType       = boost::mpl::int_<Dog>

Previous versions of C++

enum AnimalTypes { Eagle, Ostrich, Bat, Cat, Dog, Blackbird, };

typedef boost::mpl::int_<Eagle>      EagleType;
typedef boost::mpl::int_<Blackbird>  BlackbirdType;
typedef boost::mpl::int_<Ostrich>    OstrichType;
typedef boost::mpl::int_<Bat>           BatType;
typedef boost::mpl::int_<Cat>           CatType;
typedef boost::mpl::int_<Dog>           DogType;

Here are some type lists that agreggate characteristics as a trait …

C++ 11

using MammalTypes    = boost::mpl::vector <CatType, DogType, BatType>
using BirdTypes      = boost::mpl::vector <BlackbirdType, OstrichType, EagleType>
using QuadrupedTypes = boost::mpl::vector <CatType, DogType>
using BipedTypes     = boost::mpl::vector <EagleType, BlackbirdType, BatType, OstrichType>
using FlyingTypes    = boost::mpl::vector <EagleType, BlackbirdType, BatType>
using WalkingTypes   = boost::mpl::vector <CatType, DogType, OstrichType>

Previous versions of C++

typedef boost::mpl::vector <CatType, DogType, BatType>
typedef boost::mpl::vector <BlackbirdType, OstrichType, EagleType>
typedef boost::mpl::vector <CatType, DogType>
typedef boost::mpl::vector <EagleType, BlackbirdType, BatType, OstrichType>
typedef boost::mpl::vector <EagleType, BlackbirdType, BatType>
typedef boost::mpl::vector <CatType, DogType, OstrichType>

Now we can ask questions about a particular animal type using boost::mpl metafunctions. First, we need a meta function that tells us if a given type exists in a type list. Here it is …

C++ 11

//    Metafunction implemented by class template IsTypeInList
template <typename TypeList, typename queryType>
struct IsTypeInList
{
    using type_pos   = typename boost::mpl::find <TypeList, queryType>::type;
    using finish     = typename boost::mpl::end  <TypeList>::type finish;
    //    By convention, MPL metafunctions return type, value_type and value
    using type       = typename boost::mpl::not_ <boost::is_same <type_pos, finish> >::type;
    using value_type = typename type::value_type value_type;
    static const bool value = type::value;
};

Previous versions of C++

//    Metafunction implemented by class template IsTypeInList
template <typename TypeList, typename queryType>
struct IsTypeInList
{
    typedef typename boost::mpl::find <TypeList, queryType>::type type_pos;
    typedef typename boost::mpl::end  <TypeList>::type finish;
    //    By convention, MPL metafunctions return type, value_type and value
    typedef typename boost::mpl::not_ <boost::is_same <type_pos, finish> >::type type;
    typedef typename type::value_type value_type;
    static const bool value = type::value;
};

We can use that function anywhere we want to test for a boolean predicate. Here's a somewhat contrived function that tells us at run-time if an Eagle can fly …

static bool                 CanFly   ()
{
    //    The following compile-time expression resolves to a
    //    static const boolean value
    return IsTypeInList<FlyingTypes, EagleType >::value;
}

Now we have all the information we need to create a conditionally-inherited class. We include a metafunction in the inheritance list of a class. The metafunction selects a base class to inherit if it has a required trait.

Here's some code that indicates where we're heading. The comment for each inherited interface class tells you what we want to do: inherit or not inherit that class …

class Eagle :   public     IAnimalHasLegs    /*  inherit       */,
                public  IAnimalThatWalks  /*  inherit       */,
                public  IAnimalThatFlies  /*  inherit       */,
                public  IAnimalHasWings   /*  inherit       */
{
};
class Ostrich : public     IAnimalHasLegs    /*  inherit       */,
                public  IAnimalThatWalks  /*  inherit       */,
                public  IAnimalThatFlies  /*  don't inherit */,
                public  IAnimalHasWings   /*  inherit       */
{
};

We need to figure out what to substitute for those comments. What metafunction can we provide that selects a given base class when an animal shows that trait? Equally important, how do we know when to omit a base class (e.g. for the ostrich that can't fly)?

Unsurprisingly, the solution is in two parts …

  1. Make the inheriting class a template class
  2. Make the interface classes polymorphic

Here's the templated derived class …

template <typename animalType>
class TypedAnimal : public  IAnimalHasLegs   <animalType>,
                    public  IAnimalThatWalks <animalType>,
                    public  IAnimalThatFlies <animalType>,
                    public  IAnimalHasWings  <animalType>
{
};

Here's the desired specialisation for Ostrich. Note that even though we might inherit from IAnimalHasWings, in this hypothetical implementation we've omitted IAnimalThatFlies and therefore don't have to implement its method Fly …

class Ostrich public : public TypedAnimal <OstrichType>
{
    //    Interface methods that we must implement
    int  nLegs ();    //    IAnimalHasLegs
    int  nWings ();    //    IAnimalHasWings
    void Walk ();    //    IAnimalThatWalks
};

How do we achieve that desired result? We must modify our interface class definitions so that they morph according to the animalType they receive from the inheritance trail above. For example, here's IAnimalHasLegs re-cast as a templated class …

//  Generalised template — this is never instantiated
template<typename animalType, typename enabler = void>
struct IAnimalHasLegs;

/// IAnimalHasLegs interface specialised for bipeds
template<typename animalType>
struct IAnimalHasLegs
<
    animalType,
    typename boost::enable_if
    < typename
        IsTypeInList
        <
            BipedTypes, animalType
        >::type
    >::type
>
{
    virtual int nLegs () = 0; // Inheriting class must implement
    ... other virtual functions
};

There remains a problem with this approach. When a trait fails the interface class metafunction test, it is not instantiated (which is what we want). However, that leaves a hole in the TypedAnimal class's inheritance list. If we could see it, it would look like this …

class Ostrich public : public TypedAnimal <OstrichType>,
                       public IAnimalWithLegs,         //    inherited
                       public IAnimalThatWalks,        //    inherited
                       /* IAnimalThatFlies */,         //    omitted
                       public IAnimalHasWings          //    inherited
{
};

The problem lies with the omitted interface IAnimalThatFlies. Because the meta-code has made it vanish, there are two commas between IAnimalThatWalks and IAnimalHasWings. Two commas make the compiler complain about a syntax error.

The solution to that problem is another templated version of the interface class. This version is instantianted only when the animal type is not in the type list, and the class body is completely empty …

//  Empty template class specialisation for animals that are not bipeds
template<typename animalType>
struct IAnimalHasLegs
<
    animalType,
    typename boost::enable_if
    <   //  This is where the test differs from the previous specialisation
        typename boost::mpl::not_
        <
            IsTypeInList
            <
                BipedTypes, animalType
            >
        >::type
    >::type
>
{
    //    Nothing here: this class is empty
};

The empty class fills the space between the commas, so the compiler is content. However, the class may not be quite empty: the compiler provides default constructors and a destructor. If that bothers you, you can write an empty constructor to override the compiler's default behaviour. Subsequently, the optimiser probably removes that empty class.

IAnimalHasLegs Complete Definition

Here's the complete definition of interface IAnimalHasLegs. To summarise, we have …

//  Generalised template — this is never instantiated
template<typename animalType, typename enabler = void>
struct IAnimalHasLegs;

/// IAnimalHasLegs interface specialised for bipeds
template<typename animalType>
struct IAnimalHasLegs
<
    animalType,
    typename boost::enable_if
    < typename
        IsTypeInList
        <
            BipedTypes, animalType
        >::type
    >::type
>
{
    //    Dynamic polymorphic implementation
    virtual int nLegs () = 0; // Derived class must implement
    ... other virtual functions
};
//  Empty template class specialisation for animals that are not bipeds
template<typename animalType>
struct IAnimalHasLegs
<
    animalType,
    typename boost::enable_if
    <
        typename boost::mpl::not_
        <
            IsTypeInList
            <
                BipedTypes, animalType
            >
        >::type
    >::type
>
{
    //    Nothing here: this class is empty
};

Eureka!

Voilà! We've achieved conditional class inheritance. At this point, we can choose to implement dynamic or static polymorphism.

Dynamic Polymorphism

Dynamic polymorphism is implemented through the traditional virtual pure interface, which is what is shown above. The interface class specifies virtual pure functions that the derived class must implement.

Static Polymorphism

Static polymorphism is achieved through templated classes. In particular, we can use the curious recurring template pattern (CRTP). CRTP tells the base class to provide an interface function that is implemented in the derived class, without the drawbacks of virtual functions.

You can read more about dynamic and static polymorphism in the next chapter of this series.

Conditional Classes and MicroStation Development

What we've described here is a general approach to a technique that enables conditional class inheritance. However, we've written this article keeping MicroStation development in mind. There's a practical example available that defines MicroStation element types, and some MPL metafunctions that operate on those classes.

Read the next episode: Polymorphic Classes for MicroStation Elements


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 C++ and the MicroStationAPI to the MicroStation Programming Forum.