2

Given the following type hierarchy

class Base { public: virtual ~Base(); }
class OurDervied : public Base {}
class TheirDerived : public Base {}

class General { public: virtual ~General(); }
class MySpecial : public General {};
class YourSpecial : public General {};

I have a function f(Base *bp).

In f, I want to create an object with type that depends on the type passed in. For example, f creates a MySpecial when receiving an instance of OurDerived, and creates a YourSpecial when receiving an instance of TheirDerived.

I think I can do this with dynamic_cast. It probably requires trying to cast the received object repeatedly until a match is found (non-nullptr returned).

Another option is giving OurDerived, TheirDerived, etc a unique tag and then use a switch case construct to create MySpecial, YourSpecial, etc.

Are there any other options for mapping class types in C++?

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
SundayMonday
  • 19,147
  • 29
  • 100
  • 154
  • 1
    Do `MyClass` and `TheirClass` derive from a common base class (call it `BaseClass`) and would it be acceptable to delegate construction of the object to a polymorphic factory function that returns a `BaseClass`? – 5gon12eder Feb 14 '15 at 20:18
  • @5gon12eder yes, `MyClass` and `TheirClass` derive from a common base class. Yes, it might be acceptable. – SundayMonday Feb 14 '15 at 21:17
  • 1
    That information is quite critical so I took the liberty to edit it into your question. I hope you won't mind my little renaming of the types to make them easier to remember. – 5gon12eder Feb 14 '15 at 22:24
  • 1
    Would `CRTP` not be a good option here? Unless you really need it all done at runtime.. – Brandon Feb 14 '15 at 22:31

3 Answers3

3

Manual Type Switching

If the types you want to create have no common ancestor, you have no other option but use a

if (dynamic_cast<const DerivedA *>(&base))
  {
    // Create an object of some type.
  }
else if (dynamic_cast<const DerivedB *>(&base))
  {
    // Create an object of some other type.
  }
else if (dynamic_cast<const DerivedC *>(&base))
  {
    // Create an object of yet aother type.
  }
else
  {
    // Handle the case that no type was matched.  Maybe use a default or
    // issue an error.
  }

cascade and there is no direct way you can return the created object because a function cannot decide at run-time what return type it wants to have. The only way out would be to use type erasure or ugly unions.

Lookup Table with Factory Functions

Fortunately, this is not what you have to do if all the types you want to create are derived from a common base class, as you have indicated in the comments. In this case, you can map the typeid of an object to a factory function that creates the appropriate object. As usual with run-time polymorphism, this requires a heap allocation.

void
take_action(const Base& base)
{
  using FactoryT = std::function<std::unique_ptr<General>()>;
  static const std::map<std::type_index, FactoryT> factories {
    {typeid(DerivedA), [](){ return std::make_unique<Special1>(); }},
    {typeid(DerivedB), [](){ return std::make_unique<Special2>(); }},
    {typeid(DerivedC), [](){ return std::make_unique<Special3>(); }},
  };
  const auto o_uptr = factories.at(typeid(base))();
  // Use the object.  It can also be returned.
}

I have made the std::map<std::type_index, std::function<FactoryT()>> static so it is created only once for the entire run-time of the program. It is not clear whether or not this is beneficial in your particular situation. Maybe benchmark it.

Here is a complete working example.

#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <typeindex>
#include <typeinfo>

struct Base
{
  virtual ~Base() = default;

  virtual std::string
  name() const
  {
    return "Base";
  }
};

std::ostream&
operator<<(std::ostream& os, const Base& base)
{
  return os << base.name();
}

template<char Token>
struct Derived : Base
{
  virtual std::string
  name() const override
  {
    std::string name {"Derived"};
    name += Token;
    return name;
  }
};

using DerivedA = Derived<'A'>;
using DerivedB = Derived<'B'>;
using DerivedC = Derived<'C'>;

struct General
{
  virtual ~General() = default;

  virtual std::string
  name() const
  {
    return "General";
  }
};

template<char Token>
struct Special : General
{
  virtual std::string
  name() const override
  {
    std::string name {"Special"};
    name += Token;
    return name;
  }
};

std::ostream&
operator<<(std::ostream& os, const General& general)
{
  return os << general.name();
}

using Special1 = Special<'1'>;
using Special2 = Special<'2'>;
using Special3 = Special<'3'>;

void
take_action(const Base& base)
{
  using FactoryT = std::function<std::unique_ptr<General>()>;
  static const std::map<std::type_index, FactoryT> factories {
    {typeid(DerivedA), [](){ return std::make_unique<Special1>(); }},
    {typeid(DerivedB), [](){ return std::make_unique<Special2>(); }},
    {typeid(DerivedC), [](){ return std::make_unique<Special3>(); }},
  };
  const auto o_uptr = factories.at(typeid(base))();
  std::cout << base << " was mapped to " << *o_uptr << std::endl;
}

