This is an augmentation to @MichaelAnderson's solution.
Do the same FactoryBonusModifier
, but don't generate the enum
and array in python. Do it in template meta programming.
First, some boilerplate. This stuff is just a toolkit for template metaprogramming:
template<typename...>
struct type_list {};
// get the nth type from a list:
template<int n, typename list>
struct nth_type;
// the 0th type is the first type:
template<0, typename t0, typename... types>
struct nth_type<0, type_list<t0,types...>> {
typedef t0 type;
};
// the nth type is the n-1th type of the tail of the list:
template<int n, typename t0, typename... types>
struct nth_type<n, type_list<t0,types...>>:nth_type<n-1,type_list<types...>>
{};
// Get the index of T in the list. 3rd parameter is for metaprogramming.
template<typename T, typename list, typename=void>
struct index_in;
// If T is the first type in the list, the index is 0
template<typename T, typename t0, typename... list>
struct index_in<T, type_list<t0, list...>, typename std::enable_if<std::is_same<T,t0>::value>::type> {
enum {value = 0};
};
// If T is not the first type in the list, the index is 1 plus the index of T
// in the tail of the list:
template<typename T, typename t0, typename... list>
struct index_in<T, type_list<t0, list...>, typename std::enable_if<!std::is_same<T,t0>::value>::type> {
enum {value = index_in<T, type_list<list...>>::value+1};
};
// calls () on the arguments passed to it in order:
inline void do_in_order() {}
template<typename L0, typename... Lambdas>
void do_in_order( L0&& l0, Lambdas&&... lambdas ) {
std::forward<L0>(l0)(); // std::forward is for insane corner cases, not usually needed
do_in_order( std::forward<Lambdas>(lambdas)... );
}
We have type_list
, which packages up a list of types to pass around or manipulate. We have two operations on type_list
, nth_type
which extracts a type from the list from its index, and index_in
which takes a type and returns its index.
Finally, we have a helper function called do_in_order
, which we can use with parameter pack expansion to iterate over the parameter pack and do an action for each element in the parameter pack. I prefer it to other hacks, because it is easy to write and results in fewer surprises.
Once we have this basic set of tools, we can really easily write a Factory Factory:
// bad name for this template. It takes the type list list and applies
// the producer template on each, then stores pointers to instances of those
// in an array of base pointers (well unique_ptrs). It also provides
// a type-to-index mapping, an index-to-instance mapping, and a type-to
// instance mapping:
template<typename list, template<typename>class producer, typename base>
struct mapping;
template<typename... Ts, template<typename>class producer, typename base>
struct mapping<type_list<Ts...>, producer, base>
{
enum Enum {
min_value = 0,
max_value = sizeof...(list)
};
template<typename T>
static Enum GetIndex() constexpr { return (Enum)index_in<T, type_list<Ts...>>::value; }
std::unique_ptr<base> Array[max_value];
mapping() {
do_in_order( [&Array](){
Array[ GetIndex<Ts>() ].reset( new producer<Ts>() );
}... );
}
// typed get:
template<typename T>
producer<T>* GetItem() const {
return static_cast<producer<T>*>( Array[GetIndex<T>].get() );
}
// index get:
base* GetItem(std::size_t n) const {
if (n >= max_value)
return nullptr;
return Array[n].get();
};
which does nothing as yet. Add in Michaels's answer:
class FactoryBonusModifier
{
public:
/// Function to overload
virtual BonusModifierAbstract* createBonus() const = 0;
protected:
};
template<typename Bonus>
class Factory : public FactoryBonusModifier
{
public:
virtual Bonus* createBonus() const
{
return new Bonus();
}
};
then feed Factory
and a list of supported types to mapping
:
type_list< ItemBonus, InheritBonus, TaxBonus, PerformanceBonus > BonusList;
typedef mapping< BonusList, Factory, FactoryBonusModifier > MetaFactory_t;
MetaFactory_t MetaFactory;
and we are done.
MetaFactory_t::Enum
is a type that represents individual factories, as well as their offset into an array. To get the Enum
value for a given bonus type, MetaFactory_t::GetIndex<BonusType>()
gives you that.
If you want the factory for a given bonus type, MetaFactory.GetItem<BonusType>()
will return a properly typed pointer to Factory<BonusType>
. If you have the Enum
value n
, then MetaFactory.GetItem(n)
returns a pointer to the FactoryBonusModifier
base class (and nullptr
if n
is out of range).
The factories lifetime is managed by the lifetime of the MetaFactory
object.
In short, code generation was often used for this sort of thing before templates. But variardic templates make integer<->
type mapping pretty darn easy, and allow quite impressive code generation.
As a side bonus, this version includes lots of type safety that the python-generated version didn't. I could even write a function that takes n
, and calls a passed in functor with a pointer to proper type of the factory in question, generating even more type safety.
Now, if you had over a few 100 bonus types, this technique gets more difficult, because compiler recursion limits are hit. Even then, there are techniques to allow lists of 1000s of types to be processed this way (they are, however, more awkward).
The above code has not been compiled, so almost certainly contains bugs, but the base design is solid -- I've done this before.