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 union
s.
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.