int
main()
{
  take_action(DerivedA {});
  take_action(DerivedB {});
  take_action(DerivedC {});
  return 0;
}

Output:

DerivedA was mapped to Special1
DerivedB was mapped to Special2
DerivedC was mapped to Special3

Visitor Pattern

Of course, you should ask yourself the question why you actually want to do this. There are for sure legitimate applications of this technique but taking an abstract type and then taking action based on its dynamic type is usually a sign of over-abstraction and makes for poorly maintainable code. Did you consider adding the factory directly to Base?

struct Base
{
  virtual ~Base() = default;

  virtual std::unique_ptr<General>
  getDealer() = 0;

  // ...

};

The Derived classes can then override getDealer to do what the factories lambdas did in the above example.

If this seems to intrusive (maybe the Base class shouldn't know anything about the General class at all), you could consider using the visitor pattern. It is a bit more work but allows for better decoupling. There is plenty of information available on this pattern so I'll only show its application to your specific problem and refer you to your favorite search engine if you need more explanation.

#include <iostream>
#include <memory>
#include <string>

struct BaseVisitor;

struct Base
{
  virtual ~Base() = default;

  virtual void
  accept(BaseVisitor&) const = 0;

  virtual std::string
  name() const
  {
    return "Base";
  }
};

std::ostream&
operator<<(std::ostream& os, const Base& base)
{
  return os << base.name();
}

template<char Token>
struct Derived : Base
{
  virtual void
  accept(BaseVisitor& vtor) const override;

  virtual std::string
  name() const override
  {
    std::string name {"Derived"};
    name += Token;
    return name;
  }
};

using DerivedA = Derived<'A'>;
using DerivedB = Derived<'B'>;
using DerivedC = Derived<'C'>;

struct BaseVisitor
{
  virtual ~BaseVisitor() = default;

  virtual void
  visit(const DerivedA&) = 0;

  virtual void
  visit(const DerivedB&) = 0;

  virtual void
  visit(const DerivedC&) = 0;
};

// Cannot be defined earlier because we need the complete type of BaseVisitor.
template<char Token>
void
Derived<Token>::accept(BaseVisitor& vtor) const
{
  vtor.visit(*this);
}

struct General
{
  virtual ~General() = default;

  virtual std::string
  name() const
  {
    return "General";
  }
};

template<char Token>
struct Special : General
{
  virtual std::string
  name() const override
  {
    std::string name {"Special"};
    name += Token;
    return name;
  }
};

std::ostream&
operator<<(std::ostream& os, const General& general)
{
  return os << general.name();
}

using Special1 = Special<'1'>;
using Special2 = Special<'2'>;
using Special3 = Special<'3'>;

void
take_action(const Base& base)
{
  struct Mapper : BaseVisitor
  {
    std::unique_ptr<General> uptr {};

    virtual void
    visit(const DerivedA&) override
    {
      this->uptr.reset(new Special1 {});
    }

    virtual void
    visit(const DerivedB&) override
    {
      this->uptr.reset(new Special2 {});
    }

    virtual void
    visit(const DerivedC&) override
    {
      this->uptr.reset(new Special3 {});
    }
  };
  Mapper visitor {};
  base.accept(visitor);
  std::cout << base << " was mapped to " << *visitor.uptr << std::endl;
}

int
main()
{
  take_action(DerivedA {});
  take_action(DerivedB {});
  take_action(DerivedC {});
  return 0;
}

Note how we have nicely broken the coupling between Base and General. On the down side, we had to introduce some kind of parent-to-child dependency via the BaseVisitor class.

This solution also gets completely rid of any explicit run-time type inference and elegantly lets the dynamic dispatch machinery do all the magic behind the scenes.

Community
  • 1
  • 1
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
2

Yes, you can delegate type mapping to derived classes:

class Base
{
public:
   virtual General* map() = 0;
};
class OurDerived: public Base
{
protected:
   General* map()
   {
      // compute Type* for OurDerved
   }
};
class TheirDerived: public Base
{
protected:
   General* map()
   {
      // compute Type* for TheirDerived
   }
};
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
Valeri Atamaniouk
  • 5,125
  • 2
  • 16
  • 18
1

It's hard to say without knowing what responsibilities your function has, or how you feel about coupling {My|Your}Special to {Our|Their}Derived.

Is Base constructible? Is Base or its derived classes allowed to have virtual methods? If you already incurred the cost of a vtable, I would delegate the responsibility to the derived types themselves, and explicitly make the method abstract on Base to force each derivation to explain itself in this regard.

Are MySpcial / YourSpecial related in the type hierarchy? Otherwise you are better off experimenting with explicit template instantiations of a helper function.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
ZaldronGG
  • 924
  • 8
  • 13