1

I would like to make a macro to easily create new Classes that derive from the same base class with different name and slightly different behavior.

I already have

class FactoryBonusModifier
{
public:
   /// Function to overload
   virtual BonusModifierAbstract* createBonus() const = 0;
protected:
};

#define DEFAULT_BONUS_FACTORY_DECLARATION(FactoryName)      \
class Factory## FactoryName : public FactoryBonusModifier   \
{                                                           \
public:                                                     \
    virtual BonusModifierAbstract* createBonus() const;     \
};

#define DEFAULT_BONUS_FACTORY_IMPLEMENTATION(FactoryName)                 \
    BonusModifierAbstract* Factory## FactoryName::createBonus() const    \
    {  return new FactoryName();  }


DEFAULT_BONUS_FACTORY_DECLARATION(BonusModifierGoThroughWall);

and the implementation part writen in the cpp.

I want to know if I can have a macro that would build an enum and an array of these new classes with as little copy/paste possible.

In the end I want to have something like

enum BonusType{
Bonus1,
Bonus2,
...,
Nb_Bonus
};

FactoryBonusModifier* factories[Nb_Bonus] = 
{
    new FactoryBonus1(),
    new FactoryBonus2(),
    ...,
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Michael Ferris
  • 160
  • 1
  • 2
  • 10
  • I would not say this is strictly duplicate, as this deals specifically (at least in part) with using macros for the purpose, and the other question does not. – CodeMouse92 Feb 03 '15 at 04:06

2 Answers2

1

I'd avoid using a macro for this if you can avoid it. I'd use templates instead.

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();
    }
}

So the tricky bit is creating the enum and factory list. For this I would generate the code, rather than trying to use templates. You can integrate code generation into most build processes really easily.. but you'll need to read up on it.

Personally I'd use a really simple python script, something like

bonuses = ["BonusModifierGoThroughWall", "SomeOtherBonus" ];
print "enum BonusType {"
for x in bonuses:
  print "t"+x+","
print "nBonusTypes"
print "};"

print "FactoryBonusModifier* factories["+len(bonuses)+"] = {"
for x in bonuses:
  print "new Factory<"+bonus+">(),"
print "};"

Which should output:

enum BonusType {
  tBonusModifierGoThroughWall,
  tSomeOtherBonus,
  nBonusTypes
};

FactoryBonusModifier* factories[2] = {
  new Factory<BonusModifierGoThroughWall>(),
  new Factory<SomeOtherBonus>(),
};
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
1

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524