Without RTTI, in C++ how can I determine at runtime if an object in a collection implements an interface

Viewed 151

I have a collection of objects that share a common base class, but additionally some of them implement one or more interfaces. When iterating over them, I want to be able to run the methods of these interfaces only if the object is of a class that implements them.

Let's say I have something like this:

include <iostream>
#include <vector>

using namespace std;

class IMovable
{
public:
  virtual bool canFly ()
  {
    return false;
  }

  virtual bool canSwim ()
  {
    return false;
  }

  virtual bool canCrawl ()
  {
    return false;
  }
};

class IFlying: virtual public IMovable
{
public:
  virtual void fly () = 0;
};

class IAquatic: virtual public IMovable
{
public:
  virtual void swim () = 0;
};

class ITerrestrial: virtual public IMovable
{
public:
  virtual void crawl () = 0;
};

class Animal
{
public:
  void eat ()
  {
    cout << "Yummy";
  }
};

class Bird:public Animal, public IFlying
{
public:
  void fly () override
  {
    cout << "Fly like a bird";
  }
  bool canFly () override
  {
    return true;
  }
};

class Frog:public Animal, public IAquatic, public ITerrestrial
{
public:
  void swim () override
  {
    cout << "Swimming frog";
  }
  void crawl () override
  {
    cout << "Ribbit";
  }
  bool canSwim () override
  {
    return true;
  }
  bool canCrawl () override
  {
    return true;
  }
};


main ()
{
  std::vector < IMovable * >animals;

  Frog f;
  Bird b;

  animals.push_back (&f);
  animals.push_back (&b);
  for (auto animal:animals)
  {
    if (animal->canCrawl ())
    {
      static_cast < ITerrestrial * >(animal)->crawl ();
    }

    if (animal->canSwim ())
    {
      static_cast < IAquatic * >(animal)->swim ();
    }

    if (animal->canFly ())
    {
      static_cast < IFlying * >(animal)->fly ();

    }
      return 0;
  }
}

This doesn't work because I have virtual inheritance, but if I don't have it the base is ambiguous, and if I don't have the vector set to a base type of the interfaces, I cannot downcast. I cannot use RTTI unfortunately, so that is out of the question.

I wonder if there is perhaps a less convoluted way to achieve this result. Not having to do a bunch of static_casts would be a big bonus.

1 Answers

One method is to expose interface conversion functions explicitly from the main interface:

#include <vector>
#include <iostream>

struct IFlying;
struct IAquatic;
struct ITerrestrial;

struct IMovable {
    virtual ~IMovable() noexcept = default;
    virtual IFlying* canFly() noexcept { return 0; }
    virtual IAquatic* canSwim() noexcept { return 0; }
    virtual ITerrestrial* canCrawl() noexcept { return 0; }
};

struct IFlying : virtual IMovable {
    virtual void fly() = 0;
};

struct IAquatic : virtual IMovable {
    virtual void swim() = 0;
};

struct ITerrestrial : virtual IMovable {
    virtual void crawl() = 0;
};

struct Bird : IFlying {
    void fly() override { std::cout << "Fly like a bird\n"; }
    IFlying* canFly() noexcept override { return this; }
};

struct Frog : IAquatic, ITerrestrial {
    void swim() override { std::cout << "Swimming frog\n"; }
    void crawl() override { std::cout << "Ribbit\n"; }
    IAquatic* canSwim() noexcept override { return this; }
    ITerrestrial* canCrawl() noexcept override { return this; }
};

int main() {
    std::vector<IMovable*> animals;
    Frog f;
    Bird b;
    animals.push_back(&f);
    animals.push_back(&b);
    for(auto animal : animals) {
        if(ITerrestrial* t = animal->canCrawl())
            t->crawl();
        if(IAquatic* t = animal->canSwim())
            t->swim();
        if(IFlying* t = animal->canFly())
            t->fly();
    }
}

This mechanism is more efficient than dynamic_cast. dynamic_cast chases type_info pointers of the base classes in the inheritance hierarchy trying to find the required base class, whereas these conversions cost one virtual call.


Instead of named conversion functions like:

virtual IFlying* canFly() noexcept { return 0; }

They can also be implicit conversion operators:

virtual operator IFlying*() noexcept { return 0; }

And then:

if(IFlying* t = *animal)
    t->fly();
Related